javascript 在 contenteditable 中精确拖放

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14678451/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 22:18:10  来源:igfitidea点击:

Precise Drag and Drop within a contenteditable

javascripthtmlcssdrag-and-dropdraggable

提问by ChaseMoskal

The Setup

设置

So, I have a contenteditable div -- I'm making a WYSIWYG editor: bold, italics, formatting, whatever, and most lately: inserting fancy images (in a fancy box, with a caption).

所以,我有一个内容可编辑的 div——我正在制作一个所见即所得的编辑器:粗体、斜体、格式等等,最近:插入精美的图像(在一个精美的框中,带有标题)。

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

The user adds these fancy images with a dialog I present them with: they fill out the details, upload the image, and then much like the other editor functions, I use document.execCommand('insertHTML',false,fancy_image_html);to plop it in at the user's selection.

用户使用我向他们展示的对话框添加这些精美的图像:他们填写详细信息,上传图像,然后与其他编辑器功能非常相似,我使用document.execCommand('insertHTML',false,fancy_image_html);它在用户选择时插入。

Desired Functionality

所需的功能

So, now that my user can plop in a fancy image -- they need to be able to move it around. The user needs to be able to click and drag the image (fancy box and all) to place it anywhere that they please within the contenteditable. They need to be able to move it between paragraphs, or even within paragraphs -- between two words if they want.

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

What gives me hope

什么给了我希望

Keep in mind -- in a contenteditable, plain old <img>tags are already blessed by the user-agent with this lovely drag-and-drop capability. By default, you can drag and drop <img>tags around wherever you please; the default drag-and-drop operation behaves as one would dream.

请记住——在一个内容可编辑的、普通的旧<img>标签中,用户代理已经拥有这种可爱的拖放功能。默认情况下,您可以随意拖放<img>标签;默认的拖放操作就像人们梦寐以求的那样。

So, considering how this default behavior already works so smashingly on our <img>buddies -- and I only want to extend this behaviour a little bit to include a tad more HTML -- this seems like something that should be easily possible.

因此,考虑到这种默认行为已经对我们的<img>伙伴如此有效——我只想稍微扩展这种行为以包含更多的 HTML——这似乎应该很容易实现。

My Efforts Thus Far

我迄今为止的努力

First, I set up my fancy <a>tag with the draggable attribute, and disabled contenteditable (not sure if that's necessary, but it seems like it may as well be off):

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

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

Then, because the user could still drag the image out of the fancy <a>box, I had to do some CSS. I'm working in Chrome, so I'm only showing you the -webkit- prefixes, though I used the others too.

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

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

Now the user can drag the whole fancy box, and the little partially-faded click-drag representation image reflects this -- I can see that I'm picking up the entire box now :)

现在用户可以拖动整个漂亮的框,部分褪色的点击拖动表示图像反映了这一点——我可以看到我现在正在拿起整个框:)

I've tried several combinations of different CSS properties, the above combo seems to make sense to me, and seems to work best.

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

I was hoping that this CSS alone would be enough for the browser to use the entire element as the draggable item, automagically granting the user the functionality I've been dreaming of... It does however, appear to be more complicated than that.

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

HTML5's JavaScript Drag and Drop API

HTML5 的 JavaScript 拖放 API

This Drag and Drop stuff seemsmore complicated than it needs to be.

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

So, I started getting deep into DnD api docs, and now I'm stuck. So, here's what I've rigged up (yes, jQuery):

所以,我开始深入研究 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');
    });

So here's where I'm stuck:This almost completely works. Almost completely.I have everything working, up until the very end. In the drop event, I now have access to the fancy box's HTML content that I'm trying to have inserted at the drop location. All I need to do now, is insert it at the correct location!

所以这就是我被困的地方:这几乎完全有效。几乎完全。我一切都在工作,直到最后。在放置事件中,我现在可以访问我尝试在放置位置插入的精美框的 HTML 内容。我现在需要做的就是将它插入正确的位置!

