源碼分享者:姚觀壽
最近公司項目經常用到一個拖拽 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 });