一个CONTENTEDITABLE内精确的拖放(Precise Drag and Drop with

2019-07-19 04:38发布

安装程序

所以,我有一个DIV CONTENTEDITABLE - 我正在做一个所见即所得的编辑器:黑体,斜体格式,不管,最最近:将看中的图像(在一家豪华的盒子,有一个标题)。

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

用户将看到一个对话框我介绍他们与这些花哨的图像:他们填写的细节,上传图片,然后就像其他的编辑功能,我使用document.execCommand('insertHTML',false,fancy_image_html); 在用户的选择扑通进去。

所需的功能

所以,现在,我的用户可以在一个奇特的图像扑通 - 他们需要的是能够移动它。 用户需要能够单击并拖动图像(花式框和所有)的任何地方,他们请了CONTENTEDITABLE内放置。 他们需要能够把它段落之间,甚至是在段落移动 - 两个词之间,如果他们想要的。

是什么给了我希望,

请记住-在CONTENTEDITABLE,老式<img>标签已经由用户代理与这个可爱的拖拽和拖放功能的祝福。 默认情况下,你可以拖放<img>身边,无论你请标签; 默认的拖动和拖放操作行为就像一个会做梦。

因此,考虑如何默认行为已经工作这么smashingly我们<img>的好友-我只是想延长这种行为有点包括一点点更多的HTML -这似乎喜欢的事,应该是容易实现。

我迄今所作的努力

首先,我建立了我的幻想<a>标签与拖动属性,和残疾人CONTENTEDITABLE(不知道这是必要的,但现在看来似乎也可以被关闭):

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

然后,因为用户仍可以拖动图像了花哨的<a>中,我不得不做一些CSS。 我在Chrome中工作,所以我只显示你的-webkit-前缀,虽然我用了别人了。

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

现在,用户可以拖动整个看中箱,和小局部褪色单击并拖动表示图像反映了这一点 - 我可以看到,我现在拿起整个盒子:)

我已经尝试过不同的CSS属性的几种组合,上述组合似乎是有道理的我,似乎最好的工作。

我希望,仅这个CSS就足够了浏览器使用整个元素作为拖动项目,自动地授予用户我一直梦想的功能... 但它确实,似乎比这更复杂。

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内容,我试图在放置位置已插入。 所有我现在需要做的,是在正确的位置插入它!

问题是我无法找到正确的放置位置,或以任何方式插入到它。 我一直希望能找到某种“dropLocation”对象的转储看中了我的箱子进去,像dropEvent.dropLocation.content=myFancyBoxHTML; ,或者,至少,某种拖放位置值与其中找到自己的方式把内容呢? 难道我给什么?

我做这完全错了吗? 难道我完全失去了一些东西?

我试图用document.execCommand('insertHTML',false,content); 像我预料的,我应该是可以的,但它不幸失败,我在这里,作为选择尖不是在精确的放置位置位于我倒是希望。

我发现,如果我注释掉所有的event.preventDefault(); 的,选择尖变得可见,并作为人们希望,当用户准备下降,徘徊他们拖过CONTENTEDITABLE,小选择尖可以看到沿着以下用户的光标字符之间拖放操作运行 - 指示到该选择尖号表示的精确放置位置的用户。 我需要这个选择符的位置。

对于一些实验,我跌落事件过程中试图将execCommand-insertHTML'ing和dragend事件 - 无论地方插入该落的选择,插入符号是,而是使用的任何位置之前的拖动操作选择了HTML。

因为选择尖的dragover期间是可见的,我想出了一个计划。

有一段时间,我想,在dragover事件,插入一个临时标记,像<span class="selection-marker">|</span>只是后$('.selection-marker').remove(); ,在浏览器尝试不断(的dragover期间)是删除所有选择标记,然后添加一个在插入点 - 留下基本一个标记的地方是插入点,在任何时刻。 当然,该计划是为了然后用拖动的内容,我有替换此临时标记。

这些都没有工作,当然我不能让这个选择标记,在明显可见选择尖插入按计划 - 同样,将execCommand-insertedHTML放置本身无论选择尖是,拖拽操作之前。

一怒之下。 所以,你有什么我错过了什么? 它是如何做?

