简体   繁体   English

在 contenteditable 中精确拖放

[英]Precise Drag and Drop within a contenteditable

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).所以,我有一个 contenteditable 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);用户通过我向他们展示的对话框添加这些精美的图像:他们填写详细信息,上传图像,然后与其他编辑器功能非常相似,我使用document.execCommand('insertHTML',false,fancy_image_html); to plop it in at the user's selection.将其放入用户的选择中。

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.用户需要能够单击并拖动图像(花式框和所有图像)以将其放置在 contenteditable 中他们喜欢的任何位置。 They need to be able to move it between paragraphs, or even within paragraphs -- between two words if they want.他们需要能够在段落之间移动它,甚至在段落内——如果他们愿意的话,可以在两个词之间移动。

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.请记住——在一个内容可编辑的、普通的<img>标签中,用户代理已经拥有这种可爱的拖放功能。 By default, you can drag and drop <img> tags around wherever you please;默认情况下,您可以随意拖放<img>标签; the default drag-and-drop operation behaves as one would dream.默认的拖放操作就像人们梦寐以求的那样。

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):首先,我使用draggable 属性设置了我喜欢的<a>标签,并禁用了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.然后,因为用户仍然可以将图像拖出花哨的<a>框,我不得不做一些 CSS。 I'm working in Chrome, so I'm only showing you the -webkit- prefixes, though I used the others too.我在 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 seems more complicated than it needs to be.这种拖放的东西似乎比它需要的更复杂。

So, I started getting deep into DnD api docs, and now I'm stuck.所以,我开始深入研究 DnD api 文档,现在我被困住了。 So, here's what I've rigged up (yes, jQuery):所以,这就是我设置的(是的,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.在放置事件中,我现在可以访问我尝试在放置位置插入的精美框的 HTML 内容。 All I need to do now, is insert it at the correct location!我现在需要做的就是将它插入正确的位置!

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;我一直希望找到某种“dropLocation”对象来将我喜欢的盒子转储到其中,例如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?我得到了什么吗?

Am I doing it completely wrong?我这样做是完全错误的吗? Am I completely missing something?我完全错过了什么吗?

I tried to use document.execCommand('insertHTML',false,content);我尝试使用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.就像我预期的那样,我应该能够做到,但不幸的是,我在这里失败了,因为选择插入符并不像我希望的那样位于精确的放置位置。