The problem is I can't find the correct drop location, or any way to insert to it.I've been hoping to find some kind of 'dropLocation'object to dump my fancy box into, something like dropEvent.dropLocation.content=myFancyBoxHTML;, or perhaps, at least, some kind of drop location values with which to find my own way to put the content there? Am I given anything?

问题是我找不到正确的放置位置,或者找不到任何插入它的方法。我一直希望找到某种“dropLocation”对象来将我喜欢的盒子转储到类似的东西中dropEvent.dropLocation.content=myFancyBoxHTML;,或者至少是某种放置位置值,以便找到我自己的方式将内容放在那里? 我得到了什么吗?

Am I doing it completely wrong? Am I completely missing something?

我这样做是完全错误的吗?我完全错过了什么吗?

I tried to use document.execCommand('insertHTML',false,content);like I expected I should be able to, but it unfortunately fails me here, as the selection caret is not located at the precise drop location as I'd hope.

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

I discovered that if I comment out all of the event.preventDefault();'s, the selection caret becomes visible, and as one would hope, when the user prepares to drop, hovering their drag over the contenteditable, the little selection caret can be seen running along between characters following the user's cursor and drop operation -- indicating to the user that the selection caret represents the precise drop location. I need the location of this selection caret.

我发现如果我注释掉所有的event.preventDefault();'s,选择插入符号变得可见,正如人们所希望的那样,当用户准备放下时,将拖动鼠标悬停在 contenteditable 上,可以看到小选择插入符号在字符之间运行跟随用户的光标和放置操作——向用户指示选择插入符号代表精确的放置位置。我需要这个选择插入符号的位置。

With some experiments, I tried execCommand-insertHTML'ing during the drop event, and the dragend event -- neither insert the HTML where the dropping-selection-caret was, instead it uses whatever location was selected prior to the drag operation.

通过一些实验,我在 drop 事件和 dragend 事件期间尝试了 execCommand-insertHTML'ing —— 既不在 drop-selection-caret 所在的位置插入 HTML,而是使用在拖动操作之前选择的任何位置。

Because the selection caret is visible during dragover, I hatched a plan.

因为在拖动过程中可以看到选择插入符,所以我制定了一个计划。

For awhile, I was trying, in the dragover event, to insert a temporary marker, like <span class="selection-marker">|</span>, just after $('.selection-marker').remove();, in an attempt for the browser to constantly (during dragover) be deleting all selection markers and then adding one at the insertion point -- essentially leaving one marker wherever that insertion point is, at any moment. The plan of course, was to then replace this temporary marker with the dragged content which I have.

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

None of this worked, of course: I couldn't get the selection-marker to insert at the apparently visible selection caret as planned -- again, the execCommand-insertedHTML placed itself wherever the selection caret was, prior to the drag operation.

当然,这些都不起作用:我无法按计划将选择标记插入到明显可见的选择插入符中——同样,在拖动操作之前,execCommand-insertedHTML 将自己放置在选择插入符所在的任何位置。

Huff. So what have I missed? How is it done?

呼。那么我错过了什么?它是如何完成的?

How do I obtain, or insert into, the precise location of a drag-and-drop operation?I feel like this is, obviously, a common operation among drag-and-drops -- surely I must have overlooked an important and blatant detail of some kind?Did I even have to get deep into JavaScript, or maybe there's a way to do this just with attributes like draggable, droppable, contenteditable, and some fancydancy CSS3?

如何获取或插入拖放操作的精确位置?我觉得这显然是拖放操作中的常见操作——我肯定忽略了某种重要而明显的细节吗?我什至必须深入了解 JavaScript,或者也许有一种方法可以使用诸如draggable、droppable、contenteditable 和一些花哨的 CSS3 之类的属性来做到这一点?

I'm still on the hunt -- still tinkering around -- I'll post back as soon as I find out what I've been failing at :)

我仍在寻找 - 仍在修补 - 一旦我发现我一直在失败,我会立即回复:)



The Hunt Continues (edits after original post)

狩猎继续(在原始帖子后编辑)



