拖拽

HTML5 的拖放 API 能够支持在网站内部和网站之间拖放项目。同时也提供了一个更简单的供扩展和基于 Mozilla 的应用程序使用的 API。一个典型的drag操作是这样开始的:

  • 用户用鼠标选中一个可拖动的(draggable)元素
  • 移动鼠标到一个可放置的(droppable)元素
  • 然后释放鼠标。

相关事件

  • 源对象:
    • dragstart:源对象开始拖放。
    • drag:源对象拖放过程中。
    • dragend:源对象拖放结束。
  • 过程对象:
    • dragenter:源对象开始进入过程对象范围内。
    • dragover:源对象在过程对象范围内移动。
    • dragleave:源对象离开过程对象的范围。
  • 目标对象:
    • drop:源对象被拖放到目标对象内。

draggable 属性

想要拖放某个元素,必须设置该元素的 draggable 属性为 true,当该属性为 false 时,将不允许拖放。而 img 元素和 a 元素都默认设置了 draggable 属性为 true,可直接拖放,如果不想拖放这两个元素,把属性设为 false 即可。

<div class="item" draggable="true">可以拖拽元素</div>

dataTransfer 对象

在所有拖放事件中提供了一个数据传递对象 dataTransfer,用于在源对象和目标对象间传递数据。接下来认识一下这个对象的方法和属性,来了解它是如何传递数据的。dataTransfer 对象不支持IE,所以尽量用别的方法来存储我们需要的信息。以下简短介绍下其方法:

  • setData()
  • getData()
  • clearData()
  • setDragImage()
  • effectAllowed 和 dropEffect 属性

放置区域

当拖动一个项目到HTML元素中时,浏览器默认不会有任何响应。想要让一个元素变成可释放区域,该元素必须设置ondragover和ondrop事件处理程序属性。注意每个处理程序调用preventDefault()来阻止对这个事件的其它处理过程(如触点事件或指针事件)。下面的例子通过简单的事件处理展示了如何使用这些属性:

<p id="p1" draggable="true" ondragstart="dragstart_handler(event);">This element is draggable.</p>
<div id="target" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">Drop Zone</div>
function dragstart_handler(ev) {
 // Add the target element's id to the data transfer object
 ev.dataTransfer.setData("text/plain", ev.target.id);
 ev.dropEffect = "move";
}
function dragover_handler(ev) {
 ev.preventDefault();
 // Set the dropEffect to move
 ev.dataTransfer.dropEffect = "move"
}
function drop_handler(ev) {
 ev.preventDefault();
 // Get the id of the target and add the moved element to the target's DOM
 var data = ev.dataTransfer.getData("text");
 ev.target.appendChild(document.getElementById(data));
}

拖放排序

<div class="container">
    <div class="doing-section">
        <p class="sub-title">进行中</p>
        <div class="list">
            <div class="item" draggable="true" data-id="1">1</div>
            <div class="item" draggable="true" data-id="2">2</div>
            <div class="item" draggable="true" data-id="3">3</div>
        </div>
    </div>
    <div class="done-section">
        <p class="sub-title">已完成</p>
        <div class="list">
            <div class="item" draggable="true" data-id="4">4</div>
            <div class="item" draggable="true" data-id="5">5</div>
            <div class="item" draggable="true" data-id="6">6</div>
        </div>
    </div>
    <div class="delete-section">
        <p class="sub-title">回收站</p>
    </div>
</div>
*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html,body{
    height: 100%;
}
.container{
    height: 100%;
    display: flex;
    padding: 10px;
}
.doing-section,.done-section,.delete-section{
    display: flex;
    flex-direction: column;
    width: 288px;
    margin-right: 10px;
    background-color: #eee;
    border-radius: 3px;
    padding: 10px;
}
.sub-title{
    padding: 14px 18px;
    font-size: 15px;
    font-weight: 700;
}
.list{
    flex: auto;
}
.item{
    background: #fff;
    border-radius: 8px;
    margin-bottom: 10px;
    padding: 12px 10px 12px 20px;
    line-height: 20px;
    overflow: hidden;
}
.item:hover{
    transition: all .2s ease;
    border-left: 10px solid #ddd;
    cursor: pointer;
}
.item.draging{
    background: #f5f5f5;
    opacity: .5;
}

var draging_id = null;
var draging_el = null;
var draging_pr = null;

// 开始选项拖拽
$('.item').on('dragstart',function(e){
    let id = e.currentTarget.dataset.id;
    $(this).addClass('draging');
    draging_id = id;
    draging_el = this;
    draging_pr = this.parentNode;
    console.log(id,'start');
    e.originalEvent.dataTransfer.setData("text/plain",id);
    e.originalEvent.dropEffect = "move";
})

// 拖拽结束
$('.item').on('dragend',function(e){
    e.preventDefault(); 
    let id = e.currentTarget.dataset.id;
    $(this).removeClass('draging');
    draging_id = null;
    draging_el = null;
    draging_pr = null;
    console.log(id,'end')
})

// 拖拽排序
$('.item').on('dragenter',function(e){
    e.preventDefault();
    // 防止页面及页面以外的元素拖入报错
    if(!draging_id || !draging_el || !draging_pr){
        return
    }

    // 获取当前被覆盖元素的id
    let id = e.currentTarget.dataset.id;
    if(id !== draging_id ){
        this.parentNode.insertBefore(draging_el,this);
    }
})

// 拖拽排序底部位置
$('.list').on('dragover',function(e){
    e.preventDefault();
    // 防止页面及页面以外的元素拖入报错
    if(!draging_id || !draging_el || !draging_pr){
        return
    }


    var len = $(this).find('.item').length;
    if(!len){
        // 如果没有元素的话,直接添加过去
        $(this).append(draging_el)
    }else{
        // 如何当前元素是最后一个元素,返回,不作处理
        var lastchild = $(this).find('.item').eq(len - 1);
        var lastchildId = lastchild.data('id');
        if(lastchildId == draging_id){
            return
        }

        // 如果鼠标位置高度大于最后一个元素高度的话,元素放在最后一个位置
        var lastchildHeight = lastchild.innerHeight();
        var lastchildTop = lastchild.offset().top;
        var positionTop = lastchildTop + lastchildHeight;
        var mouseTop = e.clientY;
        if(mouseTop > positionTop){
            $(this).append(draging_el)
        }
    }
})

// 回收站
$('.delete-section').on('dragover',function(e){
    e.preventDefault();
}).on('drop',function(e){
    // 防止页面及页面以外的元素拖入报错
    if(!draging_id || !draging_el || !draging_pr){
        return
    }
    e.preventDefault();
    draging_pr.removeChild(draging_el);
})