在所有可能的地方都可以丢弃

时间:2013-08-22 19:04:04

标签: javascript drag-and-drop

我目前正在建立一个网站设计师,其中一个核心功能是能够拖动和放大重新排列元素。我已经在这个功能上工作了几天,并且已经松了好几次。关于此拖放系统最重要的注意是可拖动元素可以放在容器中的任何位置,它将卡入到位所以没有绝对定位的元素,否则元素将不会卡入到位

所以我首先开始构建可拖动元素的核心可拖动位,然后当你放下元素时我使用document.elementFromPoint()来获取光标位置所在的元素( note :我必须隐藏可拖动元素,否则它将返回)。

现在我有最接近光标的元素,主要问题是找出可拖动元素相对于该元素的位置,因为有4个选项append - prepend - insertBefore - insertAfter。我设法让append - prepend & insertBefore正常工作,但它不够可靠,因为我要使用目标的高度和偏移来确定append or prepend和我正在增加Y参数getFromPoint以查看我是否在短距离内点击其他元素以确定insertBefore。这是我到目前为止的代码。

box.addEventListener('mouseup', function (e) {
    if (!dragging) return;
    dragging = false;
    var thisEl = this; // the drag-able element

    // Hide 
    this.style.display = 'none'; // hide the element so we can get 
                                 // document.elementFromPoint

    var el = document.elementFromPoint(e.pageX, e.pageY), // target
        elH = el.offsetHeight, // height of target
        elT = el.offsetTop; // offset of target

    for (var i = 0; i < 15; i++) { // This is a weird part see the reference at the bottom
        var newEl = document.elementFromPoint(e.pageX, e.pageY + i);
        if (newEl !== el) {
            this.style.display = 'block';
            this.style.position = 'static';
            return newEl.parentNode.insertBefore(thisEl, newEl);
        }
    }

    if (e.pageY < elT + (elH / 2)) { // if the pageY is less than the target offset + half of the height, that's how I am calculating prepend
        el.appendChild(this);
        el.insertBefore(this, el.firstChild);
    } else {
        el.appendChild(this); // else append;
    }

    this.style.display = 'block';
    this.style.position = 'static'; // Snap back in with 'static'
});

这只是我的mouseup事件,是完成所有工作的事件。其他事件只是使元素可拖动,而不是真正重要。

这是一个小提琴

http://jsfiddle.net/mLX5A/2/

所以,如果那不是问题,那么这里是一个简短的版本。

我的可拖动元素需要能够在任何地方删除并捕捉到。这样做的最好方法是什么,因为我在小提琴中完成它的方式绝对不是好事。如何在mouseup上检测元素相对于目标的位置。

参考奇怪的部分。

这是它的工作原理(警告它不好) 当我用elementFromPoint获取目标元素时,我创建一个循环 这将循环15次并增加Y elementFromPoint的{​​{1}}值,因此基本上elementFromPoint向下移动15px并且如果它在该短空间内击中新元素我假设你不想在目标之前插入元素。

我非常乐意收到与此代码无关的答案,因为这也会使其他用户受益。

如果你认为这篇文章很长而且令人困惑,那就不要因为我已经这么做了好几天了,它抬起头来让我疯狂。

我想请注意,具有可拖动功能的容器是设计器的主要部分。所以我不能选择所有绝对定位的元素,并且我不会在每个可能的位置放置一个元素,这样我就可以检测出可拖动元素必须去的位置,因为无论是什么该容器将是一个没有不必要内容的质量结果。

我还要注意,我的应用不会支持和旧浏览器即 IE6,IE7,IE8,IE9

7 个答案:

答案 0 :(得分:8)

我会考虑使用jQuery UI&#39; Sortable widget portlets example 处理insertBeforeinsertAfter要求。我创建了一个简单的 fiddle ,它基于portlet示例构建,并且还满足prependappend要求。

这对您来说只是一个开始,我确信您可以根据需要进行操作。 connectWith非常重要,具体取决于您希望放置内容的位置。

<强> Fiddle

JS

$(".column").sortable({
    items: ".portlet",
    connectWith: ".column"
});
$(".portlet").sortable({
    items: ".portlet-content",
    connectWith: ".portlet"
});
$(".column").disableSelection();