Farrukh posted a good suggestion -- use:

Farrukh 提出了一个很好的建议——使用:

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

To see where the selection caret actually is. I plopped this into the dragoverevent, which is when I figure the selection caret is visibily hopping around between my editable content in the contenteditable.

查看选择插入符的实际位置。我把它放到了dragover事件中,当我发现选择插入符号在 contenteditable 中我的可编辑内容之间明显地跳来跳去时。

Alas, the Range object that is returned, reports offset indices that belong to the selection caret prior to the drag-and-drop operation.

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

It was a valiant effort. Thanks Farrukh.

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

So what's going on here?I am getting the sensation that the little selection caret I see hopping around, isn't the selection caret at all! I think it's an imposter!

那么这里发生了什么?我有一种感觉,我看到的小选择插入符号根本不是选择插入符号!我觉得是骗子!

Upon Further Inspection!

经进一步检查!

Turns out, it is an imposter! The realselection caret remains in place during the entire drag operation! You can see the little bugger!

原来,这是个冒名顶替者!在实际选择尖整个拖动操作过程中保持到位!你可以看到小虫子!

I was reading MDN Drag and Drop Docs, and found this:

我正在阅读MDN Drag and Drop Docs,发现这个:

Naturally, you may need to move the insertion marker around a dragover event as well. You can use the event's clientX and clientY properties as with other mouse events to determine the location of the mouse pointer.

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

Yikes, does this mean I'm supposed to figure it out for myself, based on clientXand clientY?? Using mouse coordinates to determine the location of the selection caret myself? Scary!!

哎呀,这是否意味着我应该根据clientXclientY自己弄清楚?使用鼠标坐标自己确定选择插入符的位置?害怕!!

I'll look into doing so tomorrow -- unless myself, or somebody else here reading this, can find a sane solution :)

我明天会考虑这样做——除非我自己或这里的其他人能找到一个理智的解决方案:)

采纳答案by ChaseMoskal

Dragon Drop

龙掉落

I've done a ridiculous amount of fiddling. So, so much jsFiddling.

我做了很多荒谬的摆弄。所以,jsFddling 太多了。

This is not a robust, or complete solution; I may never quite come up with one. If anyone has any better solutions, I'm all ears -- I didn't want to have to do it this way, but it's the only way I've been able to uncover so far. The following jsFiddle, and the information I am about to vomit up, worked for me in this particular instance with my particular versions of Firefox and Chrome on my particular WAMP setup and computer.Don't come crying to me when it doesn't work on your website. This drag-and-drop crap is clearly every man for himself.

这不是一个健壮或完整的解决方案;我可能永远不会想出一个。如果有人有更好的解决方案,我会全力以赴 - 我不想必须这样做,但这是迄今为止我能够发现的唯一方法。下面的 jsFiddle 以及我即将吐出的信息在这个特定实例中对我有用,在我的特定 WAMP 设置和计算机上使用我的特定版本的 Firefox 和 Chrome。当它在您的网站上不起作用时,请不要向我哭泣。这种拖放式的废话显然是每个人都为自己。

jsFiddle: Chase Moskal's Dragon Drop

jsFiddle:Chase Moskal's Dragon Drop

So, I was boring my girlfriend's brains out, and she thought I kept saying "dragon drop" when really, I was just saying "drag-and-drop". It stuck, so that's what I call my little JavaScript buddy I've created for handling these drag-and-drop situations.

所以,我让我女朋友的脑子很无聊,她以为我一直在说“降龙”,其实我只是在说“拖放”。它卡住了,所以这就是我所说的我为处理这些拖放情况而创建的 JavaScript 小伙伴。

Turns out -- it's a bit of a nightmare. The HTML5 Drag-and-Drop API even at first glance, is horrible.Then, you almost warm up to it, as you start to understand and accept the way it's supposedto work.. Then you realize what a terrifying nightmare it actually is, as you learn how Firefox and Chrome go about this specification in their own special way, and seem to completely ignore all of your needs. You find yourself asking questions like: "Wait, what element is even being dragged right now? How to do I get that information? How do I cancel this drag operation? How can I stop this particular browser's unique default handling of this situation?"... The answers to your questions: "You're on your own, LOSER! Keep hacking things in, until something works!".

