在contenteditable内精确拖放

安装程序

所以,我有一个自信的div – 我正在编辑一个所见即所得的编辑器:粗体,斜体,格式化,最近和最近:插入花哨的图像(在一个漂亮的盒子里,带有标题)。

<a class="fancy" href="i.jpg" target="_blank"> <img alt="" src="i.jpg" /> Optional Caption goes Here! </a> 

用户添加这些奇特的图像,我给他们一个对话框:他们填写的细节,上传图像,然后很像其他编辑器function,我使用document.execCommand('insertHTML',false,fancy_image_html); 根据用户的select将其插入。

期望的function

所以,现在,我的用户可以在一个奇特的形象扑面而来 – 他们需要能够移动它。 用户需要能够点击并拖动图像(花式框和全部)以将其放置在满意的任何地方。 他们需要能够在段落之间,甚至段落之间移动 – 如果他们想要的话,可以在两个词之间移动。

什么给我希望

请记住 – 在一个可以理解的情况下,普通的旧的<img>标签已经被这个可爱的拖放function的用户代理所祝福了。 默认情况下,您可以在任何地方拖放<img>标签。 默认的拖放操作就像人们梦寐以求的那样。

所以,考虑到这个默认行为已经对我们的<img>伙伴如此捣蛋 – 我只想将这个行为扩展一点,以包含更多的HTML – 这看起来应该是很容易实现的。

我的努力到目前为止

首先,我使用draggable属性设置了我喜欢的<a>标签,并禁用了contenteditable(不知道是否有必要,但似乎也可以closures):

 <a class="fancy" [...] draggable="true" contenteditable="false"> 

然后,因为用户仍然可以将图像拖出花哨的<a>框,我不得不做一些CSS。 我正在使用Chrome,所以我只向你展示-webkit-前缀,尽pipe我也使用了其他的。

 .fancy { -webkit-user-select:none; -webkit-user-drag:element; } .fancy>img { -webkit-user-drag:none; } 

现在,用户可以拖动整个花式框,而部分褪色的点击拖曳表示图像反映了这一点 – 我可以看到,我正在拿起整个盒子:)

我已经尝试了不同的CSS属性的几个组合,上面的组合似乎对我来说是有道理的,似乎最好的工作。

我希望这个CSS本身足以让浏览器将整个元素用作可拖动项目,自动授予用户我梦寐以求的function…… 然而,它似乎比这更复杂。

HTML5的JavaScript拖放API

这个拖放的东西似乎比它需要更复杂。

所以,我开始深入DnD api文档,现在我卡住了。 所以,这就是我所做的(是的,jQuery):

 $('.fancy') .bind('dragstart',function(event){ //console.log('dragstart'); var dt=event.originalEvent.dataTransfer; dt.effectAllowed = 'all'; dt.setData('text/html',event.target.outerHTML); }); $('.myContentEditable') .bind('dragenter',function(event){ //console.log('dragenter'); event.preventDefault(); }) .bind('dragleave',function(event){ //console.log('dragleave'); }) .bind('dragover',function(event){ //console.log('dragover'); event.preventDefault(); }) .bind('drop',function(event){ //console.log('drop'); var dt = event.originalEvent.dataTransfer; var content = dt.getData('text/html'); document.execCommand('insertHTML',false,content); event.preventDefault(); }) .bind('dragend',function(event){ //console.log('dragend'); }); 

所以这里是我卡住的地方:这几乎完全有效。 几乎完全。 我有一切工作,直到最后。 在drop事件中,我现在可以访问想要在放置位置插入的花式框的HTML内容。 我现在需要做的就是把它插入正确的位置!

问题是我无法find正确的放置位置,或以任何方式插入到它。 我一直希望find某种“dropLocation”对象来转储我的花哨的盒子,像dropEvent.dropLocation.content=myFancyBoxHTML; 或者至less可以通过某种types的放置位置值来find我自己的方式来放置内容? 我给了什么吗?

我完全错了吗? 我完全错过了什么?

我试图使用document.execCommand('insertHTML',false,content); 就像我期望的那样,我应该可以,但不幸的是,我不能在这里,因为select脱字符号并不像我希望的那样位于精确的放置位置。

我发现,如果我注释掉所有的event.preventDefault(); 的select插入符号变得可见,并且正如人们希望的那样,当用户准备放弃时,将其拖动到可用内容上,可以看到在用户的光标和放下操作之后的字符之间沿着小的select插入符号 – 指示给用户select插入符号表示精确的放置位置。 我需要这个select插页的位置。

通过一些实验,我在drop事件中尝试了execCommand-insertHTML,而dragend事件 – 既不插入drop-selection-caret所在的HTML,而是使用拖动操作之前select的任何位置。

因为在dragover期间select脱字符是可见的,我孵化了计划。

有一段时间,我在dragover事件中尝试在$('.selection-marker').remove();之后插入临时标记,如<span class="selection-marker">|</span> $('.selection-marker').remove(); ,试图让浏览器不断(在dragover期间)删除所有select标记,然后在插入点添加一个标记 – 在任何时刻基本上留下一个标记,无论插​​入点在哪里。 当然,这个计划就是用我所拥有的拖拽内容replace这个暂时的标记。

当然,这一切都没有奏效:我无法按照计划将select标记插入明显可见的select插入符号中 – 同样,在拖放操作之前,execCommand-insertedHTML将自身置于select插入符的位置。

一怒之下。 那么我错过了什么? 它是如何完成的?

如何获取或插入拖放操作的精确位置? 显然,我觉得这是一种常见的拖拽式操作 – 当然,我肯定忽视了某种重要的,公然的细节。 我甚至不得不深入研究JavaScript,或者有一种方法可以像draggable,droppable,contenteditable和一些fancydancy CSS3这样的属性来做到这一点。

我仍然在追捕 – 仍然在修补 – 我会尽快发现我一直在失败:)

非常感谢您的阅读; 任何帮助都非常感谢:)

