11

I am currently building a website designer, and one of the core features is to be able to drag & drop to rearrange elements. I have been working on this feature for a few days and have come unstuck a few times. The most important note about this drag and drop system is that the drag-able element can be dropped anywhere within the master container and it will Snap into place so there will be no absolutely positioned elements otherwise the element won't Snap into place.

So first of I started by just building the core draggable bit where you can drag the element, then when you drop the element I am using document.elementFromPoint() to get the element where the cursor position is (note: I have to hide the draggable element otherwise it will return that).

Now I have the element closest to the cursor the main problem is figuring out where the draggable element needs to go relative to that element, because there are 4 options append - prepend - insertBefore - insertAfter. I have managed to get append - prepend & insertBefore working but it's not reliable enough because all I am going is using the height and offset of the target to determine either append or prepend and I am increasing the Y parameter on the getFromPoint to see if I hit a different element in a short distance to determine insertBefore. Here is the code I have so far.

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

This is just my mouseup event the one that does all of the work. the other events just make the element draggable, not really important.

Here is a fiddle

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

So if that did not ask the question then here's a short version.

My drag-able element needs to be able to be dropped anywhere and snap into. What is the best way to do this because the way I have done it in the fiddle is definitely not good. How can I detect where the element needs to go relative to the target on mouseup.

Reference to weird section.

Here's how it works (warning, it's not good). When I get the target element with elementFromPoint I then create a loop that will loop 15 times and increment the Y value that goes into the elementFromPoint so basically the elementFromPoint is moving down 15px and if it hits a new element within that short space I am assuming that you want to insert the element before the target.

I am more than happy to receive answers that have nothing to do with this code as that would benefit other users too.

I would like to note that the container that will have drag-able is the main part of the designer. So it is not a choice for me to have all absolutely positioned elements and I wouldn't really be a good idea to put a element in every possible place so that I can detect where the drag-able element has to go because whatever is in that container will be a quality result with no unnecessary content.

I would also like to note that my application does not and will not ever support and old browsers ie IE6, IE7, IE8, IE9

4

6 回答 6

8

我会考虑使用 jQuery UI 的Sortable 小部件portlet 示例处理和insertBefore需求insertAfter。我创建了一个简单的小提琴,它建立在 portlet 示例的基础上,也满足了prependappend要求。

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

小提琴

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>
于 2013-08-25T05:19:31.427 回答
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';
于 2013-08-28T00:05:42.920 回答
3

ChaseMoskal 使用 contenteditable="true" 很好地解决了这个问题:

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/

于 2013-08-31T16:36:43.643 回答
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;
    }

这是小提琴
我希望它至少能在某种程度上帮助你。

于 2013-08-27T12:17:38.473 回答
-1

Pinocchio,我建议您在放置之前创建某种可放置的预览设计(新的 onmouseover/mouseenter 预览),我相信这将帮助您参与您的算法。

我也将放弃使用 getFromPoint (用于跨浏览支持),而是使用 JQuery mouseenter / mouseleave 来分配时间的悬停元素以进行 mouseup/drop。

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

希望能帮助到你

于 2013-08-24T20:08:01.657 回答
-1

我很惊讶没有人提到jQuery UI “可拖动”功能!它非常强大且可定制,而不是尝试构建自己的。

于 2013-08-28T10:11:19.697 回答