HTML

<div class="column">
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">One. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">Two. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">Three. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
</div>
<div class="column">
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">Four. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
    <div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all">
        <div class="portlet-content">Five. Lorem ipsum dolor sit amet, consectetuer adipiscing elit</div>
    </div>
</div>

答案 1 :(得分:3)

interact.js是一个独立的,轻量级的拖放和调整javascript模块,适用于移动和桌面(包括IE8 +),支持与HTML和SVG元素交互。它只捕获并计算拖动用户输入,并将所有样式和视觉反馈留给您。

我用一个有效的演示更新了JS小提琴:http://jsfiddle.net/mLX5A/6/

var box = document.getElementById('box'),
    container = document.getElementById('container');

// make an Interactable of the box
interact(box)
// make a draggable of the Interactable
.draggable(true)
    .on('dragmove', function (event) {
        event.target.x |= 0;
        event.target.y |= 0;

        event.target.x += event.dx,
        event.target.y += event.dy;

        // translate the element by the change in pointer position
        event.target.style[transformProp] =
            'translate(' + event.target.x + 'px, ' + event.target.y + 'px)';
    });

// Then to make #container a dropzone dropzone:
interact('#container')      // or interact(document.getElementById('container'))
    .dropzone(true)
    .on('drop', function (event) {
        // target is the dropzone, relatedTarget was dropped into target

        event.relatedTarget.x = 0;
        event.relatedTarget.y = 0;
        event.relatedTarget.style[transformProp] = '';

        var siblings = container.querySelectorAll('p'),
            len = siblings.length;

        for (var i = 0; i < len; i++) {
            var rect = interact(siblings[i]).getRect();

            if (event.pageY < rect.top) {
                return siblings[i].parentNode
                    .insertBefore(event.relatedTarget, siblings[i]);
            }
        }

        return container.appendChild(event.relatedTarget);
    });

// CSS transform vendor prefixes
transformProp = 'transform' in document.body.style ?
    'transform' : 'webkitTransform' in document.body.style ?
    'webkitTransform' : 'mozTransform' in document.body.style ?
    'mozTransform' : 'oTransform' in document.body.style ?
    'oTransform' : 'msTransform';

答案 2 :(得分:3)

使用contenteditable =“true”,ChaseMoskal有一个很好的解决方案:

HTML

<!--====  DragonDrop Demo HTML
Here we have two contenteditable <div>'s -- they have a dashed bottom-border dividing them, and they contain various text content. The first <div> contains structured block content, and the second <div> contains Unstructured, unwrapped content.
====-->

<div contenteditable="true">
    <h1>Athenagora Lenoni Incommunicabile: Structured Content</h1>
    <h2>Cellam modico illius ergo accipiet si non ait est Apollonius.</h2>

    <a class="fancy">
        <img src="http://imageshack.us/scaled/landing/809/picture195z.jpg" />
        <caption>Hola!</caption>
    </a>

    <p>Volvitur ingreditur lavare propter increparet videns mihi cum. Tharsis ratio puella est Apollonius in deinde plectrum anni ipsa codicellos, jesus Circumdat flante vestibus lumine restat paralyticus audi anim igitur patriam Dianae. 'Iuraveras magnifice ex quae ad per te sed dominum sit Mariae Bone de his carpens introivit filiam. Plus damna nautis unum ad te. Puto suam ad quia iuvenis omnia. Etiam quantitas devenit regi adhibitis sedens loculum inveni.</p>
</div>