I discovered that if I comment out all of the event.preventDefault();我发现如果我注释掉所有的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. 's,选择插入符号变得可见,正如人们所希望的那样,当用户准备放置时,将拖动鼠标悬停在 contenteditable 上,可以看到小选择插入符号在用户光标和放置操作之后的字符之间运行 - 表示向用户表明选择插入符号表示精确的放置位置。 I need the location of this selection caret.我需要这个选择插入符号的位置。

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();有一段时间,我试图在拖动事件中插入一个临时标记,例如<span class="selection-marker">|</span> ,就在$('.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.当然,计划是然后用我拥有的拖动内容替换这个临时标记。

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 dragover event, 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 real selection caret remains in place during the entire drag operation!在整个拖动操作期间,真正的选择插入符保持原位! You can see the little bugger!你可以看到小虫子!

I was readingMDN 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 clientX and clientY ??哎呀,这是否意味着我应该根据clientXclientY自己弄清楚 Using mouse coordinates to determine the location of the selection caret myself?使用鼠标坐标自己确定选择插入符的位置? Scary!!害怕!!

I'll look into doing so tomorrow -- unless myself, or somebody else here reading this, can find a sane solution :)我明天会考虑这样做 - 除非我自己或这里的其他人可以找到一个理智的解决方案:)

Dragon Drop 龙滴

I've done a ridiculous amount of fiddling. 我做了一个荒谬的摆弄。 So, so much jsFiddling. 所以,jsFiddling这么多。

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. 下面的jsFiddle,以及我即将呕吐的信息,在我特定的WAMP设置和计算机上使用我的特定版本的Firefox和Chrome在这个特定的实例中为我工作。 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: Chase Moskal's Dragon Drop jsFiddle:Chase Moskal的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. 即使乍一看,HTML5拖放API也很糟糕。 Then, you almost warm up to it, as you start to understand and accept the way it's supposed to 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. 然后,当你开始理解并接受它应该工作的方式时,你几乎要热身了。然后你意识到它实际上是一个多么可怕的噩梦,因为你了解Firefox和Chrome如何在他们自己的特殊情况下使用这个规范方式,似乎完全忽略了你的所有需求。 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!". 你会发现自己会问这样的问题:“等等,现在甚至被拖动了什么元素?如何获取该信息?如何取消此拖动操作?如何阻止此特定浏览器对此情况的唯一默认处理?” ...你的问题的答案:“你是独立的,失败!继续黑客入侵,直到有效!”。

So, here's how I accomplished Precise Drag and Drop of Arbitrary HTML Elements within, around, and between multiple contenteditable's. 所以,这就是我如何在多个contenteditable的内部,周围和之间完成精确拖放任意HTML元素的方法。 (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) (注意:我不会完全深入了解每一个细节,你必须要看看jsFiddle - 我只是漫无目的地从我的经历中记得看似相关的细节,因为我的时间有限)

My Solution 我的解决方案

  • First, I applied CSS to the draggables (fancybox) -- we needed user-select:none; user-drag:element; 首先,我将CSS应用于draggables(fancybox) - 我们需要user-select:none; user-drag:element; user-select:none; user-drag:element; on the fancy box, and then specifically user-drag:none; 在花哨的盒子上,然后专门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. 不幸的是,这对于Firefox来说已经不够了,它需要在图像上显式设置属性draggable="false"以防止它被拖动。
  • Next, I applied attributes draggable="true" and dropzone="copy" onto the contenteditables. 接下来,我将属性draggable="true"dropzone="copy"应用于contenteditables。

To the draggables (fancyboxes), I bind a handler for dragstart . 对于draggables(fancyboxes),我为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. 我们设置dataTransfer来复制一个空白的HTML''字符串 - 因为我们需要欺骗它以为我们要拖动HTML,但是我们要取消任何默认行为。 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. 我们不能依赖默认行为,因为它经常失败,所以我发现这是最通用的解决方案。

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 dragleave and drop . 对于dropzones,我绑定了dragleavedrop的处理程序。 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 . 该dragleave处理程序只存在于Firefox,因为在Firefox,拖放会工作(Chrome的默认拒绝你)当你试图将它拖动到外面CONTENTEDITABLE,所以它执行对Firefox的,只有快速检查relatedTarget Huff. 一怒之下。

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和Firefox有不同的方式来获取Range对象,因此必须为drop事件中的每个浏览器进行不同的操作。 Chrome builds a range based on mouse-coordinates (yup that's right) , but Firefox provides it in the event data. Chrome根据鼠标坐标建立了一个范围(是的这是正确的) ,但Firefox在事件数据中提供了它。 document.execCommand('insertHTML',false,blah) turns out to be how we handle the drop. document.execCommand('insertHTML',false,blah)原来是我们处理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. 哦,我忘了提 - 我们不能在Chrome上使用dataTransfer.getData()来获取我们的dragstart集HTML - 它似乎是规范中的某种奇怪的错误。 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... 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. //追。

Since I wanted to see this in a native JS solution I worked a bit to remove all jQuery dependencies. 因为我想在本机JS解决方案中看到这一点,所以我做了一些工作来删除所有jQuery依赖项。 Hopefully it can help someone. 希望它可以帮助某人。

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> 

  1. event dragstart; 事件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); 事件掉落: 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);

To summarise above answers, the keys are event.parentNode and event.rangeOffset (firefox) and caretRangeFromPoint(event.clientX, event.clientY) (chrome).总结以上答案,关键是event.parentNodeevent.rangeOffset (firefox) 和caretRangeFromPoint(event.clientX, event.clientY) (chrome)。 Here is a minimal example:这是一个最小的例子:

 span { border: 1px solid red; } span:before { content: "grab "; background-color: #0f0; }
 <p contenteditable="true" ondrop="sel = window.getSelection(); if (document.caretRangeFromPoint) range = document.caretRangeFromPoint(event.clientX, event.clientY) else { sel.collapse(event.rangeParent,event.rangeOffset) range = sel.getRangeAt(0) } range.insertNode(sp1)" ondragover=" return false" >This is a contenteditable paragraph. Grab the green field in the following span <span draggable="True" id="sp1" ondragstart=" event.dataTransfer.setData('text/plain', this.innerText)">span</span> and drag it inside this paragraph. </p>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM