当前位置: 首页 > 知识库问答 >
问题:

在contenteditable中精确拖放

师野
2023-03-14

所以,我有一个contenteditable div--我正在制作一个所见即所得编辑器:粗体、斜体、格式化,什么都行,最近的是:插入花哨的图像(在花哨的框中,加上标题)。

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

用户通过一个对话框添加这些精美的图像:他们填写细节,上传图像,然后和其他编辑器函数一样,我使用document.execcommand('insert html',false,fancy_image_html);按用户的选择将其插入。

所以,现在我的用户可以在一个漂亮的图像中扑通一声--他们需要能够移动它。用户需要能够单击和拖动图像(花式框和所有),以将其放置在ContentEditable中他们喜欢的任何地方。他们需要能够在段落之间移动它,甚至在段落内--如果他们愿意的话,在两个单词之间移动它。

请记住--在contenteditable中,简单的旧标记已经被用户代理赋予了这种可爱的拖放功能。默认情况下,您可以随心所欲地拖放标记;默认的拖放操作就像人们想象的那样。

因此,考虑到这种默认行为已经在我们的伙伴上发挥了巨大作用--我只想稍微扩展这种行为,以包括更多的HTML--这似乎是很容易实现的。

首先,我设置了带有draggable属性的特殊的标记,并禁用了contenteditable(不确定这是否必要,但似乎最好关闭它):

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

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

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

现在用户可以拖动整个fancy box,部分褪色的小点击拖动表示图像反映了这一点--我可以看到我现在正在拾取整个box:)

我已经尝试了几种不同CSS属性的组合,上面的组合对我来说似乎是有意义的,而且似乎工作得最好。

我希望仅此CSS就足以让浏览器使用整个元素作为可拖动项,自动授予用户我梦寐以求的功能...然而,它似乎比这更复杂。

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

所以,我开始深入研究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事件中,我现在可以访问我试图在drop位置插入的fancy Box的HTML内容。我现在需要做的就是把它插入正确的位置!

问题是我找不到正确的放置位置,或者任何插入到它的方法。我一直希望找到某种“放置位置”对象来将我想要的框放入其中,比如dropevent.droplocation.content=MyFancyBoxHTML;,或者至少是某种放置位置值,用它来找到将内容放入其中的方法?我得到什么了吗?

我做得完全不对吗?我是不是完全错过了什么?

我尝试使用document.execcommand('insert html',false,content);,但不幸的是,我在这里失败了,因为选择插入符号没有像我希望的那样位于准确的放置位置。

我发现,如果注释掉所有Event.PreventDefault();的,选择插入符号就会变得可见,并且正如人们所希望的那样,当用户准备拖放时,将其拖动悬停在contenteditable上,可以看到小的选择插入符号在用户光标和拖放操作之后的字符之间运行--向用户指示选择插入符号代表精确的拖放位置。我需要此选定插入符号的位置。

通过一些实验,我在drop事件和dragend事件中尝试了execcommand-inserthtml'ing--既不插入droping-selection-cerot所在的HTML,也不使用拖动操作之前选择的任何位置。

因为在拖动过程中选择插入符号是可见的,所以我画了一个计划。

有一段时间,我试图在dragover事件中插入一个临时标记,如,就在$('.selection-marker').remove();之后,试图让浏览器不断(在dragover过程中)删除所有选择标记,然后在插入点添加一个标记--基本上是在插入点所在的任何位置随时保留一个标记。当然,我的计划是用拖动的内容替换这个临时标记。

当然,这些操作都没有奏效:我无法按计划将选择标记插入明显可见的选择插入符号处--同样,execCommand-insertedHTML在拖动操作之前将其自身放置在选择插入符号所在的位置。

如何获得或插入拖放操作的精确位置?我觉得这显然是拖放过程中的一个常见操作--我肯定忽略了某种重要而明显的细节?我甚至必须深入JavaScript吗,或者可能有一种方法,只需要使用draggable、droppable、contenteditable和一些fancydancy CSS3之类的属性就可以做到这一点?

我还在寻找--还在修修补补--一旦我发现我在哪些方面做得不及格,我就会马上回传:)

Farrukh贴出了一个很好的建议--使用:

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

以查看所选插入符号的实际位置。我将此插入到dragover事件中,此时我发现选择插入符号在ContentEditable中的可编辑内容之间明显地跳转。

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

这是一次英勇的努力。谢谢法鲁克。

这是怎么回事?我有一种感觉,我看到的小的选择插入符号跳来跳去,根本不是选择插入符号!我觉得是冒名顶替的!

原来是冒名顶替!在整个拖动操作期间,真正的选择插入符号保持在原地!你能看到那个小混蛋!

我在阅读MDN拖放文档时发现:

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

哎呀,这是不是意味着我应该根据clientX和Clienty自己搞清楚?使用鼠标坐标确定选择插入符号的位置?吓人!!

我明天会考虑这样做--除非我自己,或者这里读到这篇文章的其他人,能找到一个明智的解决办法:)


共有1个答案

元彦君
2023-03-14

我做了一大堆乱七八糟的事。这么多的小把戏。

这不是一个健壮或完整的解决方案;我可能永远也想不出一个。如果有人有更好的解决办法,我会洗耳恭听--我不想这样做,但这是目前为止我能找到的唯一方法。下面的jsFiddle和我将要吐出来的信息在我的特定WAMP设置和计算机上的特定版本的Firefox和Chrome中为我工作。当它在你的网站上不起作用时,不要来找我哭诉。这种拖拽式的废话显然是每个人都为自己。

Jsfiddle:Chase Moskal的Dragon掉落