事实证明——这有点像一场噩梦。即使乍一看,HTML5 拖放 API 也很糟糕。然后,当你开始理解并接受它应该工作的方式时,你几乎热身了......然后你意识到它实际上是一个多么可怕的噩梦,当你了解 Firefox 和 Chrome 如何以他们自己的特殊方式执行这个规范时方式,似乎完全忽略了您的所有需求。您会发现自己在问这样的问题:“等等,现在正在拖动什么元素?如何获取该信息?如何取消此拖动操作?如何停止此特定浏览器对这种情况的独特默认处理?” ......你的问题的答案:“你是靠自己的,失败者!继续入侵,直到事情成功!”。

So, here's how I accomplished Precise Drag and Drop of Arbitrary HTML Elements within, around, and between multiple contenteditable's.(note: I'm not going fully in-depth with every detail, you'll have to look at the jsFiddle for that -- I'm just rambling off seemingly relevant details that I remember from the experience, as I have limited time)

所以,这就是我如何在多个 contenteditable 内部、周围和之间完成任意 HTML 元素的精确拖放。(注意:我不会对每一个细节都进行深入研究,你必须查看 jsFiddle——我只是在我从经验中记得的看似相关的细节中闲聊,因为我的时间有限)

My Solution

我的解决方案

  • First, I applied CSS to the draggables (fancybox) -- we needed user-select:none; user-drag:element;on the fancy box, and then specifically user-drag:none;on the image within the fancy box (and any other elements, why not?). Unfortunately, this was not quite enough for Firefox, which required attribute draggable="false"to be explicitly set on the image to prevent it from being draggable.
  • Next, I applied attributes draggable="true"and dropzone="copy"onto the contenteditables.
  • 首先,我将 CSS 应用于可拖动对象(user-select:none; user-drag:element;花式框)——我们需要花式框,然后特别user-drag:none;是花式框内的图像(以及任何其他元素,为什么不呢?)。不幸的是,这对于 Firefox 来说还不够,它需要draggable="false"在图像上显式设置属性以防止它被拖动。
  • 接着,我应用属性draggable="true"dropzone="copy"到contenteditables。

To the draggables (fancyboxes), I bind a handler for dragstart.We set the dataTransfer to copy a blank string of HTML ' ' -- because we need to trick it into thinking we are going to drag HTML, but we are cancelling out any default behavior. Sometimes default behavior slips in somehow, and it results in a duplicate (as we do the insertion ourselves), so now the worst glitch is a ' ' (space) being inserted when a drag fails. We couldn't rely on the default behavior, as it would fail to often, so I found this to be the most versatile solution.

对于可拖动对象(fancyboxes),我为dragstart. 我们将 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');
    }
});

To the dropzones, I bind a handler for dragleaveand drop.The dragleave handler exists only for Firefox, as in Firefox, the drag-drop would work (Chrome denies you by default) when you tried to drag it outside the contenteditable, so it performs a quick check against the Firefox-only relatedTarget. Huff.

对于 dropzones,我为dragleaveand绑定了一个处理程序dropdragleave 处理程序仅适用于 Firefox,就像在 Firefox 中一样,当您尝试将其拖到 contenteditable 之外时,拖放会起作用(Chrome 默认拒绝您),因此它会针对 Firefox-only 执行快速检查relatedTarget呼。