//追。



狩猎继续(原始后编辑)


Farrukh发表了一个很好的build议 – 使用:

 console.log( window.getSelection().getRangeAt(0) ); 

看看select插入符号的实际位置。 我把这个放到了dragover事件中,当我把这个select的插入符号可见地在可编辑的内容之间跳跃的时候。

唉,返回的Range对象会在拖放操作之前报告属于select插入符号的偏移量索引。

这是一个勇敢的努力。 感谢Farrukh。

那么这里发生了什么? 我正在看到我所看到的那个小小的select脱口而出的感觉,根本就不是select的光环! 我认为这是一个冒名顶替者!

进一步检查!

原来,这是一个冒名顶替者! 在整个拖动操作过程中, 真正的select符号始终保持原位! 你可以看到这个小bug </s>!

我正在阅读MDN拖放文档 ,发现这个:

当然,您也可能需要在dragover事件周围移动插入标记。 您可以像使用其他鼠标事件一样使用事件的clientX和clientY属性来确定鼠标指针的位置。

哎呀,这是否意味着我应该为自己弄清楚,基于clientXclientY ? 使用鼠标坐标来确定select插入符号的位置? 害怕!!

我会考虑明天这样做 – 除非我自己或其他人在这里读这个,可以find一个理智的解决scheme:)

谢谢大家。 我明天更新。 //追。

龙滴

我做了一个荒谬的数额的摆弄。 所以,这么多js摆弄。

这不是一个强大的或完整的解决scheme; 我可能永远不会拿出一个。 如果任何人有更好的解决scheme,我全都听过 – 我不想这样做,但这是我迄今为止发现的唯一方法。 下面的jsFiddle以及我即将吐出的信息在我的特定WAMP设置和计算机上用我的特定版本的Firefox和Chrome工作。 不要在你的网站上无效的时候哭泣。 这个拖放废话显然是每个人为自己。

jsFiddle:追逐莫斯科的龙血滴

所以,我的女朋友的脑子里都是无聊的,她以为我真的一直在说“龙放”,我只是在说“拖放”。 它卡住了,所以这就是我为处理这些拖放情况而创build的小型JavaScript好友。

原来 – 这是一个噩梦。 即使乍一看,HTML5的拖放API也是可怕的。 然后,当你开始理解并接受它应该工作的方式时,你几乎已经热身了。然后,当你了解Firefox和Chrome如何在他们自己的特殊版本中使用这个规范时,你会意识到它实际上是多么可怕的噩梦方式,似乎完全忽略了你所有的需求。 你会发现自己提出这样的问题:“等等,现在甚至被拖动了什么元素?如何获取这些信息?我如何取消这个拖动操作?我怎样才能阻止这种特殊的浏览器对这种情况的默认处理? …对于你的问题的回答:“你自己,丢失!保持黑客入侵,直到有用的东西!”

所以,下面是我如何在多个contenteditable的内部,周围和之间完成任意HTML元素的精确拖放。 (注意:我没有深入详细地讨论每一个细节,你必须看看这个jsFiddle–我只是从我的经验中记忆下来的那些看似相关的细节,因为我的时间有限)

