面试题:如何实现一个拖拽插件(一)

1,448 阅读3分钟

在我的项目中使用过vue.draggable这个插件,用来做拖拽,它是封装Sortable这个插件的。

各位大爷们,传送门我放这:

github.com/SortableJS/…

github.com/SortableJS/…

面试官:说说你觉得最有挑战的项目?

我:巴拉巴拉,我用了一个vue.draggable做了拖拽效果

面试官:哦,看过其源码么?说说这个vue.draggable的拖拽原理是什么?

我:本质上是利用H5的drag属性来做的,一个数组,改变其的顺序,巴拉巴拉

面试官:那他是怎么实现改变顺序的?

我:o((⊙﹏⊙))o (面对自己不知道的问题坐如针毡,哎,找个地缝进去算了)

我做项目没有看过其源码,说起它的原理来也是靠猜的,今天我网上去下载下源码来,看了半个小时,两眼一抹黑,更懵了。

然后我就网上去找拖拽改变顺序的原理,万能的搜索引擎,我找到了这个大佬写的,拿来看了,方才如梦初醒。各位大爷,链接给你们放下面了。

www.jianshu.com/p/a923add40…

我把他的代码拷贝下来,运行了下,看了看,自己照着写了写,谢谢作者大佬。谢谢大哥精简的代码,让我明白了其实现原理。厉害,厉害,谢谢!谢谢!谢谢!

如果有侵权,我立即删了

虽然赶不上人家,不过,胡适说过:

怕什么真理无穷,进一寸有一寸的欢喜。

我之前想得很复杂,如何判断拖拽后的元素位于什么位置,如何去改变顺序,想来想去,更没有办法下笔了。各位大爷们,完整渣渣代码我贴在最后,我把我觉得核心的拿出来讲讲。还是带着问题去看吧。

  • 靠哪个属性进行拖拽?
  • 怎么判断拖拽到哪个位置了?
  • 怎么交换顺序?

1、靠哪个属性值进行拖拽

利用h5的拖拽属性draggable,配合相应的事件ondragstart和ondragover来实现拖拽。

2、怎么判断拖拽到哪个位置了

首先被拖拽的目标元素dragObj,经过每一个子元素item时候,都会触发其dragover事件。

可以利用一个函数,来计算item在父元素的的index值。经过该元素时候,计算item和dragObj的index值,然后利用dom的insertBefore来把dragObj插入到该元素上面或者下面。

判断index大小主要用来判断是在item的上面插入还是下面插入。

3、怎么交换顺序

交换顺序主要是使用了dom的insertBefore,它是插入和改变dom节点的api,各位大爷,链接我放这:

www.w3school.com.cn/jsref/met_n…

所以,说了那么多,那个获取index的函数怎么写?

// 获取元素所在数组的当前index        
function _index(el) {            
    var index = 0;           
    if (!el || !el.parentNode) {                
        return -1;          
    }            
    // 反复寻找它有几个前兄弟元素            
    // previousElementSibling 属性返回指定元素的前一个兄弟元素,循环遍历之前的所有兄弟节点           
    while (el && (el = el.previousElementSibling)) {                
        // console.log(el);                
        index++;                
        // console.log('--while---',el,index);            
    }            
    // console.log(index)            
    return index;        
}

最后,

关键:一个属性,一个变量,两个事件,一个函数,一个dom的api。

一个属性:draggable="true"

一个变量:用来存放被拖拽的元素dragObj

两个事件:ondragstart,ondragover

一个函数:计算元素处于父元素的子元素数组里面的哪个index

一个dom的api:insertBefore

思路

1、给被拖拽的子元素加上draggable="true"属性,

2、父元素绑定ondragstart和ondragover事件,

3、dragstart的时候,把被拖拽的元素赋值存起来为dragObj,

4、每个被经过的子元素target都会有dragover事件,

5、在dragover事件里,利用获取index的函数,判断被拖拽元素dragObj和target元素的index的大小

6、在taget的nextSibing或者taget自己利用insertBefore插入dragObj。

最后:

这个版本是乞丐版本,各位大爷们将就下哈。我后续还会接着啃vue.draggable源码,给各位看官用口水话讲出来,方便理解,我还会继续更新拖拽方面的内容,这只是一个开始。

R3a614dfc9ac6dfde8427d19d367537e5.gif

最后渣渣代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .item {
            width: 100%;
            height: 50px;
            border: 1px solid red;
        }
    </style>
</head>

<body>
    <div id="ordered-container"> </div>
    <script>            
        var data = [
        { label: "Item 1" }, 
        { label: "Item 2" }, 
        { label: "Item 3" }, 
        { label: "Item 4" }, 
        { label: "Item 5" }, 
        { label: "Item 6" }]

        setupOrderedContainers();

        function setupOrderedContainers() {            
            // 先渲染列表            
            var container = document.querySelector("#ordered-container");            
            var html = '';            
            data.forEach(item => {               
                html+=`<div class="item" draggable="true">                            
                    ${item.label}                        
                    </div>`;           
            })            
            container.innerHTML = html;            
            var drag = null;            
            // 拖拽开始,赋值            
            container.ondragstart = function (event) {                
                drag = event.target;           
            }            
            // 拖拽过程中,插入元素            
            container.ondragover = function (event) {                
                var target = event.target;                
                // console.log('--ondragover---', _index(drag), _index(target))                
                if(_index(drag) < _index(target)) { 
                    // 向下拖拽                   
                    // console.log('向下拖拽');                    
                    target.parentNode.insertBefore(drag, target.nextSibing);                
                } else {                    
                    // console.log('向上拖拽');                    
                    target.parentNode.insertBefore(drag, target);                
                }            
            }                   
        }  

        // 获取元素所在数组的当前index        
        function _index(el) {            
            var index = 0;           
            if (!el || !el.parentNode) {                
                return -1;          
            }            
            // 反复寻找它有几个前兄弟元素            
            // previousElementSibling 属性返回指定元素的前一个兄弟元素,循环遍历之前的所有兄弟节点           
            while (el && (el = el.previousElementSibling)) {                
                // console.log(el);                
                index++;                
                // console.log('--while---',el,index);            
            }            
            // console.log(index)            
            return index;        
        }        
    </script>
</body>

</html>