Chrome and Firefox have different ways of acquiring the Range object,so effort had to be put in to do it differently for each browser in the drop event. Chrome builds a range based on mouse-coordinates(yup that's right), but Firefox provides it in the event data. document.execCommand('insertHTML',false,blah)turns out to be how we handle the drop. OH, I forgot to mention -- we can't use dataTransfer.getData()on Chrome to get our dragstart set HTML -- it appears to be some kind of weird bug in the specification. Firefox calls the spec out on it's bullcrap and gives us the data anyways -- but Chrome doesn't, so we bend over backwards and to set the content to a global, and go through hell to kill all the default behavior...

Chrome 和 Firefox 有不同的获取 Range 对象的方法,因此必须努力在 drop 事件中为每个浏览器执行不同的操作。Chrome 基于鼠标坐标构建一个范围(是的,没错),但 Firefox 在事件数据中提供了它。document.execCommand('insertHTML',false,blah)原来是我们如何处理下降。哦,我忘了提到——我们不能dataTransfer.getData()在 Chrome 上使用来获取我们的拖动开始集 HTML——它似乎是规范中的某种奇怪的错误。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();
});

Okay, I'm sick of this. I've wrote all I want to about this. I'm calling it a day, and might update this if I think of anything important.

好吧,我厌倦了这个。我已经写了所有我想写的关于这个。我已经结束了,如果我想到任何重要的事情,可能会更新它。

Thanks everybody. //Chase.

谢谢大家。//追赶。

回答by 25r43q

Since I wanted to see this in a native JS solution I worked a bit to remove all jQuery dependencies. Hopefully it can help someone.

因为我想在原生 JS 解决方案中看到这一点,所以我做了一些工作来删除所有 jQuery 依赖项。希望它可以帮助某人。

First the markup

首先是标记

    <div class="native_receiver" style="border: 2px solid red;padding: 5px;" contenteditable="true" >
      WAITING  FOR STUFF
    </div>
    <div class="drawer" style="border: 2px solid #AAE46A; padding: 10px">
      <span class="native_drag" data-type="dateselector" contenteditable="false" draggable="true" style="border: 2px solid rgba(0,0,0,0.2);padding:4px; margin:2px">
        Block 1
      </span>
      <span class="native_drag" data-type="dateselector" contenteditable="false" draggable="true" style="border: 2px solid rgba(0,0,0,0.2);padding:4px; margin:2px">
        Second Blk
      </span>
    </div>

Then some helpers

然后一些帮手

    function addClass( elem, className ){
        var classNames = elem.className.split( " " )
        if( classNames.indexOf( className ) === -1 ){
            classNames.push( className )
        }
        elem.className = classNames.join( " " )
    }
    function selectElem( selector ){
        return document.querySelector( selector )
    }
    function selectAllElems( selector ){
        return document.querySelectorAll( selector )
    }
    function removeElem( elem ){
         return elem ? elem.parentNode.removeChild( elem ) : false
    }

Then the actual methods

那么实际的方法

    function nativeBindDraggable( elems = false ){
        elems = elems || selectAllElems( '.native_drag' );
        if( !elems ){
            // No element exists, abort
            return false;
        }else if( elems.outerHTML ){
            // if only a single element, put in array
            elems = [ elems ];
        }
        // else it is html-collection already (as good as array)

        for( let i = 0 ; i < elems.length ; i++ ){
            // For every elem in list, attach or re-attach event handling
            elems[i].dataset.transferreference = `transit_${ new Date().getTime() }`;
            elems[i].ondragstart = function(e){
                if (!e.target.id){
                    e.target.id = (new Date()).getTime();
                }

                window.inTransferMarkup = e.target.outerHTML;
                window.transferreference = elems[i].dataset.transferreference;
                addClass( e.target, 'dragged');
            };
        };
    }

    function nativeBindWriteRegion( elems = false ){
        elems = elems || selectAllElems( '.native_receiver' );
        if( !elems ){
            // No element exists, abort
            return false;
        }else if( elems.outerHTML ){
            // if only a single element, put in array
            elems = [ elems ];
        }
        // else it is html-collection

        for( let i = 0 ; i < elems.length ; i++ ){
            elems[i].ondragover = function(e){
                e.preventDefault();
                return false;
            };
            elems[i].ondrop = function(e){
                receiveBlock(e);
            };
        }
    }

    function receiveBlock(e){
        e.preventDefault();
        let content = window.inTransferMarkup;

        window.inTransferMarkup = "";

        let 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);
        }
        let sel = window.getSelection();
        sel.removeAllRanges(); 
        sel.addRange( range );
        e.target.focus();

        document.execCommand('insertHTML',false, content);
        sel.removeAllRanges();

        // reset draggable on all blocks, esp the recently created
        nativeBindDraggable(
          document.querySelector(
            `[data-transferreference='${window.transferreference}']`
          )
        );
        removeElem( selectElem( '.dragged' ) );
        return false;
    }