<div contenteditable="true">
    <strong>Unstructured Content:</strong> Toto determinata se est se ad te finis laeta gavisus, laetare quod una non ait mea ego dum est Apollonius. Intrarem puella est in deinde cupis ei Taliarchum in, tharsiam vis interrogat Verena est Apollonius eius ad suis. Antiochus ac esse more filiam sunt forma ait Cumque persequatur sic. Imas rebum accusam in fuerat est se sed esse ait Cumque ego. Secundis sacerdotem habemus ibi alteri ad quia, agere videre Proicite a civitas exulto haec. Supponite facultatibus actum dixit eos. Neminem habere litore in deinde duas formis. Quattuordecim anulum ad nomine Hesterna studiis ascende meae puer ut sua, eiusdem ordo quos annorum afferte Apollonius non ait in.
    <br /><br />
    Deducitur potest contremiscunt eum ego Pentapolim Cyrenaeorum tertia navigavit volente in fuerat eum istam vero cum obiectum invidunt cum. Christe in rei sensibilium iussit sed, scitote si quod ait Cumque persequatur sic. Amet consensit cellula filia in rei civibus laude clamaverunt donavit potest flens non solutionem innocentem si quod ait. Una Christi sanguine concomitatur quia quod non coepit, volvitur ingreditur est Apollonius eius non potentiae. Coepit cenam inhaeret Visceribusque esocem manibus modi regiam iriure dolore. Filiam in rei finibus veteres hoc ambulare manu in fuerat eum istam provoces.
</div>

<button name="toggleContentEditable">disable contenteditable</button>

CSS

/*======   DragonDrop Demo CSS
Basically, user-select, and user-drag (and all their prefixes) need to be set in a way that lets the browser know which parts of our draggable element are selectable and draggable and which aren't.
    So I guess this is that.
                   ======*/
@charset utf-8;

/* these rules only apply to fancy boxes when contenteditable is true: they are necessary for the drag-and-drop demo to work correctly */
[contenteditable="true"] .fancy {
    /**/-moz-user-select:none;-webkit-user-select:none;
    user-select:none; /* without this line, element can be dragged within itself! No-no! */
    /**/-moz-user-drag:element;-webkit-user-drag:element;
    user-drag:element; /* makes the whole fancy box draggable */
    cursor:move !important; } /* switches to the move cursor */
    [contenteditable="true"] .fancy * {
        /**/-moz-user-drag:none;-webkit-user-drag:none;
        user-drag:none; } /* hopefully disables the internal default dragging of the img */



/*======     Everything below this area
               is STRICLY for STYLE
                 and VISUAL APPEAL.
              It shouldn't concern you.    ======*/

html,body{ height:100%; }
[contenteditable] {
    background:#f8f8f8; box-sizing:border-box; padding:2%; min-height:auto;
    font-size:10px; font-family:sans-serif; color:#444;
    border-bottom:2px dashed #888; }
    .fancy {
        display:inline-block; margin:10px;
        color:#444; text-align:center; font-style:italic;
        font-size:10px; font-family:sans-serif;
        background:#fff; border:8px solid white;
        box-shadow:1px 2px 8px #444;
        cursor:pointer; }
        .fancy img {
            display:block; margin-bottom:2px;
            border:1px solid white;
            box-shadow:0 1px 6px #888; }
        .fancy .caption { max-width:100px; }
    h1 { font-weight:bold; font-size:1.4em; }
    h2 { font-weight:bold; font-style:italic; text-indent:2px; }
    p { text-indent:8px; }
    strong { font-weight:bold; }
button { display:block; margin:6px auto; }

的Javascript

/*

==== Dragon Drop: a demo of precise DnD
          in, around, and between 
         multiple contenteditable's.

=================================
== MIT Licensed for all to use ==
=================================
Copyright (C) 2013 Chase Moskal

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
============

*/

function DRAGON_DROP (o) {
    var DD=this;

    // "o" params:
    DD.$draggables=null;
    DD.$dropzones=null;
    DD.$noDrags=null; // optional

    DD.dropLoad=null;
    DD.engage=function(o){
        DD.$draggables = $(o.draggables);
        DD.$dropzones = $(o.dropzones);
        DD.$draggables.attr('draggable','true');
        DD.$noDrags = (o.noDrags) ? $(o.noDrags) : $();
        DD.$dropzones.attr('dropzone','copy');
        DD.bindDraggables();
        DD.bindDropzones();
    };
    DD.bindDraggables=function(){
        DD.$draggables = $(DD.$draggables.selector); // reselecting
        DD.$noDrags = $(DD.$noDrags.selector);
        DD.$noDrags.attr('draggable','false');
        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');
            }
        });
    };
    DD.bindDropzones=function(){
        DD.$dropzones = $(DD.$dropzones.selector); // reselecting
        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();
        });
    };
    DD.disengage=function(){
        DD.$draggables=$( DD.$draggables.selector ); // reselections
        DD.$dropzones=$( DD.$dropzones.selector );
        DD.$noDrags=$( DD.$noDrags.selector );
        DD.$draggables.removeAttr('draggable').removeAttr('dragged').off('dragstart');
        DD.$noDrags.removeAttr('draggable');
        DD.$dropzones.removeAttr('droppable').off('dragenter');
        DD.$dropzones.off('drop');
    };
    if (o) DD.engage(o);
}



