源码分享者:姚观寿
最近公司项目经常用到一个拖拽 Sortable.js插件,所以有空的时候看了 Sortable.js 源码,总共1300多行这样,写的挺完美的。 本帖属于原创,转载请出名出处。
girhub地址:https://github.com/qq281113270/Sortable 希望大家帮我在github 点个赞 支持我
技术交流qq群 302817612
拖拽的时候主要由这几个事件完成,
ondragstart 事件:当拖拽元素开始被拖拽的时候触发的事件,此事件作用在被拖曳元素上
ondragenter 事件:当拖曳元素进入目标元素的时候触发的事件,此事件作用在目标元素上
ondragover 事件:拖拽元素在目标元素上移动的时候触发的事件,此事件作用在目标元素上
ondrop 事件:被拖拽的元素在目标元素上同时鼠标放开触发的事件,此事件作用在目标元素上
ondragend 事件:当拖拽完成后触发的事件,此事件作用在被拖曳元素上
主要是拖拽的时候发生ondragstart事件和ondragover事件的时候节点交换位置,其实就是把他们的节点互相调换,当然这只是最简单的拖拽排序方式,里面用到了许多技术比用判断拖拽滚动条的时候是滚动拖拽元素上面的根节点的父节点滚动,还是滚动window上面的滚动条, 还有拖拽的时候利用getBoundingClientRect() 属性判断鼠标是在dom节点的左边,右边,上面,还是下面。还有利用回调和函数式编程声明函数,利用布尔值相加相减去,做0和1判断,利用了事件绑定来判定两个列表中的不同元素,这些都是很有趣的技术。
注意:这个插件是用html5 拖拽的所以也不支持ie9 以下浏览器
接下来我们先看看简单的简单的dome,先加载他的拖拽js Sortable.js 插件,和app.css. 创建一个简单的拖拽很简单 只要传递一个dom节点进去就可以,第二个参数传一个空对象进去
当然app.css,加不加无所谓,如果不加的话要加一个样式就是
.sortable-ghost {
opacity: 0.4;
background-color: #F4E2C9;
}
拖拽的时候有阴影效果更好看些
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>无标题文档</title> 6 </head> 7 <link href="app.css" rel="stylesheet" type="text/css"/> 8 <script src="Sortable.js"></script> 9 <body> 10 <ul id="foo" class="block__list block__list_words"> 11 <li>1</li> 12 <li>2</li> 13 <li>3</li> 14 <li>4</li> 15 <li>5</li> 16 <li>6</li> 17 <li>7</li> 18 <li>8</li> 19 </ul> 20 21 <script> 22 Sortable.create(document.getElementById('foo'), {}); 23 </script> 24 </body> 25 </html>
该插件还提供了拖拽时候动画,让拖拽变得更炫,很简单加多一个参数就行animation: 150,拖拽时间内执行完动画的时间。里面是用css3动画的ie9以下浏览器 含ie9浏览器不支持
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>无标题文档</title> 6 </head> 7 <link href="app.css" rel="stylesheet" type="text/css"/> 8 <script src="Sortable.js"></script> 9 <body> 10 <ul id="foo" class="block__list block__list_words"> 11 <li>1</li> 12 <li>2</li> 13 <li>3</li> 14 <li>4</li> 15 <li>5</li> 16 <li>6</li> 17 <li>7</li> 18 <li>8</li> 19 </ul> 20 21 <script> 22 Sortable.create(document.getElementById('foo'), 23 { 24 animation: 150, //动画参数 25 }); 26 </script> 27 </body> 28 </html>
这个插件不仅仅提供拖拽功能,还提供了拖拽完之后排序,当然排序的思维很简单,判断鼠标按下去拖拽的那个节点和拖拽到目标节点的两个元素发生ondragover的时候判断他们的dom节点位置,并且互换dom位置就形成了排序。拖拽完成只有 Sortable.js 插件还提供了几个事件接口,我们看看那dome,
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>无标题文档</title> 6 </head> 7 <link href="app.css" rel="stylesheet" type="text/css"/> 8 <script src="Sortable.js"></script> 9 <body> 10 <ul id="foo" class="block__list block__list_words"> 11 <li>1</li> 12 <li>2</li> 13 <li>3</li> 14 <li>4</li> 15 <li>5</li> 16 <li>6</li> 17 <li>7</li> 18 <li>8</li> 19 </ul> 20 <script> 21 Sortable.create(document.getElementById('foo'), { 22 animation: 150, //动画参数 23 onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件 24 console.log('onAdd.foo:', [evt.item, evt.from]); 25 }, 26 onUpdate: function (evt){ //拖拽更新节点位置发生该事件 27 console.log('onUpdate.foo:', [evt.item, evt.from]); 28 }, 29 onRemove: function (evt){ //删除拖拽节点的时候促发该事件 30 console.log('onRemove.foo:', [evt.item, evt.from]); 31 }, 32 onStart:function(evt){ //开始拖拽出发该函数 33 console.log('onStart.foo:', [evt.item, evt.from]); 34 }, 35 onSort:function(evt){ //发生排序发生该事件 36 console.log('onSort.foo:', [evt.item, evt.from]); 37 }, 38 onEnd: function(evt){ //拖拽完毕之后发生该事件 39 console.log('onEnd.foo:', [evt.item, evt.from]); 40 } 41 }); 42 </script> 43 </body> 44 </html>
我们看看上面的例子,首先看看当拖拽完成的时候他发生事件顺序
onAdd onRemove 没有触发说明当列表中的拖拽数据有增加和减少的时候才会发生该事件, 当然如果有兴趣的朋友可以看看他生事件的顺序和条件。
还有传递一个evt参数,我们看看该参数有些什么东西。主要看_dispatchEvent 这个函数 改函数的功能是:创建一个事件,事件参数主要由name 提供,并且触发该事件,其实就是模拟事件并且触发该事件。
看看改函数的关键源码
var evt = document.createEvent('Event'), //创建一个事件
options = (sortable || rootEl[expando]).options, //获取options 参数
//name.charAt(0) 获取name的第一个字符串
//toUpperCase() 变成大写
//name.substr(1) 提取从索引为1下标到字符串的结束位置的字符串
//onName 将获得 on+首个字母大写+name从第一个下标获取到的字符串
onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);
evt.initEvent(name, true, true); //自定义一个事件
evt.to = rootEl; //在触发该事件发生evt的时候,将evt添加多一个to属性,值为rootEl
evt.from = fromEl || rootEl; //在触发该事件发生evt的时候,将evt添加多一个to属性,值为rootEl
evt.item = targetEl || rootEl; //在触发该事件发生evt的时候,将evt添加多一个to属性,值为rootEl
evt.clone = cloneEl; //在触发该事件发生evt的时候,将evt添加多一个to属性,值为rootEl
evt.oldIndex = startIndex; //开始拖拽节点
evt.newIndex = newIndex; //现在节点
//触发该事件,并且是在rootEl 节点上面 。触发事件接口就这这里了。onAdd: onUpdate: onRemove:onStart:onSort:onEnd:
接下来事件有了, 我们怎么做排序呢?其实很简单,只要我们做排序的列表中加一个drag-id就可以,然后在拖拽过程中有几个事件onAdd, onUpdate,onRemove,onStart,onSort,onEnd,
然后我们不需要关心这么多事件,我们也不需要关心中间拖拽发生了什么事情。然后我们关心的是当拖拽结束之后我们只要调用onEnd事件就可以了 然后改接口会提供 evt。 evt中可以有一个from就是拖列表的根节点
只要获取到改节点下面的字节的就可以获取到排序id。请看dome
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>无标题文档</title> 6 </head> 7 <link href="app.css" rel="stylesheet" type="text/css"/> 8 <script src="Sortable.js"></script> 9 10 <body> 11 <ul id="foo" class="block__list block__list_words"> 12 <li drag-id="1">1</li> 13 <li drag-id="2">2</li> 14 <li drag-id="3">3</li> 15 <li drag-id="4">4</li> 16 <li drag-id="5">5</li> 17 <li drag-id="6">6</li> 18 <li drag-id="7">7</li> 19 <li drag-id="8">8</li> 20 </ul> 21 <script> 22 Sortable.create(document.getElementById('foo'), { 23 animation: 150, //动画参数 24 onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件 25 console.log('onAdd.foo:', [evt.item, evt.from]); 26 }, 27 onUpdate: function (evt){ //拖拽更新节点位置发生该事件 28 console.log('onUpdate.foo:', [evt.item, evt.from]); 29 }, 30 onRemove: function (evt){ //删除拖拽节点的时候促发该事件 31 console.log('onRemove.foo:', [evt.item, evt.from]); 32 }, 33 onStart:function(evt){ //开始拖拽出发该函数 34 console.log('onStart.foo:', [evt.item, evt.from]); 35 }, 36 onSort:function(evt){ //发生排序发生该事件 37 console.log('onSort.foo:', [evt.item, evt.from]); 38 }, 39 onEnd: function(evt){ //拖拽完毕之后发生该事件 40 console.log('onEnd.foo:', [evt.item, evt.from]); 41 var id_arr='' 42 for(var i=0, len=evt.from.children.length; i<len; i++){ 43 id_arr+=','+ evt.from.children[i].getAttribute('drag-id'); 44 } 45 id_arr=id_arr.substr(1); 46 //然后请求后台ajax 这样就完成了拖拽排序 47 console.log(id_arr); 48 } 49 }); 50 </script> 51 </body> 52 </html>
该插件还提供了多列表拖拽。下面dome是 从a列表拖拽到b列表,b列表拖拽到a列表 两个俩表互相拖拽,然后主要参数是 group
如果group不是对象则变成对象,并且group对象的name就等于改group的值 并且添加多['pull', 'put'] 属性默认值是true
如果设置group{
pull:true, 则可以拖拽到其他列表 否则反之
put:true, 则可以从其他列表中放数据到改列表,false则反之
}
pull: 'clone', 还有一个作用是克隆,就是当这个列表拖拽到其他列表的时候不会删除改列表的节点。
看看简单的列表互相拖拽dome 只要设置参数group:"words", group的name要相同才能互相拖拽
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>无标题文档</title> 6 </head> 7 <link href="app.css" rel="stylesheet" type="text/css"/> 8 <script src="Sortable.js"></script> 9 <body> 10 <div class="container" style="height: 520px"> 11 <div data-force="30" class="layer block" style="left: 14.5%; top: 0; width: 37%"> 12 <div class="layer title">List A</div> 13 <ul id="foo" class="block__list block__list_words"> 14 <li>бегемот</li> 15 <li>корм</li> 16 <li>антон</li> 17 <li>сало</li> 18 <li>железосталь</li> 19 <li>валик</li> 20 <li>кровать</li> 21 <li>краб</li> 22 </ul> 23 </div> 24 25 26 27 28 <div data-force="18" class="layer block" style="left: 58%; top: 143px; width: 40%;"> 29 <div class="layer title">List B</div> 30 <ul id="bar" class="block__list block__list_tags"> 31 <li>казнить</li> 32 <li>,</li> 33 <li>нельзя</li 34 ><li>помиловать</li> 35 </ul> 36 </div> 37 </div> 38 39 <script> 40 41 Sortable.create(document.getElementById('foo'), { 42 group:"words", 43 animation: 150, //动画参数 44 onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件 45 console.log('onAdd.foo:', [evt.item, evt.from]); 46 }, 47 onUpdate: function (evt){ //拖拽更新节点位置发生该事件 48 console.log('onUpdate.foo:', [evt.item, evt.from]); 49 }, 50 onRemove: function (evt){ //删除拖拽节点的时候促发该事件 51 console.log('onRemove.foo:', [evt.item, evt.from]); 52 }, 53 onStart:function(evt){ //开始拖拽出发该函数 54 console.log('onStart.foo:', [evt.item, evt.from]); 55 }, 56 onSort:function(evt){ //发生排序发生该事件 57 console.log('onSort.foo:', [evt.item, evt.from]); 58 }, 59 onEnd: function(evt){ //拖拽完毕之后发生该事件 60 console.log('onEnd.foo:', [evt.item, evt.from]); 61 } 62 }); 63 64 65 Sortable.create(document.getElementById('bar'), { 66 group:"words", 67 animation: 150, //动画参数 68 onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件 69 console.log('onAdd.foo:', [evt.item, evt.from]); 70 }, 71 onUpdate: function (evt){ //拖拽更新节点位置发生该事件 72 console.log('onUpdate.foo:', [evt.item, evt.from]); 73 }, 74 onRemove: function (evt){ //删除拖拽节点的时候促发该事件 75 console.log('onRemove.foo:', [evt.item, evt.from]); 76 }, 77 onStart:function(evt){ //开始拖拽出发该函数 78 console.log('onStart.foo:', [evt.item, evt.from]); 79 }, 80 onSort:function(evt){ //发生排序发生该事件 81 console.log('onSort.foo:', [evt.item, evt.from]); 82 }, 83 onEnd: function(evt){ //拖拽完毕之后发生该事件 84 console.log('onEnd.foo:', [evt.item, evt.from]); 85 } 86 }); 87 88 </script> 89 </body> 90 </html>
当然也支持 只能从a列表拖拽到b列表 dome
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>无标题文档</title> 6 </head> 7 <link href="app.css" rel="stylesheet" type="text/css"/> 8 <script src="Sortable.js"></script> 9 <body> 10 <div class="container" style="height: 520px"> 11 <div data-force="30" class="layer block" style="left: 14.5%; top: 0; width: 37%"> 12 <div class="layer title">List A</div> 13 <ul id="foo" class="block__list block__list_words"> 14 <li>бегемот</li> 15 <li>корм</li> 16 <li>антон</li> 17 <li>сало</li> 18 <li>железосталь</li> 19 <li>валик</li> 20 <li>кровать</li> 21 <li>краб</li> 22 </ul> 23 </div> 24 25 26 27 28 <div data-force="18" class="layer block" style="left: 58%; top: 143px; width: 40%;"> 29 <div class="layer title">List B</div> 30 <ul id="bar" class="block__list block__list_tags"> 31 <li>казнить</li> 32 <li>,</li> 33 <li>нельзя</li 34 ><li>помиловать</li> 35 </ul> 36 </div> 37 </div> 38 39 <script> 40 41 Sortable.create(document.getElementById('foo'), { 42 group: { 43 name:"words", 44 pull: true, 45 put: true 46 }, 47 animation: 150, //动画参数 48 onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件 49 console.log('onAdd.foo:', [evt.item, evt.from]); 50 }, 51 onUpdate: function (evt){ //拖拽更新节点位置发生该事件 52 console.log('onUpdate.foo:', [evt.item, evt.from]); 53 }, 54 onRemove: function (evt){ //删除拖拽节点的时候促发该事件 55 console.log('onRemove.foo:', [evt.item, evt.from]); 56 }, 57 onStart:function(evt){ //开始拖拽出发该函数 58 console.log('onStart.foo:', [evt.item, evt.from]); 59 }, 60 onSort:function(evt){ //发生排序发生该事件 61 console.log('onSort.foo:', [evt.item, evt.from]); 62 }, 63 onEnd: function(evt){ //拖拽完毕之后发生该事件 64 console.log('onEnd.foo:', [evt.item, evt.from]); 65 } 66 }); 67 68 69 Sortable.create(document.getElementById('bar'), { 70 group: { 71 name:"words", 72 pull: false, 73 put: true 74 }, 75 animation: 150, //动画参数 76 onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件 77 console.log('onAdd.foo:', [evt.item, evt.from]); 78 }, 79 onUpdate: function (evt){ //拖拽更新节点位置发生该事件 80 console.log('onUpdate.foo:', [evt.item, evt.from]); 81 }, 82 onRemove: function (evt){ //删除拖拽节点的时候促发该事件 83 console.log('onRemove.foo:', [evt.item, evt.from]); 84 }, 85 onStart:function(evt){ //开始拖拽出发该函数 86 console.log('onStart.foo:', [evt.item, evt.from]); 87 }, 88 onSort:function(evt){ //发生排序发生该事件 89 console.log('onSort.foo:', [evt.item, evt.from]); 90 }, 91 onEnd: function(evt){ //拖拽完毕之后发生该事件 92 console.log('onEnd.foo:', [evt.item, evt.from]); 93 } 94 }); 95 96 </script> 97 </body> 98 </html>
当然也支持克隆 从a列表可克隆dom节点拖拽添加到b俩表 只要把参数 pull: 'clone', 这样就可以了 dome
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>无标题文档</title> 6 </head> 7 <link href="app.css" rel="stylesheet" type="text/css"/> 8 <script src="Sortable.js"></script> 9 <body> 10 <div class="container" style="height: 520px"> 11 <div data-force="30" class="layer block" style="left: 14.5%; top: 0; width: 37%"> 12 <div class="layer title">List A</div> 13 <ul id="foo" class="block__list block__list_words"> 14 <li>бегемот</li> 15 <li>корм</li> 16 <li>антон</li> 17 <li>сало</li> 18 <li>железосталь</li> 19 <li>валик</li> 20 <li>кровать</li> 21 <li>краб</li> 22 </ul> 23 </div> 24 25 26 27 <div data-force="18" class="layer block" style="left: 58%; top: 143px; width: 40%;"> 28 <div class="layer title">List B</div> 29 <ul id="bar" class="block__list block__list_tags"> 30 <li>казнить</li> 31 <li>,</li> 32 <li>нельзя</li 33 ><li>помиловать</li> 34 </ul> 35 </div> 36 </div> 37 38 <script> 39 40 Sortable.create(document.getElementById('foo'), { 41 group: { 42 name:"words", 43 pull: 'clone', 44 put: true 45 }, 46 animation: 150, //动画参数 47 onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件 48 console.log('onAdd.foo:', [evt.item, evt.from]); 49 }, 50 onUpdate: function (evt){ //拖拽更新节点位置发生该事件 51 console.log('onUpdate.foo:', [evt.item, evt.from]); 52 }, 53 onRemove: function (evt){ //删除拖拽节点的时候促发该事件 54 console.log('onRemove.foo:', [evt.item, evt.from]); 55 }, 56 onStart:function(evt){ //开始拖拽出发该函数 57 console.log('onStart.foo:', [evt.item, evt.from]); 58 }, 59 onSort:function(evt){ //发生排序发生该事件 60 console.log('onSort.foo:', [evt.item, evt.from]); 61 }, 62 onEnd: function(evt){ //拖拽完毕之后发生该事件 63 console.log('onEnd.foo:', [evt.item, evt.from]); 64 } 65 }); 66 67 68 Sortable.create(document.getElementById('bar'), { 69 group: { 70 name:"words", 71 pull: false, 72 put: true 73 }, 74 animation: 150, //动画参数 75 onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件 76 console.log('onAdd.foo:', [evt.item, evt.from]); 77 }, 78 onUpdate: function (evt){ //拖拽更新节点位置发生该事件 79 console.log('onUpdate.foo:', [evt.item, evt.from]); 80 }, 81 onRemove: function (evt){ //删除拖拽节点的时候促发该事件 82 console.log('onRemove.foo:', [evt.item, evt.from]); 83 }, 84 onStart:function(evt){ //开始拖拽出发该函数 85 console.log('onStart.foo:', [evt.item, evt.from]); 86 }, 87 onSort:function(evt){ //发生排序发生该事件 88 console.log('onSort.foo:', [evt.item, evt.from]); 89 }, 90 onEnd: function(evt){ //拖拽完毕之后发生该事件 91 console.log('onEnd.foo:', [evt.item, evt.from]); 92 } 93 }); 94 </script> 95 </body> 96 </html>
该插件也支持删除拖拽列表的节点,主要是设置filter 参数,改参数可以设置成函数,但是设置成函数的时候不还要自己定义拖拽,显得有些麻烦,所以一般设置成class,或者是tag,设置成class和tag的时候就是做拖拽列表中含有calss,tag的节点可以点击的时候可以触发onFilter函数,触发会传递一个evt参数进来evt.item 就是class或者tag的dom节点,可以通过他们的血缘关系从而删除需要删除的节点。
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>无标题文档</title> 6 </head> 7 <link href="st/app.css" rel="stylesheet" type="text/css"/> 8 <script src="Sortable3.js"></script> 9 <body> 10 11 <body> 12 <!-- Editable list --> 13 <a name="e"></a> 14 <div class="container" style="margin-top: 100px"> 15 <div id="filter" style="margin-left: 30px"> 16 <div><div data-force="5" class="layer title title_xl">Editable list</div></div> 17 18 <div style="margin-top: -8px; margin-left: 10px" class="block__list block__list_words"> 19 <ul id="editable"> 20 <li>Оля<i class="js-remove">✖</i></li> 21 <li>Владимир<i class="js-remove">✖</i></li> 22 <li>Алина<i class="js-remove">✖</i></li> 23 </ul> 24 25 26 </div> 27 </div> 28 </div> 29 <script> 30 // Editable list 31 var editableList = Sortable.create(document.getElementById('editable'), { 32 animation: 150, 33 filter: '.js-remove', 34 onFilter: function (evt) { 35 console.log(evt.item) 36 evt.item.parentNode.parentNode.removeChild(evt.item.parentNode); 37 } 38 }); 39 40 </script> 41 </body> 42 </html>
Sortable.js 接口参数还有很多个,不 一 一 做dome了列出来给大家看看,其中比较常用的是上面所说的dome参数,还有handle这个参数也常用规定哪些calss,或者tag拖拽。
其他参数接口设置:
group: Math.random(), //产生一个随机数 //产生一个随机数 //改参数是对象有三个两个参数
pull: 拉, put:放 默认都是是true pull还有一个值是: 'clone', pull: 拉,
put:放 设置为false 就不能拖拽了, 如果 pull 这种为'clone'则可以重一个列表中拖拽到另一个列表并且克隆dom节点,
name:是两个或者多个列表拖拽之间的通信,如果name相同则他们可以互相拖拽
sort: true, // 类型:Boolean,分类 false时候在自己的拖拽区域不能拖拽,但是可以拖拽到其他区域,true则可以做自己区域拖拽或者其他授权地方拖拽
disabled: false, //类型:Boolean 是否禁用拖拽 true 则不能拖拽 默认是true
store: null, // 用来html5 存储的 改返回 拖拽的节点的唯一id
handle: null, //handle 这个参数是设置该标签,或者该class可以拖拽 但是不要设置 id的节点和子节点相同的tag不然会有bug
scroll: true, //类型:Boolean,设置拖拽的时候滚动条是否智能滚动。默认为真,则智能滚动,false则不智能滚动
scrollSensitivity: 30, //滚动的灵敏度,其实是拖拽离滚动边界的距离触发事件的距离边界+-30px的地方触发拖拽滚动事件,
scrollSpeed: 10, //滚动速度
draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',//draggable 判断拖拽节点的父层是否是ou ul
ghostClass: 'sortable-ghost', // 排序镜像class,就是当鼠标拉起拖拽节点的时候添加该class
chosenClass: 'sortable-chosen', // //为拖拽的节点添加一个class 开始拖拽鼠标按下去的时候 添加该class
ignore: 'a, img', //a 或者是img
filter: null, //改参数可以传递一个函数,或者字符串,字符串可以是class或者tag,然后用于触发oFilter函数,这样可以用来自定义事件等
animation: 0, //拖拽动画时间戳
setData: function (dataTransfer, dragEl) { //设置拖拽传递的参数
dataTransfer.setData('Text', dragEl.textContent);
},
dropBubble: false, // 发生 drop事件 拖拽的时候是否阻止事件冒泡
dragoverBubble: false, //发生 dragover 事件 拖拽的时候是否阻止事件冒泡
dataIdAttr: 'data-id', //拖拽元素的id 数组
delay: 0, //延迟拖拽时间, 其实就是鼠标按下去拖拽延迟
forceFallback: false, // 不详
fallbackClass: 'sortable-fallback', // 排序回退class
fallbackOnBody: false,// 是否把拖拽镜像节点ghostEl放到body上
下面是Sortable.js插件源码,本人花了一些时间做了注释,有兴趣的朋友可以研究下。如果您发现哪些地方有错误注释可以联系我的 技术交流qq群: 302817612
1 /**! 2 * Sortable 3 * @author RubaXa <trash@rubaxa.org> 4 * @license MIT 5 */ 6 7 8 (function (factory) { 9 10 "use strict"; //严格模式 11 12 if (typeof define === "function" && define.amd) { //兼容 require.js 写法 13 define(factory); 14 } 15 else if (typeof module != "undefined" && typeof module.exports != "undefined") { //兼容node写法 16 module.exports = factory(); 17 } 18 else if (typeof Package !== "undefined") { 19 Sortable = factory(); // export for Meteor.js 兼容 Meteor.js 写法 20 } 21 else { 22 /* jshint sub:true */ 23 window["Sortable"] = factory(); //把它挂载在window下 24 25 26 } 27 })(function () { 28 "use strict"; 29 30 if (typeof window == "undefined" || typeof window.document == "undefined") { //判断该js是否在window或者document 下运行 31 return function () { 32 throw new Error("Sortable.js requires a window with a document"); //如果不是则抛出一个错误 33 }; 34 } 35 var i=0; 36 var dragEl, //当前拖拽节点,开始拖拽节点,鼠标按下去的节点 37 parentEl, 38 ghostEl, // 拖拽镜像节点 39 cloneEl, //克隆节点 40 rootEl, //鼠标开始按下去拖拽的根节点 41 nextEl, //下一个节点 42 43 scrollEl,//滚动节点 44 scrollParentEl, //滚动的父节点 45 46 lastEl, //根节点中的最后一个自己点 47 lastCSS, 48 lastParentCSS, 49 50 oldIndex, //开始拖拽节点的索引 就是鼠标按下去拖拽节点的索引 51 newIndex, //拖拽完之后现在节点 52 53 54 activeGroup, 55 autoScroll = {}, //滚动对象用于存鼠标的xy轴 56 /* 57 tapEvt 触摸对象包括x与y轴与拖拽当前节点 58 tapEvt = { 59 target: dragEl, 60 clientX: touch.clientX, 61 clientY: touch.clientY 62 }; 63 */ 64 tapEvt, 65 touchEvt, 66 67 moved, 68 69 /** @const */ 70 RSPACE = /\s+/g, //全局匹配空格 71 72 expando = 'Sortable' + (new Date).getTime(), //字符串Sortable+时间戳 73 74 win = window, //缩写win 75 document = win.document, 76 parseInt = win.parseInt; 77 //draggable html5 拖拽属性 初始化的时候是true 78 79 80 var supportDraggable = !!('draggable' in document.createElement('div')), 81 //判断浏览器是否支持css3 这个属性pointer-events 82 supportCssPointerEvents = (function (el) { 83 el = document.createElement('x'); 84 el.style.cssText = 'pointer-events:auto'; 85 return el.style.pointerEvents === 'auto'; 86 })(), 87 88 _silent = false, //默认 89 90 abs = Math.abs, 91 slice = [].slice, 92 93 touchDragOverListeners = [], //新建一个数组 鼠标触摸拖拽数组 94 //_autoScroll 相当于 被一个函数付值 95 96 /* _autoScroll = function(callback,ms){ 97 var args, 98 _this; 99 if (args === void 0) { 100 args = arguments; 101 _this = this; 102 103 setTimeout(function () { 104 if (args.length === 1) { 105 callback.call(_this, args[0]); 106 } else { 107 callback.apply(_this, args); 108 } 109 110 args = void 0; 111 }, ms); 112 } 113 其实就是_autoScroll=function(参数){ 114 放到 _throttle 的回调函数中 function (/参数/) 115 } 116 }*/ 117 118 119 120 121 /*********************************************************************************************** 122 *函数名 :_autoScroll 123 *函数功能描述 : 拖拽智能滚动 124 *函数参数 : 125 evt: 126 类型:boj, 事件对象 127 options:类型:obj, 参数类 128 rootEl:类型:obj dom节点,拖拽的目标节点 129 *函数返回值 : viod 130 *作者 : 131 *函数创建日期 : 132 *函数修改日期 : 133 *修改人 : 134 *修改原因 : 135 *版本 : 136 *历史版本 : 137 ***********************************************************************************************/ 138 _autoScroll = _throttle( 139 //回调函数 140 function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) { 141 //每次拖拽只会调用一次该函数 142 143 144 //evt 是事件对象 event 145 //options.scroll如果为真 并且rootEl 为真的时候 146 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 147 if (rootEl && options.scroll) { 148 var el, 149 rect, 150 sens = options.scrollSensitivity, //滚动灵敏度 默认是30 151 speed = options.scrollSpeed, //滚动速度 默认是10 152 x = evt.clientX, //获取鼠标在可视窗口的x值 153 y = evt.clientY, //获取鼠标在可视窗口的y值 154 155 winWidth = window.innerWidth, //获取可视窗口的高度和宽度 有兼容性问题 不包括滚动条 156 winHeight = window.innerHeight, 157 158 vx, 159 vy 160 ; 161 162 // Delect scrollEl 观察滚动节点 如果滚动的父节点scrollParentEl不等于当前的根节点的时候则 可以滚动 163 if (scrollParentEl !== rootEl) { 164 scrollEl = options.scroll; //true 布尔值 165 scrollParentEl = rootEl; //鼠标开始按下的根节点 166 167 if (scrollEl === true) { 168 scrollEl = rootEl; 169 do { 170 //判断父节点,哪个父节点出现滚动条,如果有滚动条则设置改拖拽的节点滚动条父节点 171 if ((scrollEl.offsetWidth < scrollEl.scrollWidth) || 172 (scrollEl.offsetHeight < scrollEl.scrollHeight) 173 ) { 174 break; 175 } 176 /* jshint boss:true */ 177 } while (scrollEl = scrollEl.parentNode); 178 } 179 } 180 181 182 if (scrollEl) { 183 el = scrollEl; 184 rect = scrollEl.getBoundingClientRect(); 185 /* 186 var box=document.getElementById('box'); // 获取元素 187 alert(box.getBoundingClientRect().top); // 元素上边距离页面上边的距离 188 alert(box.getBoundingClientRect().right); // 元素右边距离页面左边的距离 189 alert(box.getBoundingClientRect().bottom); // 元素下边距离页面上边的距离 190 alert(box.getBoundingClientRect().left); // 元素左边距离页面左边的距离 191 y:y = evt.clientY, //获取鼠标在可视窗口的y值 192 sens: sens = options.scrollSensitivity, //滚动灵敏度 默认是30 193 194 */ 195 196 //vx 与 vy 只是个布尔值判断 然后就得出一个值 197 /* 198 true-true=0 199 true-false=1 200 false-false=0 201 false-true=-1 202 */ 203 vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens); 204 vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens); //这样判断并不是很好因为只会在边界判断事件发生,如果一开始拖拽快速超过了设置的+-sens值滚动事件将没有发生。个人感觉改成一下判断会比较好。 205 /* 206 if(rect.top+sens-y>=0){ 207 vy=-1; 208 } else if(rect.bottom+sens-y<=0){ 209 vy=1; 210 }else{ 211 vy=0; 212 } 213 */ 214 } 215 216 217 if (!(vx || vy)) { //当他等于0的时候 拖拽滚动的是window 218 219 vx = (winWidth - x <= sens) - (x <= sens); 220 vy = (winHeight - y <= sens) - (y <= sens); 221 222 /* jshint expr:true */ 223 (vx || vy) && (el = win); 224 } 225 226 227 if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) { 228 autoScroll.el = el; 229 autoScroll.vx = vx; 230 autoScroll.vy = vy; 231 //speed=10 滚动速度 232 clearInterval(autoScroll.pid); 233 234 if (el) { 235 autoScroll.pid = setInterval(function () { 236 if (el === win) { 237 win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed); 238 } else { 239 vy && (el.scrollTop += vy * speed); //设置元素滚动条的位置,每次滚动1*speed如果是0 则不会滚动 240 vx && (el.scrollLeft += vx * speed);//设置元素滚动条的位置 241 } 242 }, 243 24); 244 } 245 } 246 } 247 //时间 毫秒 248 }, 30), 249 /*********************************************************************************************** 250 *函数名 :_prepareGroup 251 *函数功能描述 : //options.group 属性变成对象 。如果group不是对象则变成对象,并且group对象的name就等于改group的值 并且添加多['pull', 'put'] 属性默认值是true 252 如果设置group{ 253 pull:true, 则可以拖拽到其他列表 否则反之 254 put:true, 则可以从其他列表中放数据到改列表,false则反之 255 } 256 pull: 'clone', 还有一个作用是克隆,就是当这个列表拖拽到其他列表的时候不会删除改列表的节点。 257 *函数参数 : 258 options: 259 类型:boj, options 拖拽参数 260 261 *函数返回值 : viod 262 *作者 : 263 *函数创建日期 : 264 *函数修改日期 : 265 *修改人 : 266 *修改原因 : 267 *版本 : 268 *历史版本 : 269 ***********************************************************************************************/ 270 271 272 273 _prepareGroup = function (options) { 274 275 var group = options.group; //把options.group 付值给group 276 // 先判断他group 是否是对象,如果不是则变成对象,name是他的属性 277 if (!group || typeof group != 'object') { //如果当前options.group; 不存在或者不是obj则把他变成一个对象 278 group = options.group = {name: group}; 279 } 280 //判断有没有设置 'pull', 'put' 如果没有 则添加 'pull', 'put' 属性并且设置为真 281 ['pull', 'put'].forEach(function (key) { 282 if (!(key in group)) { // 283 group[key] = true; //将为group对象添加两个属性'pull', 'put' 并且为true 284 } 285 }); 286 //options.group 变成对象之后join方法将匹配不到任何东西 287 //如果他直接是数组的话这里就是把数组的值拆分成字符串连接起来 288 //options.group 属性变成对象 。 289 options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' '; 290 } 291 ; 292 293 294 295 /** 296 * @class Sortable 297 * @param {HTMLElement} el 298 * @param {Object} [options] 299 */ 300 //el html dom节点 301 //param obj 数据对象 302 303 /*********************************************************************************************** 304 *函数名 :Sortable 305 *函数功能描述 : 主类,里面包含很多方法 306 *函数参数 : dom节点rootEl 307 *函数返回值 : 308 *作者 : 309 *函数创建日期 : 310 *函数修改日期 : 311 *修改人 : 312 *修改原因 : 313 *版本 : 314 *历史版本 : 315 ***********************************************************************************************/ 316 function Sortable(el, options) { 317 //判断 param 如果不是HTMLDOM 则抛出错误 318 if (!(el && el.nodeType && el.nodeType === 1)) { 319 throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el); 320 } 321 //把dom节点存到this中 好操作 就是id 父层节点 322 this.el = el; // root element 323 this.options = options = _extend({}, options); //把options初始化的数据存到this中 好操作 324 325 326 // Export instance 327 //把 Sortable 类放在HTMLDOM节点的expando属性中 328 el[expando] = this; 329 330 331 // Default options 332 //初始化 defaults 数据 333 var defaults = { 334 group: Math.random(), //产生一个随机数 //产生一个随机数 //改参数是对象有三个两个参数 pull: 拉, put:放 默认都是是true pull还有一个值是: 'clone', pull: 拉, put:放 设置为false 就不能拖拽了, 如果 pull 这种为'clone'则可以重一个列表中拖拽到另一个列表并且克隆dom节点, name:是两个或者多个列表拖拽之间的通信,如果name相同则他们可以互相拖拽 335 336 sort: true, // 类型:Boolean,分类 false时候在自己的拖拽区域不能拖拽,但是可以拖拽到其他区域,true则可以做自己区域拖拽或者其他授权地方拖拽 337 disabled: false, //类型:Boolean 是否禁用拖拽 true 则不能拖拽 默认是true 338 store: null, // 用来html5 存储的 改返回 拖拽的节点的唯一id 339 handle: null, //handle 这个参数是设置该标签,或者该class可以拖拽 但是不要设置 id的节点和子节点相同的tag不然会有bug 340 scroll: true, //类型:Boolean,设置拖拽的时候滚动条是否智能滚动。默认为真,则智能滚动,false则不智能滚动 341 scrollSensitivity: 30, //滚动的灵敏度,其实是拖拽离滚动边界的距离触发事件的距离边界+-30px的地方触发拖拽滚动事件, 342 scrollSpeed: 10, //滚动速度 343 draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',//draggable 判断拖拽节点的父层是否是ou ul 344 ghostClass: 'sortable-ghost', // 排序镜像class,就是当鼠标拉起拖拽节点的时候添加该class 345 chosenClass: 'sortable-chosen', // //为拖拽的节点添加一个class 开始拖拽鼠标按下去的时候 添加该class 346 ignore: 'a, img', //a 或者是img 347 filter: null, //改参数可以传递一个函数,或者字符串,字符串可以是class或者tag,然后用于触发oFilter函数,这样可以用来自定义事件等 348 animation: 0, //拖拽动画时间戳 349 setData: function (dataTransfer, dragEl) { //设置拖拽传递的参数 350 dataTransfer.setData('Text', dragEl.textContent); 351 }, 352 dropBubble: false, // 发生 drop事件 拖拽的时候是否阻止事件冒泡 353 dragoverBubble: false, //发生 dragover 事件 拖拽的时候是否阻止事件冒泡 354 dataIdAttr: 'data-id', //拖拽元素的id 数组 355 delay: 0, //延迟拖拽时间, 其实就是鼠标按下去拖拽延迟 356 forceFallback: false, // 不详 357 fallbackClass: 'sortable-fallback', // 排序回退class 358 fallbackOnBody: false,// 是否把拖拽镜像节点ghostEl放到body上 359 }; 360 361 362 // Set default options 363 //当options类中的数据没有defaults类中的数据的时候 就把defaults类中的数据赋值给options类 364 for (var name in defaults) { 365 !(name in options) && (options[name] = defaults[name]); 366 } 367 //把group: 变成一个对象,本来是一个属性的 368 _prepareGroup(options); 369 370 371 372 // Bind all private methods 373 for (var fn in this) { 374 if (fn.charAt(0) === '_') { 375 //如果这个 Sortable 类下的函数 开始字符串还有_下划线的就把他的this指向Sortable类 376 this[fn] = this[fn].bind(this); 377 } 378 } 379 380 // Setup drag mode 381 //forceFallback 如果是false 那么给supportDraggable 函数他,然后判断浏览器是否支持draggable 拖拽如果支持是true 否则是false 382 this.nativeDraggable = options.forceFallback ? false : supportDraggable; 383 384 385 // Bind events 386 //添加事件 // 入口从这里开始 387 388 _on(el, 'mousedown', this._onTapStart); 389 _on(el, 'touchstart', this._onTapStart); 390 391 392 //html5 dragover 添加拖拽事件 393 if (this.nativeDraggable) { 394 //传递整个类进去 395 _on(el, 'dragover', this); //然后会执行这个函数handleEvent 396 _on(el, 'dragenter', this); //然后会执行这个函数handleEvent 397 } 398 399 //touchDragOverListeners 添加一个false 数据到数组里。 400 touchDragOverListeners.push(this._onDragOver); 401 402 // Restore sorting 403 //sort 排序函数 404 //store 是null 未找到get函数不知道怎么回事 可能它是属于store.js的api 405 options.store && this.sort(options.store.get(this)); 406 } 407 /*********************************************************************************************** 408 *函数名 :Sortable.prototype 409 *函数功能描述 : 主类,的原型 410 *函数参数 : 411 *函数返回值 : 412 *作者 : 413 *函数创建日期 : 414 *函数修改日期 : 415 *修改人 : 416 *修改原因 : 417 *版本 : 418 *历史版本 : 419 ***********************************************************************************************/ 420 421 Sortable.prototype = /** @lends Sortable.prototype */ { 422 constructor: Sortable, //防止继承混乱,构造方法指向他的构造函数 423 /*********************************************************************************************** 424 *函数名 :_onTapStart 425 *函数功能描述 : 鼠标按下去函数,oldIndex统计目标节点与同级同胞的上节点总和 426 *函数参数 : viod 427 *函数返回值 : 无 428 *作者 : 429 *函数创建日期 : 430 *函数修改日期 : 431 *修改人 : 432 *修改原因 : 433 *版本 : 434 *历史版本 : 435 ***********************************************************************************************/ 436 437 _onTapStart: function (/** Event|TouchEvent */evt) { 438 439 var _this = this, 440 el = this.el, //id dom节点 441 options = this.options, //参数类 442 type = evt.type, //事件类型 443 touch = evt.touches && evt.touches[0], //触摸屏事件 444 target = (touch || evt).target, //目标节点 445 originalTarget = target, 446 filter = options.filter; // null 447 448 //如果是鼠标按下去事件,但是如果不是左键按下去的话,或者disabled 为假的时候 结束该程序 disabled 为fasle 449 if (type === 'mousedown' && evt.button !== 0 || options.disabled) { 450 return; // only left button or enabled 451 } 452 //draggable=/[uo]l/i.test(el.nodeName) ? 'li' : '>*', 453 // target=el 454 // target = _closest(target, options.draggable, el); //true 455 // 456 if (!target) { 457 return; 458 } 459 460 // get the index of the dragged element within its parent 461 //获取索引 462 oldIndex = _index(target, options.draggable); 463 464 // Check filter+ 465 //filter 如果是函数 但是默认值filter 466 if (typeof filter === 'function') { 467 if (filter.call(this, evt, target, this)) { //并且有返回值是true 的话 468 469 //触发该函数 470 _dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex); //则触发oFilter事件 471 evt.preventDefault(); //停止默认事件 472 return; // cancel dnd 473 } 474 } 475 else if (filter) { 476 //// JavaScript数组some()方法测试数组中的某个元素是否通过由提供的功能来实现测试 ,只要有一个真则返回真 477 /* 478 例子 479 if (!Array.prototype.some) 480 { 481 Array.prototype.some = function(fun ) 482 { 483 var len = this.length; 484 if (typeof fun != "function") 485 throw new TypeError(); 486 487 var thisp = arguments[1]; 488 for (var i = 0; i < len; i++) 489 { 490 if (i in this && 491 fun.call(thisp, this[i], i, this)) 492 return true; 493 } 494 495 return false; 496 }; 497 } 498 499 function isBigEnough(element, index, array) { 500 return (element >= 10); 501 } 502 503 var retval = [2, 5, 8, 1, 4].some(isBigEnough); 504 document.write("Returned value is : " + retval ); 505 506 var retval = [12, 5, 8, 1, 4].some(isBigEnough); 507 document.write("<br />Returned value is : " + retval ); 508 509 */ 510 511 filter = filter.split(',').some(function (criteria) { //如果filter是字符串,则会用split 拆分成数组并且遍历他只有一个class 对的上则_closest 匹配tag和class 如果设置的filter中有calss 和拖拽元素上面的clss相同,或者tag相同,则会触发oFilter函数 512 criteria = _closest(originalTarget, criteria.trim(), el);//_closest 513 514 if (criteria) { 515 _dispatchEvent(_this, criteria, 'filter', target, el, oldIndex); //调用自定义事件 516 return true; 517 } 518 }); 519 520 if (filter) { 521 evt.preventDefault(); 522 return; // cancel dnd 523 } 524 } 525 526 //handle 存在 527 //originalTarget 528 //handle 这个参数是设置该标签,或者该class可以拖拽 但是不要设置 id的节点和子节点相同的tag不然会有bug 529 530 if (options.handle && !_closest(originalTarget, options.handle, el)) { 531 return; 532 } 533 534 535 // Prepare `dragstart` 536 // 到这里 537 this._prepareDragStart(evt, touch, target); 538 }, 539 540 541 542 543 /*********************************************************************************************** 544 *函数名 :_onTapStart 545 *函数功能描述 : 开始准备拖 546 *函数参数 : evt: 547 类型:obj,事件对象 548 touch: 549 类型:obj,触摸事件对象,判断是否是触摸事件还是鼠标事件 550 target: 类型:dom-obj,目标节点 551 *函数返回值 : 无 552 *作者 : 553 *函数创建日期 : 554 *函数修改日期 : 555 *修改人 : 556 *修改原因 : 557 *版本 : 558 *历史版本 : 559 ***********************************************************************************************/ 560 _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) { 561 //evt pc 的事件对象 562 //touch 移动的的事件对象 563 //target 目标节点 564 var _this = this, 565 el = _this.el, //id节点,就是父层节点 566 options = _this.options, //参数类 567 ownerDocument = el.ownerDocument, //整个文档 568 dragStartFn; //声明开始拖拽函数 569 //target 目标节点存在 dragEl 当前拖拽的节点 并且目标节点的父节点是id的节点的时候 570 if (target && !dragEl && (target.parentNode === el)) { 571 tapEvt = evt; //事件对象 572 rootEl = el; //拖拽的根节点 就是传进来的id那个节点 573 574 dragEl = target; //目标节点 当前的拖拽节点 鼠标按下去拖拽的节点 575 parentEl = dragEl.parentNode; //目标节点 当前的拖拽节点 的父节点 就是 dragEl.parentNode ==rootEl 576 nextEl = dragEl.nextSibling; //目标节点 的下一个节点 577 activeGroup = options.group; //Object {name: "words", pull: true, put: true} 578 579 //开始拖拽函数 580 dragStartFn = function () { 581 // Delayed drag has been triggered 延迟拖动已被触发 582 // we can re-enable the events: touchmove/mousemove 我们可以重新启用touchmove / MouseMove事件: 583 //解绑事件,关闭_dragStartTimer 定时器 取消dragStartFn 函数执行 584 _this._disableDelayedDrag(); 585 586 // Make the element draggable 使元件拖动 587 //把当前的拖拽节点的draggable 属性设置为真,让他支持html5拖拽事件 588 dragEl.draggable = true; 589 590 // Chosen item dragEl 目标节点 类 _this.options.chosenClass='sortable-chosen' 591 //为拖拽的节点添加一个class 592 _toggleClass(dragEl, _this.options.chosenClass, true); 593 594 // Bind the events: dragstart/dragend 绑定事件拖曳开始dragend 595 _this._triggerDragStart(touch); 596 }; 597 598 // Disable "draggable" ignore="a, img" 599 options.ignore.split(',').forEach(function (criteria) { 600 // criteria 遍历数组的当前target 601 602 //criteria.trim() 去除空格 603 /* 604 el.draggable //html5拖拽属性 605 function _disableDraggable(el) { 606 el.draggable = false; 607 } 608 609 */ 610 // 该函数功能是把当前拖拽对象的a和img节点的html5 拖拽属性改为false 611 _find(dragEl, criteria.trim(), _disableDraggable); 612 }); 613 614 _on(ownerDocument, 'mouseup', _this._onDrop); //在ownerDocument 文档上面当发生鼠标抬起的时候,添加_onDrop函数 615 _on(ownerDocument, 'touchend', _this._onDrop);//在ownerDocument 文档上面当发生触摸抬起的时候,添加_onDrop函数 616 _on(ownerDocument, 'touchcancel', _this._onDrop);//在ownerDocument 文档上面当发生触摸划过抬起的时候,解绑_onDrop函数 617 //delay 初始值为0 618 if (options.delay) { 619 /* 620 这里里面的程序块添加了事件只有调用_disableDelayedDrag,添加了一个定时器执行一次dragStartFn函数,这个函数又马上解绑_disableDelayedDrag事件,关闭定时器,整个思路是只让程序发生一次,并且马上解绑事件,销毁该事件。这样思维有些特别 621 */ 622 623 // If the user moves the pointer or let go the click or touch 如果用户移动指针或单击“单击”或“触摸” 624 // before the delay has been reached: //之前的延迟已达到 625 // disable the delayed drag //禁用延迟拖动 626 _on(ownerDocument, 'mouseup', _this._disableDelayedDrag); //当鼠标抬起的时候在文档上添加_disableDelayedDrag事件 627 _on(ownerDocument, 'touchend', _this._disableDelayedDrag); //触摸抬起的时候在文档上添加_disableDelayedDrag事件 628 _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); //触摸划过抬起的时候在文档上添加_disableDelayedDrag事件 629 _on(ownerDocument, 'mousemove', _this._disableDelayedDrag); //当鼠标移动mousemove的时候在文档上添加_disableDelayedDrag事件 630 _on(ownerDocument, 'touchmove', _this._disableDelayedDrag); //触摸移动的时候在文档上添加_disableDelayedDrag事件 631 632 _this._dragStartTimer = setTimeout(dragStartFn, options.delay); //执行dragStartFn函数 633 } else { 634 //开始拖拽 635 dragStartFn(); 636 } 637 } 638 }, 639 640 /*********************************************************************************************** 641 *函数名 :_disableDelayedDrag 642 *函数功能描述 : 禁用延迟拖拽 当拖拽延时的时候,把所有事件解绑,并且关闭定时器。 643 *函数参数 : 644 *函数返回值 : 645 *作者 : 646 *函数创建日期 : 647 *函数修改日期 : 648 *修改人 : 649 *修改原因 : 650 *版本 : 651 *历史版本 : 652 ***********************************************************************************************/ 653 _disableDelayedDrag: function () { 654 var ownerDocument = this.el.ownerDocument; 655 656 clearTimeout(this._dragStartTimer); //关闭定时器 657 _off(ownerDocument, 'mouseup', this._disableDelayedDrag);//当鼠标抬起的时候在文档上解绑_disableDelayedDrag事件 658 659 _off(ownerDocument, 'touchend', this._disableDelayedDrag);//触摸抬起的时候在文档上解绑_disableDelayedDrag事件 660 _off(ownerDocument, 'touchcancel', this._disableDelayedDrag);//当触摸划过抬起的时候在文档上解绑_disableDelayedDrag事件 661 _off(ownerDocument, 'mousemove', this._disableDelayedDrag);//当鼠移动起的时候在文档上解绑_disableDelayedDrag事件 662 _off(ownerDocument, 'touchmove', this._disableDelayedDrag);//触摸的时候在文档上解绑_disableDelayedDrag事件 663 }, 664 /*********************************************************************************************** 665 *函数名 :_triggerDragStart 666 *函数功能描述 : 为拖拽前做好准本,包括判断是否是触摸设备,或者pc,或者没有dragend 667 *函数参数 : 668 *函数返回值 : 669 *作者 : 670 *函数创建日期 : 671 *函数修改日期 : 672 *修改人 : 673 *修改原因 : 674 *版本 : 675 *历史版本 : 676 ***********************************************************************************************/ 677 _triggerDragStart: function (/** Touch */touch) { 678 679 //按下去的值 680 if (touch) { 681 // Touch device support 触摸设备支持 682 tapEvt = { 683 target: dragEl, 684 clientX: touch.clientX, 685 clientY: touch.clientY 686 }; 687 688 this._onDragStart(tapEvt, 'touch'); //触摸设备 689 } 690 else if (!this.nativeDraggable) { 691 692 this._onDragStart(tapEvt, true); //pc设备 693 } 694 else { 695 //如果当前的html还没有设置拖拽属性则先设置拖拽属性 696 _on(dragEl, 'dragend', this); 697 _on(rootEl, 'dragstart', this._onDragStart); 698 699 } 700 701 try { 702 if (document.selection) { 703 // Timeout neccessary for IE9 704 setTimeout(function () { 705 document.selection.empty(); //取消选中 706 }); 707 } else { 708 window.getSelection().removeAllRanges();//取消选中 709 } 710 } catch (err) { 711 712 } 713 }, 714 715 716 717 _dragStarted: function () { 718 if (rootEl && dragEl) { //如果鼠标按下去的拖拽节点存在和拖拽的根节点存在 719 // Apply effect 720 //为拖拽节点添加一个class名字是'sortable-ghost' 721 _toggleClass(dragEl, this.options.ghostClass, true); 722 //Sortable类赋值给Sortable.active 属性 723 Sortable.active = this; 724 725 // Drag start event 726 727 //开始拖拽 并且会相应onStart 接口函数 728 _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex); 729 } 730 }, 731 732 _emulateDragOver: function () { 733 734 if (touchEvt) { 735 if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) { 736 return; 737 } 738 739 this._lastX = touchEvt.clientX; 740 this._lastY = touchEvt.clientY; 741 742 if (!supportCssPointerEvents) { 743 _css(ghostEl, 'display', 'none'); 744 } 745 746 var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY), 747 parent = target, 748 groupName = ' ' + this.options.group.name + '', 749 i = touchDragOverListeners.length; 750 751 if (parent) { 752 do { 753 if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) { 754 while (i--) { 755 touchDragOverListeners[i]({ 756 clientX: touchEvt.clientX, 757 clientY: touchEvt.clientY, 758 target: target, 759 rootEl: parent 760 }); 761 } 762 763 break; 764 } 765 766 target = parent; // store last element 767 } 768 /* jshint boss:true */ 769 while (parent = parent.parentNode); 770 } 771 772 if (!supportCssPointerEvents) { 773 _css(ghostEl, 'display', ''); 774 } 775 } 776 }, 777 778 /* 779 tapEvt = { 780 target: dragEl, 781 clientX: touch.clientX, 782 clientY: touch.clientY 783 }; 784 */ 785 /*********************************************************************************************** 786 *函数名 :_onTouchMove 787 *函数功能描述 : 触摸移动拖拽动画事件ghostEl,把拖拽移动的xy值给ghostEl节点 788 *函数参数 : viod 789 *函数返回值 : 无 790 *作者 : 791 *函数创建日期 : 792 *函数修改日期 : 793 *修改人 : 794 *修改原因 : 795 *版本 : 796 *历史版本 : 797 ***********************************************************************************************/ 798 _onTouchMove: function (/**TouchEvent*/evt) { 799 //evt 事件对象 800 if (tapEvt) { 801 // only set the status to dragging, when we are actually dragging 802 if (!Sortable.active) { //Sortable.active 不存在则执行_dragStarted函数 设置拖拽动态 803 this._dragStarted(); 804 } 805 806 // as well as creating the ghost element on the document body 807 // 创建一个ghostEl dom节点,并且是克隆拖拽节点的rootEl下面就是id那个dom节点,添加在,并且设置了一些属性,高,宽,top,left,透明度,鼠标样式, 808 this._appendGhost(); 809 810 var touch = evt.touches ? evt.touches[0] : evt, //判断是否是触摸事件还是pc鼠标事件 811 dx = touch.clientX - tapEvt.clientX, //鼠标移动的x位置减去鼠标按下去的位置。 812 dy = touch.clientY - tapEvt.clientY,//鼠标移动的y位置减去鼠标按下去的位置。 813 //3d 特效 x是左右,y是上下,z是放大缩小 设置3d效果 814 translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; 815 816 moved = true; 817 touchEvt = touch; //事件对象 818 819 _css(ghostEl, 'webkitTransform', translate3d); //设置3d效果 820 _css(ghostEl, 'mozTransform', translate3d); //设置3d效果 821 _css(ghostEl, 'msTransform', translate3d) ; //设置3d效果 822 _css(ghostEl, 'transform', translate3d); //设置3d效果 823 824 825 evt.preventDefault(); // 阻止默认事件 826 } 827 }, 828 /*********************************************************************************************** 829 *函数名 :_appendGhost 830 *函数功能描述 : 创建一个ghostEl dom节点,并且是克隆拖拽节点的rootEl下面就是id那个dom节点,添加在,并且设置了一些属性,高,宽,top,left,透明度,鼠标样式, 831 *函数参数 : viod 832 *函数返回值 : 无 833 *作者 : 834 *函数创建日期 : 835 *函数修改日期 : 836 *修改人 : 837 *修改原因 : 838 *版本 : 839 *历史版本 : 840 ***********************************************************************************************/ 841 _appendGhost: function () { 842 843 if (!ghostEl) { // 如果ghostEl 是空的,或者是假,或者是undefined,或者是0,则执行下面程序 844 /*getBoundingClientRect() 845 其实跟 o_dom.getBoundingClientRect().left= o_dom.offsetLeft; 他们值相等 846 这个方法返回一个矩形对象,包含四个属性:left、top、right和bottom。分别表示元素各边与页面上边和左边的距离。 847 */ 848 var rect = dragEl.getBoundingClientRect(), 849 css = _css(dragEl), //返回当前obj 所有的style的属性 850 options = this.options, //this.options 参数 851 ghostRect; //一个空变量 852 853 ghostEl = dragEl.cloneNode(true); //克隆dragEl 当前拖拽的节点 854 //options.ghostClass='sortable-ghost' 855 _toggleClass(ghostEl, options.ghostClass, false); 856 //fallbackClass= 'sortable-fallback 857 _toggleClass(ghostEl, options.fallbackClass, true); 858 859 //给新创建的节点的left和top和该节点的left和top值相等,所以要减去marginTop,marginLeft 860 _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10)); 861 _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10)); 862 863 _css(ghostEl, 'width', rect.width); //宽和高和拖拽节点相同 864 _css(ghostEl, 'height', rect.height); 865 _css(ghostEl, 'opacity', '0.8'); //透明度为0.8 866 _css(ghostEl, 'position', 'fixed'); // 固定定位 867 _css(ghostEl, 'zIndex', '100000'); //层为100000 868 _css(ghostEl, 'pointerEvents', 'none'); //pointer-events:none顾名思意,就是鼠标事件拜拜的意思。元素应用了该CSS属性,链接啊,点击啊什么的都变成了“浮云牌酱油”。 869 //把ghostEl 添加到拖拽的根节点那 870 options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl); 871 872 // Fixing dimensions. 固定尺寸 但是我觉这样写多此一举,因为上面已经设置高宽了,然后再乘以2,再减去一般结果还是一样的 873 ghostRect = ghostEl.getBoundingClientRect(); 874 _css(ghostEl, 'width', rect.width * 2 - ghostRect.width); 875 _css(ghostEl, 'height', rect.height * 2 - ghostRect.height); 876 877 } 878 }, 879 /*********************************************************************************************** 880 *函数名 :_onDragStart 881 *函数功能描述 : 拖拽开始 为document添加触摸事件与鼠标事件 882 *函数参数 : 883 evt: 884 类型:obj, 事件对象 885 useFallback:类型:string, Boolean 值 886 *函数返回值 : 887 *作者 : 888 *函数创建日期 : 889 *函数修改日期 : 890 *修改人 : 891 *修改原因 : 892 *版本 : 893 *历史版本 : 894 ***********************************************************************************************/ 895 896 _onDragStart: function (/**Event*/evt, /**boolean*/useFallback) { 897 //html5拖拽属性。dataTransfer对象有两个主要的方法:getData()方法和setData()方法。 898 var dataTransfer = evt.dataTransfer, 899 options = this.options; 900 901 //解绑文档上面的一些事件 902 this._offUpEvents(); 903 //Object {name: "words", pull: true, put: true} 904 //activeGroup={name: "words", pull: true, put: true} 905 if (activeGroup.pull == 'clone') { //如果 参数是clone 则可以克隆节点而不是拖拽节点过去 906 cloneEl = dragEl.cloneNode(true); //cloneNode(false) 克隆复制节点,参数如果是false则不复制里面的html,true则会复制整个dom包括里面的html 907 //设置cloneEl 节点隐藏 908 _css(cloneEl, 'display', 'none'); 909 //插入加点,在当前拖拽的dom节点前面插入一个节点 910 rootEl.insertBefore(cloneEl, dragEl); 911 } 912 913 if (useFallback) { //如果是触摸则添加触摸事件 914 915 if (useFallback === 'touch') { 916 // Bind touch events 917 //添加触摸移动事件 918 _on(document, 'touchmove', this._onTouchMove); 919 //添加触摸抬起事件 920 _on(document, 'touchend', this._onDrop); 921 //添加触摸划过结束事件 922 _on(document, 'touchcancel', this._onDrop); 923 } else { 924 // Old brwoser 925 //pc 添加鼠标移动事件 926 _on(document, 'mousemove', this._onTouchMove); 927 //pc 添加鼠标抬起事件 928 _on(document, 'mouseup', this._onDrop); 929 } 930 931 this._loopId = setInterval(this._emulateDragOver, 50); 932 } 933 else { 934 //html5拖拽属性。dataTransfer对象有两个主要的方法:getData()方法和setData()方法。 935 if (dataTransfer) { 936 dataTransfer.effectAllowed = 'move';//move :只允许值为”move”的dropEffect。 937 /* 938 setData: function (dataTransfer, dragEl) { dataTransfer.setData('Text', dragEl.textContent);} 939 设置拖拽时候拖拽信息 940 */ 941 options.setData && options.setData.call(this, dataTransfer, dragEl); 942 } 943 944 _on(document, 'drop', this); //添加拖拽结束事件 945 946 setTimeout(this._dragStarted, 0); //pc拖拽事件 947 } 948 }, 949 /*********************************************************************************************** 950 *函数名 :_onDragOver 951 *函数功能描述 : 拖拽元素进进入拖拽区域, 判断拖拽节点与拖拽碰撞的节点,交换他们的dom节点位置,并执行动画。 952 *函数参数 :evt 953 *函数返回值 : 954 *作者 : 955 *函数创建日期 : 956 *函数修改日期 : 957 *修改人 : 958 *修改原因 : 959 *版本 : 960 *历史版本 : 961 ***********************************************************************************************/ 962 _onDragOver: function (/**Event*/evt) { 963 964 var el = this.el, 965 target, 966 dragRect, 967 revert, 968 options = this.options, 969 group = options.group, 970 groupPut = group.put, 971 isOwner = (activeGroup === group), 972 canSort = options.sort; 973 if (evt.preventDefault !== void 0) { 974 evt.preventDefault(); //阻止默认事件 975 !options.dragoverBubble && evt.stopPropagation();//终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播 976 } 977 978 moved = true; 979 //activeGroup={name: "words", pull: true, put: true} 980 981 982 //activeGroup=true 983 //options.disabled=false 984 //isOwner=true 因为isOwner=true 则执行canSort || (revert = !rootEl.contains(dragEl)) 985 //如果父节点包含子节点则返回true ,contains,所以当canSort 是假时候(revert = !rootEl.contains(dragEl) 986 //revert = !rootEl.contains(dragEl) 取反赋值 987 //这里的if需要一个假才能拖拽 988 //(activeGroup.name === group.name) ==true; 989 //(evt.rootEl === void 0 || evt.rootEl === this.el) ==true 990 //所以 该功能是 给设置sort参数提供的 991 if ( 992 activeGroup && 993 !options.disabled && 994 ( 995 isOwner? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list 996 : activeGroup.pull && groupPut && ( 997 (activeGroup.name === group.name) || // by Name 998 (groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array 999 ) 1000 ) && 1001 (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback 1002 ) 1003 { 1004 // Smart auto-scrolling 智能滚动 1005 _autoScroll(evt, options, this.el); 1006 1007 if (_silent) { 1008 return; 1009 } 1010 1011 target = _closest(evt.target, options.draggable, el); //调整拖拽目标节点 1012 1013 1014 dragRect = dragEl.getBoundingClientRect(); //获取dom节点的一个获取left,right ,top,bottmo,值 1015 1016 1017 1018 if (revert) { //revert undefined 1019 _cloneHide(true); //设置克隆的节点隐藏还是显示 1020 1021 if (cloneEl || nextEl) { //如果克隆节点存在或者下一个节点存在 1022 1023 rootEl.insertBefore(dragEl, cloneEl || nextEl);//就把dragEl添加到克隆节点存在或者下一个节点的上面 1024 1025 } 1026 else if (!canSort) { //canSort 默认是true ,是设置是否 判断是否在自己区域拖拽 1027 rootEl.appendChild(dragEl); //canSort 是假添加到根节点 1028 } 1029 return; 1030 } 1031 1032 //el.children.length 如果拖拽根节点没有子节点的时候该为true 1033 //el.children[0] === ghostEl如果根节点的字节点等于镜像节点的时候为真 1034 //el === evt.target 根节点等于目标节点的时候 1035 if ((el.children.length === 0) || (el.children[0] === ghostEl) || 1036 (el === evt.target) && (target = _ghostIsLast(el, evt)) 1037 ) { 1038 1039 if (target) { // 如果_ghostIsLast 返回最后一个节点 1040 if (target.animated) { //判断 target.animated 动画是否在执行,如果在执行那么就不执行下面函数 1041 return; 1042 } 1043 1044 targetRect = target.getBoundingClientRect(); 1045 } 1046 //隐藏克隆节点 1047 _cloneHide(isOwner); 1048 1049 1050 /* 1051 rootEl:拖拽根节点 1052 el:拖拽根节点 1053 dragEl:拖拽节点 1054 dragRect:拖拽几点的Rect 1055 target:目标节点或者是根节点的最后一个子节点,释放鼠标的节点 1056 targetRect:target的Rect 1057 */ 1058 1059 1060 if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) { 1061 if (!dragEl.contains(el)) { // 判断dragEl中没有存在根节点 1062 el.appendChild(dragEl); //就把目拖拽节点添加到根节点那 1063 parentEl = el; // actualization 1064 } 1065 //动画 1066 this._animate(dragRect, dragEl); 1067 target && this._animate(targetRect, target); 1068 } 1069 } 1070 //target 拖拽的目标节点存在 1071 //target.animated动画没有在执行 1072 //target !== dragEl 拖拽的节点不等于目标节点 就是发生了dragenter事件 1073 //target 拖拽的父节点是根节点 rootEl 1074 else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) { 1075 1076 i++; 1077 if (lastEl !== target) { // 拖拽的目标节点就是发生拖拽ondragover时候的节点 不是最后一个子节点的时候 1078 lastEl = target; //将拖拽的目标节点赋值给最后一个节点 1079 lastCSS = _css(target); //获取最后目标节点的css全部属性 1080 lastParentCSS = _css(target.parentNode); //获取根节点的全部css属性 1081 } 1082 1083 1084 1085 ///left|right|inline/.test(str) 匹配str中只要含有left|right|inline 中的任何一个就可以为真 1086 //floating 其实就是判断这里的拖拽节点是否已经有浮动,或者是inline也跟浮动差不多,或者是css3的flex-direction横向对其成一排的那个属性 1087 //isWide:如果目标节点的宽大于拖拽节点的宽 1088 //isLong:如果目标节点的高大于拖拽节点的高 1089 var targetRect = target.getBoundingClientRect(), //目标节点的rect 1090 width = targetRect.right - targetRect.left, //目标节点的宽 1091 height = targetRect.bottom - targetRect.top, //目标节点的高 1092 floating = /left|right|inline|inlineBlock/.test(lastCSS.cssFloat + lastCSS.display) 1093 || (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0), 1094 isWide = (target.offsetWidth > dragEl.offsetWidth), //目标节点宽大于拖拽节点 1095 isLong = (target.offsetHeight > dragEl.offsetHeight),//目标节点高大于拖拽节点 1096 //halfway 如果floating 浮动,inline,横向对齐 了就判断此时鼠标是在target中间的左边还是右边,右边则为true,否则false 1097 //halfway 如果floating 没有浮动,inline,横向对齐 就判断此时鼠标是在target中间的上面边还是下面,下边则为true,否则false 1098 halfway = (floating ? 1099 (evt.clientX - targetRect.left) / width : 1100 (evt.clientY - targetRect.top) / height 1101 ) 1102 > 0.5, 1103 // 目标节点的下一个节点。 1104 nextSibling = target.nextElementSibling, 1105 /* 1106 rootEl:拖拽根节点 1107 el:拖拽根节点 1108 dragEl:拖拽节点 1109 dragRect:拖拽几点的Rect 1110 target:拖拽节点或者是根节点的最后一个子节点 1111 targetRect:target的Rect 1112 */ 1113 1114 moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect), //空undefined 1115 after // after true 往下拖拽, false 往上拖拽 1116 ; 1117 1118 if (moveVector !== false) { 1119 _silent = true; 1120 setTimeout(_unsilent, 30); //30毫秒设置为假 1121 1122 _cloneHide(isOwner); //隐藏克隆节点 1123 1124 if (moveVector === 1 || moveVector === -1) {//空undefined 1125 after = (moveVector === 1); 1126 } 1127 else if (floating) { //如果浮动 1128 1129 var elTop = dragEl.offsetTop, //拖拽接点的top 1130 tgTop = target.offsetTop; //目标节点top 1131 1132 if (elTop === tgTop) { // //拖拽几点的top===目标节点top 说明他们是在同一列中 1133 1134 //target.previousElementSibling 如果目标节点的上一个节点是拖拽节点,这里就是他们上下节点互换位置,!isWide 目标节点宽小于拖拽节点 1135 //或者 halfway 为真并且 isWide 目标节点宽大于拖拽节点 1136 // 1137 1138 1139 } else { 1140 1141 //目标节点top大于>拖拽接点的top 1142 after = tgTop > elTop; // after true 往下拖拽 false 往上拖拽 1143 1144 } 1145 } else { 1146 //没有浮动的时候 1147 // 目标节点的下一个节点。 1148 // console.log('nextSibling !== dragEl'+(nextSibling !== dragEl)); //往下拖拽 1149 //dom节点是按照拖拽完之后排序做判断 1150 //(nextSibling !== dragEl) 不是往上拖拽的时候 则为真的时候 如果目标节点的高大于拖拽节点的高 1151 //halfway 为真的时候。如果目标节点的高大于拖拽节点的高after 为真 1152 after = (nextSibling !== dragEl) && !isLong || halfway && isLong; 1153 } 1154 1155 if (!dragEl.contains(el)) { 1156 1157 if (after && !nextSibling) { // 1158 1159 el.appendChild(dragEl); //如果此时 目标的的下一个节点不存在那么直接把拖拽节点添加在后面即可 1160 } else { 1161 //判断拖拽节点添加到哪个位置after真时候是往下拖拽则把拖拽节点添加到target下面。 1162 //判断拖拽节点添加到哪个位置after假时候是往下拖拽则把拖拽节点添加到target上面。 1163 target.parentNode.insertBefore(dragEl, after ? nextSibling : target); 1164 } 1165 } 1166 1167 parentEl = dragEl.parentNode; // actualization 1168 //交换位置前的drgRect, 交换后位置的拖拽节点dragEl 1169 //交换位置前的目标节点targetRect, 交换后位置的目标节点dragEl 1170 1171 //执行动画。css3动画 1172 this._animate(dragRect, dragEl); //执行css3动画其实只是一种掩饰而已,真正的核心是他们交换dom节点的位置 1173 //执行动画。css3动画 1174 this._animate(targetRect, target); 1175 } 1176 } 1177 } 1178 }, 1179 1180 /*********************************************************************************************** 1181 *函数名 :_animate 1182 *函数功能描述 : 动画效果,执行css3动画 1183 *函数参数 : 1184 prevRect:obj,初始动画的坐标, 1185 target:obj,target 其实最重要是获取交换dom节点后的坐标 1186 *函数返回值 : 1187 *作者 : 1188 *函数创建日期 : 1189 *函数修改日期 : 1190 *修改人 : 1191 *修改原因 : 1192 *版本 : 1193 *历史版本 : 1194 ***********************************************************************************************/ 1195 _animate: function (prevRect, target) { 1196 //每次当目标节点与拖拽节点交替的时候就调用次改函数 1197 //i++; 1198 1199 1200 //prevRect: obj.getBoundingClientRect 1201 //target:obj 1202 var ms = this.options.animation; //动画延迟 1203 1204 if (ms) { 1205 var currentRect = target.getBoundingClientRect(); 1206 1207 //debugger; 1208 _css(target, 'transition', 'none'); 1209 _css(target, 'transform', 'translate3d(' 1210 + (prevRect.left - currentRect.left) + 'px,' 1211 + (prevRect.top - currentRect.top) + 'px,0)' 1212 ); 1213 1214 target.offsetWidth; // repaint 1215 1216 _css(target, 'transition', 'all ' + ms + 'ms'); 1217 _css(target, 'transform', 'translate3d(0,0,0)'); 1218 1219 clearTimeout(target.animated); 1220 target.animated = setTimeout(function () { 1221 _css(target, 'transition', ''); 1222 _css(target, 'transform', ''); 1223 target.animated = false; 1224 }, ms); 1225 } 1226 }, 1227 /*********************************************************************************************** 1228 *函数名 :_offUpEvents 1229 *函数功能描述 : 解绑文档上的拖拽函数 1230 *函数参数 : viod 1231 *函数返回值 :viod 1232 *作者 : 1233 *函数创建日期 : 1234 *函数修改日期 : 1235 *修改人 : 1236 *修改原因 : 1237 *版本 : 1238 *历史版本 : 1239 ***********************************************************************************************/ 1240 1241 _offUpEvents: function () { 1242 var ownerDocument = this.el.ownerDocument; 1243 1244 _off(document, 'touchmove', this._onTouchMove); //当文档上面document 发生触摸移动事件的时候解绑 _onTouchMove事件 1245 _off(ownerDocument, 'mouseup', this._onDrop); //当文档上面document 发生鼠标抬起事件的时候解绑 _onTouchMove事件 1246 _off(ownerDocument, 'touchend', this._onDrop); //当文档上面document 发生触摸结束事件的时候解绑 _onTouchMove事件 1247 1248 //当一些更高级别的事件发生的时候(如电话接入或者弹出信息)会取消当前的touch操作,即触发ontouchcancel。一般会在ontouchcancel时暂停游戏、存档等操作。 1249 1250 _off(ownerDocument, 'touchcancel', this._onDrop); //当文档发生手指划过结束的时候解绑_onDrop 事件 1251 }, 1252 //Drop被拖拽的元素在目标元素上同时鼠标放开触发的事件,此事件作用在目标元素上 1253 1254 /*********************************************************************************************** 1255 *函数名 :init 1256 *函数功能描述 : 初始化作用 1257 *函数参数 : 1258 *函数返回值 : 1259 *作者 : 1260 *函数创建日期 : 1261 *函数修改日期 : 1262 *修改人 : 1263 *修改原因 : 1264 *版本 : 1265 *历史版本 : 1266 ***********************************************************************************************/ 1267 _onDrop: function (/**Event*/evt) { 1268 //evt 事件对象 1269 1270 var el = this.el, //拖拽的根节点 1271 options = this.options; //参数类 1272 1273 clearInterval(this._loopId); //清除_loopId 定时器 1274 clearInterval(autoScroll.pid);//清除pid 定时器 1275 clearTimeout(this._dragStartTimer);//清除_dragStartTimer 定时器 1276 1277 // Unbind events 1278 _off(document, 'mousemove', this._onTouchMove); //解除文档上面的鼠标移动事件函数为_onTouchMove 1279 /* 1280 1281 DataTransfer 对象:退拽对象用来传递的媒介,使用一般为Event.dataTransfer。 1282 draggable 属性:就是标签元素要设置draggable=true,否则不会有效果,例如: 1283 1284 <div title="拖拽我" draggable="true">列表1</div> 1285 1286 ondragstart 事件:当拖拽元素开始被拖拽的时候触发的事件,此事件作用在被拖曳元素上 1287 ondragenter 事件:当拖曳元素进入目标元素的时候触发的事件,此事件作用在目标元素上 1288 ondragover 事件:拖拽元素在目标元素上移动的时候触发的事件,此事件作用在目标元素上 1289 ondrop 事件:被拖拽的元素在目标元素上同时鼠标放开触发的事件,此事件作用在目标元素上 1290 ondragend 事件:当拖拽完成后触发的事件,此事件作用在被拖曳元素上 1291 Event.preventDefault() 方法:阻止默认的些事件方法等执行。在ondragover中一定要执行preventDefault(),否则ondrop事件不会被触发。另外,如果是从其他应用软件或是文件中拖东西进来,尤其是图片的时候,默认的动作是显示这个图片或是相关信息,并不是真的执行drop。此时需要用用document的ondragover事件把它直接干掉。 1292 Event.effectAllowed 属性:就是拖拽的效果。 1293 如果nativeDraggable是true 那么 1294 */ 1295 if (this.nativeDraggable) { 1296 _off(document, 'drop', this); //解绑drop 事件 函数是handleEvent 1297 _off(el, 'dragstart', this._onDragStart); //解绑html5的拖拽dragstart事件 函数是 _onDragStart 1298 } 1299 //解绑文档上面的一些事件 1300 this._offUpEvents(); 1301 1302 if (evt) { 1303 if (moved) { 1304 evt.preventDefault(); //阻止默认事件 1305 !options.dropBubble && evt.stopPropagation(); //阻止事件冒泡 1306 } 1307 //ghostEl 在736行时候才会创建该节点,所以在736行调用_onDrop函数的时候都是为空 1308 //如果拖拽的镜像对象存在那么他就添加在拖拽的根节点 1309 ghostEl && ghostEl.parentNode.removeChild(ghostEl); 1310 1311 1312 if (dragEl) { 1313 if (this.nativeDraggable) { 1314 //如果拖拽节点存在了 就解绑this 的 handleEvent 事件 1315 _off(dragEl, 'dragend', this); 1316 } 1317 //禁用拖拽html5 属性 1318 _disableDraggable(dragEl); 1319 1320 // Remove class's //删除css 1321 _toggleClass(dragEl, this.options.ghostClass, false); 1322 _toggleClass(dragEl, this.options.chosenClass, false); 1323 1324 if (rootEl !== parentEl) { //如果从一个列表拖拽到另一个列表的时候 1325 //返回当前的索引 1326 newIndex = _index(dragEl, options.draggable); 1327 1328 if (newIndex >= 0) { //如果当前的索引大于0 1329 // drag from one list and drop into another //从类表中拖拽到另一个列表 1330 //事件接口 1331 _dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex); //开始拖拽函数创建与触发 1332 1333 _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);//开始拖拽函数创建与触发 1334 1335 // Add event 1336 _dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex);//添加节点拖拽函数创建与触发 1337 1338 // Remove event//删除节点拖拽函数创建与触发 1339 _dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex); 1340 } 1341 1342 } 1343 else { //同一个列表中 1344 // Remove clone 1345 cloneEl && cloneEl.parentNode.removeChild(cloneEl); 1346 1347 if (dragEl.nextSibling !== nextEl) { 1348 // Get the index of the dragged element within its parent 1349 newIndex = _index(dragEl, options.draggable); 1350 1351 if (newIndex >= 0) { 1352 1353 1354 // drag & drop within the same list //update拖拽更新新数据 1355 _dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex); 1356 _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex); 1357 } 1358 } 1359 } 1360 1361 if (Sortable.active) { //Sortable.active 存在说明已经拖拽开始了 1362 /* jshint eqnull:true */ 1363 if (newIndex == null || newIndex === -1) {//newIndex 这个条件成立的时候是拖拽第一个节点并且没有更换拖拽位置 1364 newIndex = oldIndex; 1365 } 1366 //拖拽结束 1367 _dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex); 1368 1369 // Save sorting //保存排序 1370 this.save(); 1371 } 1372 } 1373 1374 } 1375 //重新初始化参数 1376 this._nulling(); 1377 }, 1378 /*********************************************************************************************** 1379 *函数名 :_nulling 1380 *函数功能描述 : 初始化拖拽的数据 1381 *函数参数 : 1382 *函数返回值 : 1383 *作者 : 1384 *函数创建日期 : 1385 *函数修改日期 : 1386 *修改人 : 1387 *修改原因 : 1388 *版本 : 1389 *历史版本 : 1390 ***********************************************************************************************/ 1391 _nulling: function () { 1392 if (Sortable.active === this) { 1393 rootEl = //鼠标按下去拖拽节点的根节点 1394 dragEl = //鼠标按下去拖拽节点 1395 parentEl = //拖拽的父节点 鼠标拖拽 发生ondragover 事件 拖拽节点放到目标节点的时候发生事件 的根节点 父节点,也有可能是鼠标按下去拖拽的根节点 1396 ghostEl = // 拖拽镜像 1397 nextEl = //下一个节点 1398 cloneEl = //拖拽克隆节点 1399 1400 scrollEl = //滚动节点 1401 scrollParentEl = //滚动的父节点 1402 1403 tapEvt = //tapEvt 触摸对象包括x与y轴与拖拽当前节点 1404 touchEvt = //触摸事件对象 1405 1406 moved = //布尔值 1407 newIndex = //拖拽的现在索引 1408 1409 lastEl = //拖拽根节点中的最后一个子节点 1410 lastCSS = //拖拽根节点中的最后一个子节点class 1411 1412 activeGroup = //options.group 1413 Sortable.active = null; 1414 1415 } 1416 }, 1417 /*********************************************************************************************** 1418 *函数名 :handleEvent 1419 *函数功能描述 : 为事件绑定this的时候提供该事件,判断是否在拖拽还是拖拽结束,调用对应的函数 1420 *函数参数 : 1421 evt: 1422 类型:object,事件类型 拖拽的事件类型 1423 *函数返回值 : 1424 *作者 : 1425 *函数创建日期 : 1426 *函数修改日期 : 1427 *修改人 : 1428 *修改原因 : 1429 *版本 : 1430 *历史版本 : 1431 ***********************************************************************************************/ 1432 handleEvent: function (/**Event*/evt) {//handleEvent 是该事件绑定这个对象的时候则发生这里的事件,则事件绑定给Sortable 的时候则发生这里的事件 1433 var type = evt.type; 1434 //dragover 在拖拽区域移动拖拽时候发生事件相当于move 1435 //dragenter 元素放入到拖拽的区域中相当于 over 1436 if (type === 'dragover' || type === 'dragenter') { //事件正在拖拽的时候 1437 1438 1439 if (dragEl) { //在300行的时候调用dragover与dragenter事件,这个时候dragEl是处于声明而已但是没有赋值所以是undefined, 如果dragEl 存在则是真正拖拽的时候,dragEl是拖拽镜像 1440 this._onDragOver(evt); 1441 _globalDragOver(evt); 1442 } 1443 } 1444 else if (type === 'drop' || type === 'dragend') { //拖拽事件结束的时候 1445 this._onDrop(evt); 1446 } 1447 }, 1448 1449 1450 /** 1451 * Serializes the item into an array of string. 1452 * @returns {String[]} 1453 */ 1454 /*********************************************************************************************** 1455 *函数名 :toArray 1456 1457 *函数功能描述 : 获取dom节点的 data-id 的属性 如果没有则 会调用_generateId函数生成唯一表示符 1458 *函数参数 : viod 1459 *函数返回值 : 类型:array 生成唯一标识符的id数组 1460 *作者 : 1461 *函数创建日期 : 1462 *函数修改日期 : 1463 *修改人 : 1464 *修改原因 : 1465 *版本 : 1466 *历史版本 : 1467 ***********************************************************************************************/ 1468 toArray: function () { 1469 var order = [], 1470 el, 1471 children = this.el.children, //获取所有子节点 1472 i = 0, 1473 n = children.length, //获取子节点的长度 1474 options = this.options; 1475 1476 1477 for (; i < n; i++) { 1478 el = children[i]; 1479 if (_closest(el, options.draggable, this.el)) { 1480 //getAttribute获取 data-id 的属性 1481 //order.push 如果没有data-id 属性获取不到值,则会调用_generateId函数生成唯一表示符 1482 order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); 1483 } 1484 } 1485 //返回唯一标识符id 数组 类型 1486 return order; 1487 }, 1488 1489 1490 /** 1491 * Sorts the elements according to the array. 1492 * @param {String[]} order order of the items 1493 */ 1494 1495 /*********************************************************************************************** 1496 *函数名 :sort 1497 1498 *函数功能描述 : 删除含有这个id的子节点 删除他 让他重新排序, 从栈底部插入数据 1499 *函数参数 : order: 1500 类型:array, 数组id 1501 1502 *函数返回值 : void 1503 *作者 : 1504 *函数创建日期 : 1505 *函数修改日期 : 1506 *修改人 : 1507 *修改原因 : 1508 *版本 : 1509 *历史版本 : 1510 ***********************************************************************************************/ 1511 sort: function (order) { 1512 debugger; 1513 //order 数组 1514 var items = {}, 1515 rootEl = this.el; //鼠标开始拖拽的根节点 1516 1517 1518 this.toArray().forEach(function (id, i) { //遍历this.toArray() 数组中的id 1519 var el = rootEl.children[i]; 1520 1521 if (_closest(el, this.options.draggable, rootEl)) { 1522 items[id] = el; //遍历数组中的id 赋值给一个对象 1523 } 1524 }, this); 1525 1526 order.forEach(function (id) { 1527 if (items[id]) { 1528 rootEl.removeChild(items[id]); //删除含有这个id的子节点 删除他 让他重新排序, 1529 rootEl.appendChild(items[id]);//删除含有这个id的子节点 删除他 让他重新排序, 从栈底部插入数据 1530 } 1531 }); 1532 }, 1533 1534 1535 /** 1536 * Save the current sorting 1537 保存排序 1538 */ 1539 save: function () { 1540 var store = this.options.store; 1541 store && store.set(this); 1542 }, 1543 1544 1545 /** 1546 * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. 1547 * @param {HTMLElement} el 1548 * @param {String} [selector] default: `options.draggable` 1549 * @returns {HTMLElement|null} 1550 */ 1551 /*********************************************************************************************** 1552 *函数名 :_closest 1553 *函数功能描述 : 用来调节节点,匹配节点。匹配calss。 匹配触发dom该函数的dom节点中的tag或者class,selector参数可以是tag或者class或者>*, 1554 如果是>* 并且当前的父节点和ctx 参数相同 则不需要匹配直接返回el,如果是tag或者class则匹配。 1555 *函数参数 : 1556 el: 1557 类型:obj,拖拽节点dom 1558 selector: 1559 类型:字符串,如果selector是'li' : '>*'则返回是改节点dom,还有如果selector是和当前拖拽节点的name相同则也返回改节点dom,还有匹配触发该函数的el中的class是否是和参数中selector相同,相同则返回true,否则返回null 1560 1561 *函数返回值 :dom和null 1562 *作者 : 1563 *函数创建日期 : 1564 *函数修改日期 : 1565 *修改人 : 1566 *修改原因 : 1567 *版本 : 1568 *历史版本 : 1569 ***********************************************************************************************/ 1570 closest: function (el, selector) { 1571 1572 return _closest(el, selector || this.options.draggable, this.el); 1573 }, 1574 1575 1576 /** 1577 * Set/get option 1578 * @param {string} name 1579 * @param {*} [value] 1580 * @returns {*} 1581 */ 1582 /*********************************************************************************************** 1583 *函数名 :option 1584 *函数功能描述 : 获取option对象中的某个参数,或者设置option对象中的某个参数 1585 1586 *函数参数 :name: 1587 类型:string, option的key, 1588 1589 value:类型:string, 设置option的值 1590 1591 *函数返回值 : viod 1592 *作者 : 1593 *函数创建日期 : 1594 *函数修改日期 : 1595 *修改人 : 1596 *修改原因 : 1597 *版本 : 1598 *历史版本 : 1599 ***********************************************************************************************/ 1600 1601 option: function (name, value) { 1602 var options = this.options; 1603 1604 if (value === void 0) { //当没有传递第二个参数的时候 则返回该options参数的某个值 1605 return options[name]; 1606 } else { 1607 options[name] = value;// 设置options参数的某个值 1608 1609 if (name === 'group') { // 如果name 是group 则在后面添加['pull', 'put']属性 1610 _prepareGroup(options); 1611 } 1612 } 1613 }, 1614 1615 1616 /** 1617 * Destroy 破坏 1618 */ 1619 /*********************************************************************************************** 1620 *函数名 :destroy 1621 *函数功能描述 : 清空拖拽事件,和情况拖拽列表dom节点,销毁拖拽 。 1622 1623 *函数参数 viod 1624 *函数返回值 : viod 1625 *作者 : 1626 *函数创建日期 : 1627 *函数修改日期 : 1628 *修改人 : 1629 *修改原因 : 1630 *版本 : 1631 *历史版本 : 1632 ***********************************************************************************************/ 1633 destroy: function () { 1634 var el = this.el; 1635 1636 el[expando] = null; //把每一个时间戳的Sortable 的对象置为空 1637 1638 _off(el, 'mousedown', this._onTapStart); // 解绑拖拽类表中的mousedown事件_onTapStart函数 1639 _off(el, 'touchstart', this._onTapStart); // 解绑拖拽类表中的touchstart事件_onTapStart函数 1640 1641 if (this.nativeDraggable) { 1642 _off(el, 'dragover', this); // 解绑拖拽类表中的dragover事件handleEvent函数 1643 _off(el, 'dragenter', this);// 解绑拖拽类表中的dragover事件handleEvent函数 1644 } 1645 1646 // Remove draggable attributes 1647 //把页面上所有含有draggable 属性的dom节点 全部删除该属性,让它不能拖拽 1648 Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { 1649 el.removeAttribute('draggable'); 1650 }); 1651 //删除touchDragOverListeners 触摸列表事件_onDragOver 1652 touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1); 1653 1654 this._onDrop(); //重新初始化 1655 1656 this.el = el = null; //把拖拽列表的dom节点清空 1657 } 1658 }; 1659 1660 /*********************************************************************************************** 1661 *函数名 :_cloneHide 1662 *函数功能描述 : 设置克隆的节点隐藏显示,是否添加到页面 1663 *函数参数 : 1664 state: 1665 类型:Boolean 真,假 1666 *函数返回值 : viod 1667 *作者 : 1668 *函数创建日期 : 1669 *函数修改日期 : 1670 *修改人 : 1671 *修改原因 : 1672 *版本 : 1673 *历史版本 : 1674 ***********************************************************************************************/ 1675 function _cloneHide(state) { 1676 //state布尔值 1677 //cloneEl 克隆的节点 1678 //state 状态 1679 if (cloneEl && (cloneEl.state !== state)) {//如果cloneEl 存在,并且cloneEl.state 不等于state 的时候 1680 _css(cloneEl, 'display', state ? 'none' : ''); //state 为真的时候把它隐藏,为假的时候显示 1681 !state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);//state为假的时候cloneEl.state 为真则把cloneEl添加在dragEl前面 1682 cloneEl.state = state; // 1683 } 1684 } 1685 1686 /*********************************************************************************************** 1687 *函数名 :_closest 1688 *函数功能描述 : 匹配触发dom该函数的dom节点中的tag或者class,selector参数可以是tag或者class或者>*, 1689 如果是>* 并且当前的父节点和ctx 参数相同 则不需要匹配直接返回el,如果是tag或者class则匹配 1690 *函数参数 : 1691 el: 1692 类型:obj,拖拽节点dom 1693 selector: 1694 类型:字符串,如果selector是'li' : '>*'则返回是改节点dom,还有如果selector是和当前拖拽节点的name相同则也返回改节点dom,还有匹配触发该函数的el中的class是否是和参数中selector相同,相同则返回true,否则返回null 1695 ctx:ctx用来匹配当前selector的父节点是否等于ctx节点 1696 *函数返回值 :dom和null 1697 *作者 : 1698 *函数创建日期 : 1699 *函数修改日期 : 1700 *修改人 : 1701 *修改原因 : 1702 *版本 : 1703 *历史版本 : 1704 ***********************************************************************************************/ 1705 function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) { 1706 /* el 目标节点 1707 selector /[uo]l/i.test(el.nodeName) ? 'li' : '>*', 1708 ctx 父亲节点*/ 1709 if (el) { 1710 //如果没有传父亲节点过来则是整个文档 1711 ctx = ctx || document; 1712 1713 do { 1714 1715 if ( 1716 //如果不是li而是其他节点 并且已经搜索完了直到是父亲节点的时候就返回true 1717 //或者走_matches _matches 需要true 1718 //selector === '>*' && el.parentNode === ctx 如果 selector === '>*' 表示父节点不是ol或ul 1719 (selector === '>*' && el.parentNode === ctx) 1720 || _matches(el, selector) 1721 ) { 1722 1723 return el; 1724 } 1725 } 1726 //el !== ctx 如果目标节点不是当前的父节点,则会一直找上一层的父节点知道当他找到当前的根父节点程序则停止。 1727 while (el !== ctx && (el = el.parentNode)); //如果条件不成立一直需找上一层父节点 1728 } 1729 1730 return null; 1731 } 1732 1733 /* 1734 *函数名 :_globalDragOver 1735 *函数功能描述 :设置拖动的元素移动到放置目标。 1736 *参数说明: 1737 evt:类型obj事件对象 1738 返回值:void 1739 */ 1740 function _globalDragOver(/**Event*/evt) { 1741 /* 1742 1.effectAllowed属性表示允许拖放元素的哪种dropEffect。什么是dropEffect?也是dataTransfer 的一种属性。 1743 dropEffect属性可以知道被拖动的元素能够执行哪种放置行为(当拖到目的地时)。这个属性有下列4个可能的值。 1744 “none”:不能把拖动的元素放在这里。这是除文本框之外所有元素的默认值。 1745 “move”:应该把拖动的元素移动到放置目标。 1746 “copy”:应该把拖动的元素复制到放置目标。 1747 “link”:表示放置目标会打开拖动的元素(但拖动的元素必须是一个链接,有URL)。 1748 2. dt.effectAllowed = 'all':即说被拖动元素在放置到目的地时,可以上面的任意一种效果来处理。 1749 3. 必须在ondraggstart事件处理程序中设置effectAllowed属性。 1750 */ 1751 if (evt.dataTransfer) { 1752 1753 evt.dataTransfer.dropEffect = 'move'; //“move”:应该把拖动的元素移动到放置目标。 1754 } 1755 evt.preventDefault(); 1756 } 1757 1758 /* 1759 *函数名 :_on 1760 *函数功能描述 : 事件绑定 1761 *参数说明: 1762 el:类型DOM节点, 1763 name:类型string,事件类型 1764 fn:类型:function,需要绑定的函数 1765 */ 1766 function _on(el, event, fn) { 1767 el.addEventListener(event, fn, false); 1768 } 1769 1770 /* 1771 *函数名 :_toggleClass 1772 *函数功能描述 : 添加删除calss 1773 *参数说明: 1774 el:类型DOM节点, 需要添加和删除的dom节点, 1775 name:类型string,需要添加删除class字符串的 1776 fn:类型:布尔值,如果是真则删除name的class名称否则添加 1777 */ 1778 function _off(el, event, fn) { 1779 el.removeEventListener(event, fn, false); 1780 } 1781 1782 /* 1783 *函数名 :_toggleClass 1784 *函数功能描述 : 添加删除calss 1785 *参数说明: 1786 el:类型DOM节点, 需要添加和删除的dom节点, 1787 name:类型string,需要添加删除class字符串的 1788 state:类型:布尔值,如果是真则删除name的class名称否则添加 1789 */ 1790 function _toggleClass(el, name, state) { 1791 //console.log(el.classList); //获取dom节点clss的个数并且以数组形式存储起来 1792 //el.classList 判断当前的拖拽节点又没有class 如果有 1793 if (el) { 1794 if (el.classList) { 1795 //如果state 是真则在改节点上面添加name class 否则删除name class 1796 el.classList[state ? 'add' : 'remove'](name); 1797 } 1798 else { 1799 //replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。 1800 // RSPACE = /\s+/g, 匹配1-n个空格 全局匹配 1801 // (' ' + el.className + ' ').replace(RSPACE, ' ') 去除class 中的所有空格 并且只保留一个空格 每一个class中 1802 //className = (' ' + el.className + ' ').replace(RSPACE, '').replace(' ' + name + ' ', ' '); 剔除 name class 1803 var className = (' ' + el.className + ' ').replace(RSPACE, '').replace(' ' + name + ' ', ' '); 1804 1805 //如果state 是真则在改节点上面添加name class 否则删除name class 1806 el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' '); 1807 } 1808 } 1809 1810 1811 } 1812 /* 1813 *函数名 :设置 样式 与 获取dom节点的style属性 1814 *函数功能描述 : 添加删除calss,获取dom节点全部css属性,如果是一个参数的时候将返回该dom节点的全部css属性,如果是两个参数的时候该返回该css的第二个参数的值,如果是三个参数的话将设置css样式 1815 *参数说明: 1816 el:类型DOM节点, 需要添加和删除的dom节点, 1817 prop:类型string,需要添加删除class字符串的那么 1818 val:类型:布尔值,如果是真则删除name的class名称否则添加 1819 */ 1820 1821 function _css(el, prop, val) { 1822 var style = el && el.style; //如果el存在并且他是dom节点style=el.style 1823 1824 if (style) { 1825 if (val === void 0) { //如果val===undefined 1826 //var win = document.defaultView; 返回当前文档上面的所有对象 1827 //document.defaultView.getComputedStyle 返回当前文档上的对象的样式方法 1828 // 1829 if (document.defaultView && document.defaultView.getComputedStyle) { 1830 val = document.defaultView.getComputedStyle(el, ''); //获取到改dom节点的全部style属性,并且带有值 1831 } 1832 else if (el.currentStyle) { 1833 val = el.currentStyle; //getComputedStyle与currentStyle获取样式(style/class) 1834 } 1835 1836 return prop === void 0 ? val : val[prop]; //如果prop为undefined则返回style全部属性 1837 } 1838 else { 1839 if (!(prop in style)) { //如果prop中这个属性中style中没有 1840 prop = '-webkit-' + prop; //则在这个prop前面加 '-webkit-' 字符串 1841 } 1842 1843 style[prop] = val + (typeof val === 'string' ? '' : 'px'); // 如果val类型是数子则添加px 1844 } 1845 } 1846 } 1847 1848 1849 1850 1851 /*********************************************************************************************** 1852 *函数名 :_find 1853 *函数功能描述 : 获取拖拽节点下面的所有a和img标签,并且设置他们禁止拖拽行为 1854 *函数参数 : 1855 ctx: 1856 类型:dom-obj 拖拽的节点 1857 tagName: 1858 类型:string,ctx.getElementsByTagName(tagName) 1859 获取拖拽节点下面的所有a和img 1860 *函数返回值 : a和img的dom集合 1861 *作者 : 1862 *函数创建日期 : 1863 *函数修改日期 : 1864 *修改人 : 1865 *修改原因 : 1866 *版本 : 1867 *历史版本 : 1868 ***********************************************************************************************/ 1869 function _find(ctx, tagName, iterator) { 1870 /* 1871 ctx 拖拽的节点 1872 ctx.getElementsByTagName(tagName) 获取拖拽节点下面的所有a和img 1873 _disableDraggable 是一个函数 1874 iterator=_disableDraggable 1875 _find(ctx, tagName, iterator) 该函数功能是把当前拖拽对象的a和img节点的html5 拖拽属性改为false 1876 */ 1877 1878 if (ctx) { 1879 var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length; 1880 1881 if (iterator) { 1882 for (; i < n; i++) { 1883 iterator(list[i], i); 1884 } 1885 } 1886 1887 return list; 1888 } 1889 1890 return []; 1891 } 1892 1893 /*********************************************************************************************** 1894 *函数名 :_dispatchEvent 1895 *函数功能描述 : 创建一个事件,事件参数主要由name 提供,并且触发该事件,其实就是模拟事件并且触发该事件 1896 *函数参数 : 1897 sortable: 1898 类型: obj sortable 1899 rootEl: 1900 类型: dom-obj 鼠标按下去拖拽节点的根节点 1901 1902 name: 类型: string 需要创建的事件 1903 1904 targetEl:dom-obj 鼠标按下去拖拽节点,触屏到发生事件ondragover的节点的根节点,就是目标节点的根节点。但是如果是start事件的时候传进来的改参数就是鼠标按下去拖拽节点的根节点 1905 1906 fromEl: 1907 类型: dom-obj 鼠标按下去拖拽节点的根节点 参数和第二个一样,为什么重写参数进来呢,可能是为了兼容这样的的吧 1908 1909 startIndex: 1910 类型: number 鼠标按下去拖拽节点的索引 1911 newIndex: 1912 类型: number 1913 1914 * *函数返回值 : 1915 *作者 : 1916 *函数创建日期 : 1917 *函数修改日期 : 1918 *修改人 : 1919 *修改原因 : 1920 *版本 : 1921 *历史版本 : 1922 ***********************************************************************************************/ 1923 function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) { 1924 1925 var evt = document.createEvent('Event'), //创建一个事件 1926 options = (sortable || rootEl[expando]).options, //获取options 参数 1927 //name.charAt(0) 获取name的第一个字符串 1928 //toUpperCase() 变成大写 1929 //name.substr(1) 提取从索引为1下标到字符串的结束位置的字符串 1930 //onName 将获得 on+首个字母大写+name从第一个下标获取到的字符串 1931 onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); 1932 1933 evt.initEvent(name, true, true); //自定义一个事件 1934 1935 evt.to = rootEl; //在触发该事件发生evt的时候,将evt添加多一个to属性,值为rootEl 1936 evt.from = fromEl || rootEl; //在触发该事件发生evt的时候,将evt添加多一个to属性,值为rootEl 1937 evt.item = targetEl || rootEl; //在触发该事件发生evt的时候,将evt添加多一个to属性,值为rootEl 1938 evt.clone = cloneEl; //在触发该事件发生evt的时候,将evt添加多一个to属性,值为rootEl 1939 1940 evt.oldIndex = startIndex; //开始拖拽节点 1941 evt.newIndex = newIndex; //现在节点 1942 //触发该事件,并且是在rootEl 节点上面 。触发事件接口就这这里了。onAdd: onUpdate: onRemove:onStart:onSort:onEnd: 1943 1944 rootEl.dispatchEvent(evt); 1945 1946 if (options[onName]) { 1947 options[onName].call(sortable, evt); 1948 } 1949 } 1950 1951 /*********************************************************************************************** 1952 *函数名 :_onMove 1953 *函数功能描述 : 表格分页数据 1954 *函数参数 : 1955 fromEl: 1956 类型:obj,拖拽的根节点 1957 toEl: 1958 类型:obj,拖拽的根节点 1959 dragEl: 1960 类型:obj,拖拽的节点 1961 dragRect: 1962 类型:obj,拖拽的节点rect 1963 targetEl: 1964 类型:obj,目标节点 ondragover 发生事件的节点 1965 targetRect: 1966 类型:obj,目标节点rect 1967 *函数返回值 : retVal 1968 *作者 : 1969 *函数创建日期 : 1970 *函数修改日期 : 1971 *修改人 : 1972 *修改原因 : 1973 *版本 : 1974 *历史版本 : 1975 ***********************************************************************************************/ 1976 function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) { 1977 1978 var evt, 1979 //sortable 类 //fromEl 是根节点 1980 sortable = fromEl[expando], //expando = 'Sortable' + (new Date).getTime(), //字符串Sortable+时间戳 el[expando] = this; //把 Sortable 类放在HTMLDOM节点的expando属性中 1981 1982 onMoveFn = sortable.options.onMove, //空undefined 1983 retVal; 1984 1985 1986 evt = document.createEvent('Event'); //创建一个事件对象 1987 evt.initEvent('move', true, true); //添加移动事件 1988 1989 evt.to = toEl; //把根节点赋值给to的属性上面 1990 evt.from = fromEl; //把根节点赋值给from的属性上面 1991 evt.dragged = dragEl; //把现在拖拽节点赋值给to的属性上面 1992 evt.draggedRect = dragRect; //把现 在拖拽节点的rect 1993 evt.related = targetEl || toEl; //判断目标节点或者是toel节点谁存在谁赋值给related 不过targetEl 有限权限高 1994 evt.relatedRect = targetRect || toEl.getBoundingClientRect(); //判断targetEl的节点的rect或者是toel节点rect谁存在谁赋值给related 不过targetEl的rect 有限权限高 1995 1996 fromEl.dispatchEvent(evt); //在根节点触发移动事件 1997 1998 if (onMoveFn) { 1999 retVal = onMoveFn.call(sortable, evt); //如果移动函数存在则知心移动函数onMoveFn 2000 } 2001 2002 return retVal; //返回该移动函数执行的结果 //空undefined 2003 } 2004 /*********************************************************************************************** 2005 *函数名 :_disableDraggable 2006 *函数功能描述 : 禁用拖动 把heml5的拖拽属性设置为假 2007 *函数参数 :viod 2008 *函数返回值 :无 2009 *作者 : 2010 *函数创建日期 : 2011 *函数修改日期 : 2012 *修改人 : 2013 *修改原因 : 2014 *版本 : 2015 *历史版本 : 2016 ***********************************************************************************************/ 2017 2018 function _disableDraggable(el) { 2019 el.draggable = false; 2020 } 2021 2022 /*********************************************************************************************** 2023 *函数名 :_unsilent 2024 *函数功能描述 : 将 _silent 设置为假 2025 *函数参数 :viod 2026 *函数返回值 :无 2027 *作者 : 2028 *函数创建日期 : 2029 *函数修改日期 : 2030 *修改人 : 2031 *修改原因 : 2032 *版本 : 2033 *历史版本 : 2034 ***********************************************************************************************/ 2035 function _unsilent() { 2036 _silent = false; 2037 } 2038 2039 2040 /** @returns {HTMLElement|false} */ 2041 /*********************************************************************************************** 2042 *函数名 :_ghostIsLast 2043 *函数功能描述 : 表格分页数据 2044 *函数参数 : 2045 el :类型:dom,拖拽的根节点 2046 evt:类型:obj,事件对象 2047 *函数返回值 : 2048 *作者 : 2049 *函数创建日期 : 2050 *函数修改日期 : 2051 *修改人 : 2052 *修改原因 : 2053 *版本 : 2054 *历史版本 : 2055 ***********************************************************************************************/ 2056 function _ghostIsLast(el, evt) { 2057 var lastEl = el.lastElementChild, //最后一个节点 2058 rect = lastEl.getBoundingClientRect(); //最后一个节点的rect 2059 return ( 2060 (evt.clientY - (rect.top + rect.height) > 5) || //判断鼠标位置是否在dom节点的bottom下面还是上面 如果是做下面则结果大于0 否则小于0 ,如果鼠标位置在dom节点bottom下面大于5px的时候则返回lastEl 节点 2061 (evt.clientX - (rect.right + rect.width) > 5) //不知道是不是程序把right写错了,写成了left。如果是right话则是这样。判断鼠标位置是否在dom节点的right左边还是右边 如果是左边则大于0,否则小于0 如果鼠标位置在dom节点right右边面大于5px的时候则返回lastEl 节点 2062 ) && lastEl; // min delta 2063 } 2064 2065 2066 /** 2067 * Generate id 2068 * @param {HTMLElement} el 2069 * @returns {String} 2070 * @private 2071 */ 2072 /*********************************************************************************************** 2073 *函数名 :_generateId 2074 *函数功能描述 : 根据tag的name和class,src,href,文本内容,来匹配生成唯一的标识符 2075 *函数参数 : 2076 el:dom节点 2077 *函数返回值 :string 2078 *作者 : 2079 *函数创建日期 : 2080 *函数修改日期 : 2081 *修改人 : 2082 *修改原因 : 2083 *版本 : 2084 *历史版本 : 2085 ***********************************************************************************************/ 2086 2087 2088 function _generateId(el) { 2089 var str = el.tagName + el.className + el.src + el.href + el.textContent, 2090 i = str.length, 2091 sum = 0; 2092 2093 while (i--) { 2094 sum += str.charCodeAt(i); 2095 } 2096 2097 return sum.toString(36); //生成36进制 2098 } 2099 2100 /** 2101 * Returns the index of an element within its parent for a selected set of 2102 * elements 2103 * @param {HTMLElement} el 2104 * @param {selector} selector 2105 * @return {number} 2106 */ 2107 /*********************************************************************************************** 2108 *函数名 :_index 2109 *函数功能描述 : 返回在其父范围内的元素的元素的索引 2110 *函数参数 : 2111 el 2112 *函数返回值 :number 2113 *作者 : 2114 *函数创建日期 : 2115 *函数修改日期 : 2116 *修改人 : 2117 *修改原因 : 2118 *版本 : 2119 *历史版本 : 2120 ***********************************************************************************************/ 2121 2122 function _index(el, selector) { 2123 var index = 0; 2124 2125 //如果目标节点不存在,或者目标节点的父节点不存在则返回一个 -1 就是当前目标节点如果是window 则返回-1 2126 if (!el || !el.parentNode) { 2127 return -1; 2128 } 2129 //el.previousElementSibling 获取上一个节点 2130 2131 /* 2132 TEMPLATE 标签 html5 模板标签 例子 2133 // 模板文本 2134 <template id="tpl"> 2135 <img src="dummy.png" title="{{title}}"/> 2136 </template> 2137 2138 // 获取模板 2139 <script type="text/javascript"> 2140 var tplEl = document.getElementById('tpl') 2141 // 通过tplEl.innerText获取也可以 2142 var tpl = tplEl.innerHTML 2143 tpl = tpl.replace(/^[\s\u3000]*|[\s\u3000]*$/, '') 2144 Handlebars.compile(tpl)({title: 'test'}) 2145 </script> 2146 2147 意思是当节点是TEMPLATE标签的时候则表示该搜索标签已经到达了最顶端 2148 */ 2149 while (el && (el = el.previousElementSibling)) { 2150 if (el.nodeName.toUpperCase() !== 'TEMPLATE' 2151 && _matches(el, selector)) { 2152 index++; 2153 } 2154 } 2155 2156 return index; 2157 } 2158 /*********************************************************************************************** 2159 *函数名 :_matches 2160 *函数功能描述 : 匹配tag和tag,匹配clsss和tag, 2161 *函数参数 : 2162 el: 2163 类型:obj,当前拖拽节点dom 2164 selector: 2165 类型:string, tag或者clasname 2166 2167 *函数返回值 : 2168 类型:Boolean,真假,selector如果传递的是tag,则当前的el的tag和selector的tag要同样,或当前的el的class含有selector 中的calss则返回真,否则返回假 2169 *作者 : 2170 *函数创建日期 : 2171 *函数修改日期 : 2172 *修改人 : 2173 *修改原因 : 2174 *版本 : 2175 *历史版本 : 2176 ***********************************************************************************************/ 2177 function _matches(/**HTMLElement*/el, /**String*/selector) { 2178 /* 2179 el 目标节点 2180 selector = /[uo]l/i.test(el.nodeName) ? 'li' : '>*', 2181 selector=>* 2182 */ 2183 if (el) { 2184 //split 字符串截取 //没有.则返回为空 但是selector 的值还是原来的 li 但是selector 类型变成了数组 2185 //shift 删除数组第一个字符串并返回该字符串 2186 //toUpperCase 把字符串变成大写 2187 //整个思维是把一个字符串比如 ".aa .ccc .bbb .ddd" 然后提取到改class中的第一个变成大写 AA 2188 selector = selector.split('.'); // 这里分割判断是class还是tag 2189 2190 var tag = selector.shift().toUpperCase(), // class 2191 2192 //join 把数组["a","b","c"]变成a|b|c 2193 // (?=)会作为匹配校验,但不会出现在匹配结果字符串里面 就是前后必须要匹配有空格 但是不会匹配上空格 2194 //比如匹配 " aa bb cc " 匹配到 [aa,bb,cc] 2195 /* 2196 \\s 与 (?=\\s) 前后必须要有空格,但是不会匹配上空格 2197 */ 2198 re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g'); // 2199 2200 2201 2202 return ( 2203 //tag === '' el.nodeName.toUpperCase() == tag 2204 //selector.length=2 tag === '' true 2205 //el.nodeName.toUpperCase() == tag 如果父节点是ul ol 则这里条件为真 2206 (tag === '' || el.nodeName.toUpperCase() == tag) && 2207 //匹配 class 的长度等于标签的长度,。这里意思是在el中匹配的class含有selector的class就能匹配上 2208 (!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length) 2209 ); 2210 } 2211 2212 return false; 2213 } 2214 2215 2216 /*********************************************************************************************** 2217 *函数名 :_throttle 2218 *函数功能描述 : 回调初始化一个函数 并且调用该回调函数 2219 *函数参数 : 2220 callback: 2221 类型:function,回调函数 2222 ms: 2223 类型:number, 毫秒 2224 2225 *函数返回值 : 2226 类型:function,函数,可以用来声明一个函数作用 2227 *作者 : 2228 *函数创建日期 : 2229 *函数修改日期 : 2230 *修改人 : 2231 *修改原因 : 2232 *版本 : 2233 *历史版本 : 2234 ***********************************************************************************************/ 2235 function _throttle(callback, ms) { 2236 var args, 2237 _this; 2238 2239 return function (/*有可能是n个参数*/) { 2240 2241 if (args === void 0) { 2242 args = arguments; //arguments 就是callback中的callback 所以参数决定来源于它 2243 _this = this; 2244 //执行回调函数 2245 setTimeout(function () { 2246 if (args.length === 1) { //当callback 函数一个参数的时候 2247 callback.call(_this, args[0]); 2248 } else { 2249 callback.apply(_this, args);//当callback 函数多个参数的时候 2250 } 2251 args = void 0; 2252 }, ms); 2253 } 2254 }; 2255 } 2256 /*********************************************************************************************** 2257 *函数名 :_extend 2258 *函数功能描述 :类合并 2259 *函数参数 : 2260 dst:类型:obj,子类 2261 src:类型:obj,父类 2262 *函数返回值 : dst 子类 2263 *作者 : 2264 *函数创建日期 : 2265 *函数修改日期 : 2266 *修改人 : 2267 *修改原因 : 2268 *版本 : 2269 *历史版本 : 2270 ***********************************************************************************************/ 2271 function _extend(dst, src) { 2272 if (dst && src) { 2273 for (var key in src) { //遍历对象 2274 if (src.hasOwnProperty(key)) { //过滤原型 2275 dst[key] = src[key]; 2276 } 2277 } 2278 } 2279 2280 return dst; 2281 } 2282 2283 2284 //声明一个类utils 2285 // Export utils 2286 Sortable.utils = { 2287 on: _on, 2288 off: _off, 2289 css: _css, 2290 find: _find, 2291 is: function (el, selector) { 2292 2293 return !!_closest(el, selector, el); 2294 }, 2295 extend: _extend, 2296 throttle: _throttle, 2297 closest: _closest, 2298 toggleClass: _toggleClass, 2299 index: _index 2300 }; 2301 2302 2303 /** 2304 * Create sortable instance 2305 * @param {HTMLElement} el 2306 * @param {Object} [options] 2307 */ 2308 /*********************************************************************************************** 2309 *函数名 :Sortable.create 2310 *函数功能描述 :在类Sortable中添加多个一个方法,而调用Sortable构造函数实例化给Sortable.create 属性,创建了拖拽功能 2311 *函数参数 : 2312 el:类型:obj,拖拽列表的dom节点 2313 options:类型:obj,拖拽的参数 2314 *函数返回值 : dst 子类 2315 *作者 : 2316 *函数创建日期 : 2317 *函数修改日期 : 2318 *修改人 : 2319 *修改原因 : 2320 *版本 : 2321 *历史版本 : 2322 ***********************************************************************************************/ 2323 2324 Sortable.create = function (el, options) { 2325 return new Sortable(el, options); 2326 }; 2327 2328 2329 // Export 2330 Sortable.version = '1.4.2'; //版本 2331 2332 return Sortable; 2333 });