And lastly instantiate

最后实例化

nativeBindDraggable();
nativeBindWriteRegion();

Below is the functioning snippet

下面是功能片段

function addClass( elem, className ){
            var classNames = elem.className.split( " " )
            if( classNames.indexOf( className ) === -1 ){
                classNames.push( className )
            }
            elem.className = classNames.join( " " )
        }
        function selectElem( selector ){
            return document.querySelector( selector )
        }
        function selectAllElems( selector ){
            return document.querySelectorAll( selector )
        }
        function removeElem( elem ){
             return elem ? elem.parentNode.removeChild( elem ) : false
        }
        
      
     function nativeBindDraggable( elems = false ){
      elems = elems || selectAllElems( '.native_drag' );
      if( !elems ){
       // No element exists, abort
       return false;
      }else if( elems.outerHTML ){
       // if only a single element, put in array
       elems = [ elems ];
      }
      // else it is html-collection already (as good as array)
            
      for( let i = 0 ; i < elems.length ; i++ ){
       // For every elem in list, attach or re-attach event handling
       elems[i].dataset.transferreference = `transit_${ new Date().getTime() }`;
       elems[i].ondragstart = function(e){
        if (!e.target.id){
         e.target.id = (new Date()).getTime();
        }

        window.inTransferMarkup = e.target.outerHTML;
        window.transferreference = elems[i].dataset.transferreference;
        addClass( e.target, 'dragged');
       };
      };
     }
        
     function nativeBindWriteRegion( elems = false ){
      elems = elems || selectAllElems( '.native_receiver' );
      if( !elems ){
       // No element exists, abort
       return false;
      }else if( elems.outerHTML ){
       // if only a single element, put in array
       elems = [ elems ];
      }
      // else it is html-collection
      
      for( let i = 0 ; i < elems.length ; i++ ){
       elems[i].ondragover = function(e){
        e.preventDefault();
        return false;
       };
       elems[i].ondrop = function(e){
        receiveBlock(e);
       };
      }
     }
        
        function receiveBlock(e){
      e.preventDefault();
      let content = window.inTransferMarkup;
      
      window.inTransferMarkup = "";
      
      let 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);
      }
      let sel = window.getSelection();
      sel.removeAllRanges(); 
      sel.addRange( range );
      e.target.focus();
      
      document.execCommand('insertHTML',false, content);
      sel.removeAllRanges();
      
            // reset draggable on all blocks, esp the recently created
      nativeBindDraggable(
              document.querySelector(
                `[data-transferreference='${window.transferreference}']`
              )
            );
      removeElem( selectElem( '.dragged' ) );
      return false;
     }


    nativeBindDraggable();
    nativeBindWriteRegion();
        <div class="native_receiver" style="border: 2px solid red;padding: 5px;" contenteditable="true" >
          WAITING  FOR STUFF
        </div>
        <div class="drawer" style="border: 2px solid #AAE46A; padding: 10px">
          <span class="native_drag" data-type="dateselector" contenteditable="false" draggable="true" style="border: 2px solid rgba(0,0,0,0.2);padding:4px; margin:2px">
            Block 1
          </span>
          <span class="native_drag" data-type="dateselector" contenteditable="false" draggable="true" style="border: 2px solid rgba(0,0,0,0.2);padding:4px; margin:2px">
            Second Blk
          </span>
        </div>

回答by holistic

  1. event dragstart; dataTransfer.setData("text/html", "<div class='whatever'></div>");
  2. event drop: 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);
  1. 事件拖动开始; 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);