$(function(){

    window.DragonDrop = new DRAGON_DROP({
        draggables:$('.fancy'),
        dropzones:$('[contenteditable]'),
        noDrags:$('.fancy img')
    });

    // This is just the enable/disable contenteditable button at the bottom of the page.
    $('button[name="toggleContentEditable"]').click(function(){
        var button=this;
        $('[contenteditable]').each(function(){
            if ($(this).attr('contenteditable')==='true') {
                $(this).attr('contenteditable','false');
                $(button).html('enable contenteditable');
            } else {
                $(this).attr('contenteditable','true');
                $(button).html('disable contenteditable');
            }
        });
    });

});

小提琴:

http://jsfiddle.net/ChaseMoskal/T2zHQ/

答案 3 :(得分:2)

我试图做我所知道的事情。

<div class="container">
    <div id="box"></div>
     <div class="draggable"> alsdf dsalf asdfsadf
    dsaf sadfldsaf sadkf sadlfsadf
    asdf safdsafdksadf sadf lasldkfjsaldf safdsa
    dfsadflsadf asdlfsafdsafdsa
    fsafdsadf safdls
    </div>
    <div class="draggable"> alsdf dsalf asdfsadf
    dsaf sadfldsaf sadkf sadlfsadf
    asdf safdsafdksadf sadf lasldkfjsaldf safdsa
    dfsadflsadf asdlfsafdsafdsa
    fsafdsadf safdls
    </div>
    <div class="draggable"> alsdf dsalf asdfsadf
    dsaf sadfldsaf sadkf sadlfsadf
    asdf safdsafdksadf sadf lasldkfjsaldf safdsa
    dfsadflsadf asdlfsafdsafdsa
    fsafdsadf safdls
    </div>
    <div class="draggable"> alsdf dsalf asdfsadf
    dsaf sadfldsaf sadkf sadlfsadf
    asdf safdsafdksadf sadf lasldkfjsaldf safdsa
    dfsadflsadf asdlfsafdsafdsa
    fsafdsadf safdls
    </div>
</div>

<强>脚本

var drag = new function(){
      this.box;
      this._cnt;
      this.hvEle;
      this.trgEle;
      this.initX;
      this.initY;
      this.dragging=false;
};
$(document).on('mousedown','#box',function(){
    drag.box = $(this);  
    initX = drag.box.offset().left;
    initY = drag.box.offset().top;

    drag._cnt = drag.box.closest('.container');
    drag.hvEle = drag._cnt.find('.draggable');
    drag.box.css({position:"absolute"});

    drag.hvEle.mousemove(function(e){
        e.stopPropagation();
        drag.dragging=true;
        drag.box.css({cursor:"move"});
        drag.hvEle.removeClass('dragHv');
        drag.trgEle = $(this);
        drag.trgEle.addClass('dragHv');
        var x =  e.pageX ||e.clientX;
        var y =  e.pageY ||e.clientY;
        drag.box.css({left:x,top:y});

    });

    drag.box.mouseup(function(e){        
         drag.trgEle.append(drag.box);
        /***** You can write your required logic here to either append or
         insertBefore or insertAfter
        ****/
        drag.box.css({position:'static',cursor:"move"}); 
         drag.hvEle.unbind('mousemove');
         drag.box.unbind('mouseup');
         drag.dragging=false;
     });
});
$('.container').disableSelection();

这是确保元素不会从容器元素中删除的额外逻辑