所以,我是无聊我的女朋友的大脑出来,她认为我一直说“龙滴”,而实际上,我只是说“拖放”。它卡住了,所以这就是我为处理这些拖放情况而创建的JavaScript小伙伴。

结果--这有点像噩梦。HTML5的拖放API即使乍一看也是可怕的。然后,当你开始理解和接受它的工作方式时,你就几乎开始热身了…当你了解到Firefox和Chrome是如何以自己特有的方式来执行这个规范的,并且似乎完全忽略了你的所有需求时,你就会意识到这实际上是一场可怕的噩梦。您发现自己会问这样的问题:“等等,现在拖动的是什么元素?我如何获取这些信息?我如何取消拖动操作?我如何停止这个特定浏览器对这种情况的独特默认处理?”...你的问题的答案是:“你只能靠自己了,失败者!继续黑进东西,直到东西起作用为止!”。

下面是我如何在多个ContentEditable的内部、周围和之间精确地拖放任意HTML元素的。(注意:我并不是对每一个细节都进行深入的讨论,您必须查看jsFiddle--我只是漫谈一下我从经验中记得的似乎相关的细节,因为我的时间有限)

  • 首先,我将CSS应用于draggables(fancybox)--我们需要user-select:none;User-drag:element;,然后具体地User-drag:none;在fancy box中的图像上(以及任何其他元素,为什么不呢?)。不幸的是,这对Firefox来说还不够,它需要在图像上显式设置属性draggable=“false”以防止其被拖动。
  • 接下来,我将属性draggable=“true”dropzone=“copy”应用到ContentEditables上。

我将dragstart的处理程序绑定到draggables(fancyboxs)。我们将dataTransfer设置为复制一个空白的HTML字符串''--因为我们需要欺骗它,使它以为我们要拖动HTML,但我们要取消任何默认行为。有时默认行为会以某种方式滑入,并导致重复(就像我们自己做插入一样),所以现在最坏的故障是拖动失败时插入了一个''(空格)。我们不能依赖默认行为,因为它经常失败,所以我发现这是最通用的解决方案。

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');
    }
});

我将dragleavedrop的处理程序绑定到dropzones。dragleave处理程序仅适用于Firefox,就像在Firefox中一样,当您试图将其拖放到contenteditable之外时,拖放会起作用(Chrome默认情况下拒绝),因此它会根据Firefox专用的relatedtarget执行快速检查。哈夫。

Chrome和Firefox有不同的获取范围对象的方法,所以在drop事件中,每个浏览器都需要做出不同的努力。Chrome建立了一个基于鼠标坐标的范围(没错),但Firefox在事件数据中提供了它。document.execcommand('insert html',false,blah)是我们处理丢弃的方式。哦,我忘了提一下--我们不能在Chrome上使用datatransfer.getData()来获取我们的dragstart集HTML--这似乎是规范中的某种奇怪的bug。Firefox在它的胡扯上调用了规范,并给了我们数据--但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();
});

好吧,我受够了。关于这件事我想写的都写了。我今天就到此为止,如果我想到什么重要的事情,我可能会更新这个。

谢谢大家。//蔡斯。

 类似资料:
  • 问题内容: 我想做的就是能够在屏幕上拖放精灵。我尝试了以下代码: 该代码确实起作用,但是,当我快速拖动球时,我猜它检测到“球”不再包含点“位置”并且球停止了,这意味着我又将球捡起了。我希望球能够快速响应我的触摸,从而使球不会停止移动。我该怎么做? 问题答案: 我有一个实现,其中我将UIImageView子类化,并将其称为“ DraggableImage”

  • 问题内容: 我需要在contenteditablediv中实现数字的突出显示(将来我会添加更复杂的规则)。问题是当我用JavaScript替换插入新内容时,DOM更改和contenteditablediv失去了焦点。我需要的是将div放在当前位置上,以使注意力集中在div上,这样用户就可以键入而不会出现任何问题,而我的功能只需突出显示数字即可。谷歌搜索我认为Rangy库是最好的解决方案。我有以下代

  • 我有一个jqueryui可拖动的图像和一个包含text/html且contentEditable=true的jqueryui可拖放的div。 我希望能够将图像拖动到contentEditable文本上,当我将其拖放时,我希望能够将图像拖放到该文本/字符位置。 我已经找到了很多方法来做到这一点,如果我点击或选择可编辑的文本,然后使用选定的文本范围拖动图像,但当我只是拖动图像并放下它时,没有选择文本。

  • contenteditable <element contenteditable="true|false"> <p contenteditable="true">这是一个可编辑的段落。</p> contenteditable 光标定位到最后 function keepLastIndex(elem){ var range, length; //IE兼容性处理,光

  • 准备好,下面的内容会比较难以理解。 目前为止,我们已经使用map、nmap、vmap以及imap创建了实用的按键映射。 他们很方便,但是有个缺点。运行下面的命令: :::vim :nmap - dd :nmap \ - 试试按下\(在normal模式)。有什么现象? 当你按下\时,Vim会解释其为-。但是我们又映射了-!Vim会继续解析-为dd, 即它会删除整行。 你使用那些命令创建的映射可能会

  • 问题内容: 我在春季有以下几点 但是,Spring会自动为/ hello /和/hello.*添加映射。如何完全匹配网址? 只有/ hello应该可以工作,其他任何都应该404 问题答案: 关闭后缀匹配()将解决您的问题,但是,如果在您的配置中使用它(实际上不是手动连接所有必需的基础结构bean),实际上并不是那么容易。在这种情况下,定义其他类型的bean 不会有任何效果。 您有两种选择: 删除将