如何获得,或插入,一拖和拖放操作的精确位置? 我觉得这很明显是一种常见的操作拖和下降之间-我肯定一定是忽略了某种重要的和公然的细节? 难道我甚至有深进入的JavaScript,或者也许有办法做到这一点只是像拖动,可弃,CONTENTEDITABLE属性,有的fancydancy CSS3?

我仍然在寻找 - 仍然摆弄周围 - 我只要我找出我一直在失败后回来:)


继续狩猎(原帖后编辑)


法鲁克贴好建议 - 使用:

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

要查看选择尖实际上是。 我噗的一声落到dragover事件,这是当我想选择尖是我在CONTENTEDITABLE可编辑的内容之间visibily跳跃解决这个问题。

唉,返回的Range对象,报告抵销属于前拖和拖放操作选择尖指标。

这是一个勇敢的努力。 由于法鲁克。

所以,这是怎么回事吗? 我越来越感觉这个小选择尖我看到到处乱窜乱跳,不选择尖了! 我认为这是一个骗子!

在进一步的检查!

事实证明,这是一个骗子! 真正的选择尖整个拖动操作过程中保持到位! 你可以看到小开溜!

我读MDN拖放文件 ,并发现这一点:

当然,你可能需要移动插入标记围绕一个dragover事件也是如此。 您可以使用事件的clientX和clientY属性与其他鼠标事件来判断鼠标指针的位置。

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

我会考虑这样做的明天 - 除非我自己,还是别人在这里读这篇文章,可以找到一个理智的解决方案:)

Answer 1:

龙掉落

我已经做了小提琴演奏的荒谬量。 那么,这么多jsFiddling。

这不是一个强大的,或完整的解决方案; 我可能从来没有完全拿出一个。 如果任何人有任何更好的解决方案,我所有的耳朵-我不希望有做这种方式,但它是我已经能够迄今为止发现的唯一途径。 下面的jsfiddle,我要吐出来的信息,在这种特定情况下工作对我来说与Firefox和Chrome对我特别的WAMP的设置和电脑我的特定版本。 别来哭,我当它不上你的网站工作。 这种阻力和下降废话显然是每个人的向往。

的jsfiddle:大通Moskal的龙掉落

所以,我很无聊我女朋友的大脑了,她以为我不停地说“龙掉落”的时候真的,我只是说“拖 - 放”。 它卡住了,所以这就是我把我的一些JavaScript哥们我处理这些拖和下降的情况下创建的。

原来-这是一个有点一场噩梦。 即使乍一看HTML5的拖放和拖放API,是可怕的。 然后,你几乎热身吧,当你开始了解并接受它应该工作..顺便那你知道什么是可怕的噩梦它实际上是,当你学习Firefox和Chrome浏览器如何去这个规范在自己特殊的路,似乎完全忽略所有的需求。 你发现自己在问这样的问题:“????等待,甚至被拖什么元素,现在该怎么办,我得到的信息如何取消此拖动操作我怎样才能阻止这种情况的这种特殊的浏览器独有的默认处理” ......这些问题的答案您的问题:“你对你自己的,LOSER请黑客攻击的东西,直到有工作!”。

所以,在这里就是我实现精确拖放内的任意HTML元素的下降,周围和之间的多个CONTENTEDITABLE的。 (注:我没有完全深入每一个细节去,你必须看的jsfiddle为-我只是东拉西扯过,我从经验中记得貌似相关的细节,因为我的时间有限)

我的解决办法

  • 首先,我应用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。

到拖拽元素(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');
    }
});

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

Chrome和Firefox都取得Range对象的方式不同,所以工作必须投入到在drop事件每个浏览器采取不同的方式。 Chrome浏览器建立了基于鼠标坐标 (烨这是正确的)一个范围,但Firefox的事件数据提供了它。 document.execCommand('insertHTML',false,blah)原来是我们如何处理的下降。 哦,我忘了提-我们不能用dataTransfer.getData()在Chrome得到我们的dragstart组HTML -这似乎是某种形式的规范怪异的bug。 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();
});

好吧,我讨厌这一点。 我已经写了所有我想这个问题。 一天我打电话吧,如果我认为重要的东西可能会更新这个。

谢谢大家。 //追。



Answer 2:

因为我想看看这个在本地JS解决方案,我的工作有点删除所有jQuery的依赖性。 希望它可以帮助别人。

首先标记

    <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>

然后一些助手

    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();

下面是功能片段

 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> 



Answer 3:

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


文章来源: Precise Drag and Drop within a contenteditable