$(document).on('mousemove','html',function(){
    if(drag.dragging){
        drag.box.css({cursor:"no-drop"});
    }
});
$(document).on('mouseup','html',function(){
    if(drag.dragging){
        drag.box.css({left:drag.initX,top:drag.initY,position:"static"});
        drag.box.css({cursor:"move"});
        drag.hvEle.unbind('mousemove');
        drag.box.unbind('mouseup');
        dragging=false;
    }
});
**Styles****

    .container{
      width:100%;
      height:100%;    
      border:1px solid green;
    }
    .draggable{
     padding:10px 5px;
    }
    .draggable:nth-child(even){
      background:#efefef;
      border-bottom:1px solid black;
      border-top:1px solid black;
    }
    #box{
     height:50px;
     width:200px;
     background:red;
     cursor:move;   
    }
    .dragHv{
        background-color:yellow !important;
    }

这是fiddle
我希望它至少可以通过某种方式帮助你。

答案 4 :(得分:1)

请参阅此示例,这可能会对您有所帮助

<html>

<head>



<script language="javascript" type="text/javascript"><!--

    var dragitem = undefined;



    function setdragitem(item, evt) {

        dragitem=item;

        // alert('item: '+item);

        // item is an HTML DIV element.

        // evt is an event.



        // If the item should not be draggable, enable this next line.

        // evt.preventDefault();



        return true;

    }

    function cleardragitem() {

        dragitem=undefined;

        // alert('item: '+item);

    }

    function dodrag() {

        // alert('item: '+dragitem);

    }



    // This is required---used to tell WebKit that the drag should

    // be allowed.

    function handledragenter(elt, evt) {

        evt.preventDefault();

        return true;

    }

    function handledragover(elt, evt) {

        evt.preventDefault();

        return true;

    }





    function handledragleave(elt, evt) {



    }



    function handledrop(elt, evt) {

        // alert('drop');

        dragitem.style.display="none";

        var newid=dragitem.id + '_dest';

        var dest = document.getElementById(newid);

        dest.innerHTML = dragitem.innerHTML;

    }





// --></script>



<style type="text/css"><!--



    .wordbox { border: 1px solid black; text-align: center; width: 50px; float: left; -webkit-user-drag: element; -webkit-user-select: none; }

    .spacer { clear: both; }

    .target { margin-top: 30px; padding: 30px; width: 70px; border: 1px solid black; background: #c0c0ff; margin-bottom: 30px; -webkit-user-drop: element; }

    .word   { margin: 30px; min-height: 30px; border-bottom: 1px solid black; width: 50px;  float: left; }



--></style>



</head>

<body>



<p>Drop words onto target area to put them in their places.</p>



<div class='wordbox' id='this' ondragstart='setdragitem(this, event);' ondrag='dodrag();' ondragend='cleardragitem();'>This</div>

<div class='wordbox' id='is' ondragstart='setdragitem(this, event);' ondrag='dodrag();' ondragend='cleardragitem();'>is</div>

<div class='wordbox' id='a' ondragstart='setdragitem(this, event);' ondrag='dodrag();' ondragend='cleardragitem();'>a</div>

<div class='wordbox' id='test' ondragstart='setdragitem(this, event);' ondrag='dodrag();' ondragend='cleardragitem();'>test</div>



<div class='spacer'></div>

<div class='target' ondragenter='handledragenter(this, event);' ondragover='handledragover(this, event);' ondragleave='handledragleave(this, event);' ondrop='handledrop(this, event);'>TARGET</div>



<div class='words'>

<div class='word' id='this_dest'></div>

<div class='word' id='is_dest'></div>

<div class='word' id='a_dest'></div>

<div class='word' id='test_dest'></div>

</div>





</body>

</html>

了解更多see this link

否则see this link also

答案 5 :(得分:-1)

Pinocchio,我建议你在放弃之前创建一些预览可放置设计(一个新的onmouseover / mouseenter预览版),我可以帮助你解决你的算法问题。

我也放弃使用getFromPoint(用于交叉浏览支持),而是使用JQuery mouseenter / mouseleave来指定鼠标悬停时间的悬停元素。

手持悬停元素以及相对于文档的位置(jQquery.position),您可以根据光标位置以某种方式进行计算,以便附加或添加元素。

希望有所帮助

答案 6 :(得分:-1)

请不要将我的答案投下来,但我很惊讶没有人提到jQuery UI "Draggable"功能!它非常强大且可以自定义,而不是尝试构建自己的。