我的解决scheme

  • 首先,我将CSS应用于可拖动(fancybox) – 我们需要user-select:none; user-drag:element; user-select:none; user-drag:element; 在花式框上,然后user-drag:none; 在花哨的框中的图像(和其他任何元素,为什么不?)。 不幸的是,这对Firefox来说还不够,它需要在图像上明确地设置属性draggable="false" ,以防止它被拖动。
  • 接下来,我将属性draggable="true"dropzone="copy"到contenteditables上。

对于draggables(fancyboxes),我绑定了dragstart的处理dragstart 我们设置dataTransfer复制一个空白的HTMLstring – 因为我们需要欺骗它,以为我们要拖动HTML,但是我们正在取消任何默认行为。 有时默认行为会以某种方式滑动,并导致重复(因为我们自己插入),所以现在最糟糕的小故障是在拖拽失败时插入“(空格)”。 我们不能依靠默认行为,因为它会经常失败,所以我发现这是最通用的解决scheme。

 DD.$draggables.off('dragstart').on('dragstart',function(event){ var e=event.originalEvent; $(e.target).removeAttr('dragged'); var dt=e.dataTransfer, content=e.target.outerHTML; var is_draggable = DD.$draggables.is(e.target); if (is_draggable) { dt.effectAllowed = 'copy'; dt.setData('text/plain',' '); DD.dropLoad=content; $(e.target).attr('dragged','dragged'); } }); 

对于dropzones,我绑定一个dragleavedrop的处理程序。 dragleave处理程序仅适用于Firefox,因为在Firefox中,当您试图将其拖到可控材料外时,拖放工作(默认情况下,Chrome会拒绝您),因此它会对仅与Firefox相关的目标执行快速检查。 一怒之下。

Chrome和Firefox有不同的获取Range对象的方法,所以在Drop事件中,每个浏览器都需要付出不同的努力。 Chrome根据鼠标坐标 (yup是正确的)构build一个范围,但是Firefox在事件数据中提供了这个范围。 document.execCommand('insertHTML',false,blah)原来是我们如何处理下降。 OH,我忘了提及 – 我们不能在Chrome上使用dataTransfer.getData()来获得我们的dragstart集HTML – 它看起来是规范中的某种奇怪的错误。 Firefox把这个规范称为bullcrap,反正也给了我们数据 – 但是Chrome并没有这么做,所以我们向后弯,把内容设置成全局的,然后通过地狱来杀死所有的默认行为。

 DD.$dropzones.off('dragleave').on('dragleave',function(event){ var e=event.originalEvent; var dt=e.dataTransfer; var relatedTarget_is_dropzone = DD.$dropzones.is(e.relatedTarget); var relatedTarget_within_dropzone = DD.$dropzones.has(e.relatedTarget).length>0; var acceptable = relatedTarget_is_dropzone||relatedTarget_within_dropzone; if (!acceptable) { dt.dropEffect='none'; dt.effectAllowed='null'; } }); DD.$dropzones.off('drop').on('drop',function(event){ var e=event.originalEvent; if (!DD.dropLoad) return false; var range=null; if (document.caretRangeFromPoint) { // Chrome range=document.caretRangeFromPoint(e.clientX,e.clientY); } else if (e.rangeParent) { // Firefox range=document.createRange(); range.setStart(e.rangeParent,e.rangeOffset); } var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); $(sel.anchorNode).closest(DD.$dropzones.selector).get(0).focus(); // essential document.execCommand('insertHTML',false,'<param name="dragonDropMarker" />'+DD.dropLoad); sel.removeAllRanges(); // verification with dragonDropMarker var $DDM=$('param[name="dragonDropMarker"]'); var insertSuccess = $DDM.length>0; if (insertSuccess) { $(DD.$draggables.selector).filter('[dragged]').remove(); $DDM.remove(); } DD.dropLoad=null; DD.bindDraggables(); e.preventDefault(); }); 

好的,我厌倦了这一点。 我已经写了所有我想要的这个。 我每天都在打电话,如果我想到任何重要的事情,我可能会更新。

谢谢大家。 //追。

  1. 事件dragstart; dataTransfer.setData("text/html", "<div class='whatever'></div>");
  2. 事件下降: var me = this; setTimeout(function () { var el = me.element.getElementsByClassName("whatever")[0]; if (el) { //do stuff here, el is your location for the fancy img } }, 0); var me = this; setTimeout(function () { var el = me.element.getElementsByClassName("whatever")[0]; if (el) { //do stuff here, el is your location for the fancy img } }, 0);