最近公司项目经常用到一个拖拽 Sortable.js插件,所以有空的时候看了 Sortable.js 源码,总共1300多行这样,写的挺完美的。
官网: http://rubaxa.github.io/Sortable/
拖拽的时候主要由这几个事件完成,
ondragstart 事件:当拖拽元素开始被拖拽的时候触发的事件,此事件作用在被拖曳元素上
ondragenter 事件:当拖曳元素进入目标元素的时候触发的事件,此事件作用在目标元素上
ondragover 事件:拖拽元素在目标元素上移动的时候触发的事件,此事件作用在目标元素上
ondrop 事件:被拖拽的元素在目标元素上同时鼠标放开触发的事件,此事件作用在目标元素上
ondragend 事件:当拖拽完成后触发的事件,此事件作用在被拖曳元素上
主要是拖拽的时候发生ondragstart事件和ondragover事件的时候节点交换位置,其实就是把他们的节点互相调换,当然这只是最简单的拖拽排序方式,里面用到了许多技术比用判断拖拽滚动条的时候是滚动拖拽元素上面的根节点的父节点滚动,还是滚动window上面的滚动条, 还有拖拽的时候利用getBoundingClientRect() 属性判断鼠标是在dom节点的左边,右边,上面,还是下面。还有利用回调和函数式编程声明函数,利用布尔值相加相减去,做0和1判断,利用了事件绑定来判定两个列表中的不同元素,这些都是很有趣的技术。
注意:这个插件是用html5 拖拽的所以也不支持ie9 以下浏览器
接下来我们先看看简单的简单的dome,先加载他的拖拽js Sortable.js 插件,和app.css. 创建一个简单的拖拽很简单 只要传递一个dom节点进去就可以,第二个参数传一个空对象进去
当然app.css,加不加无所谓,如果不加的话要加一个样式就是
1
2
3
4
|
.sortable-ghost {
opacity:
0.4
;
background-color
:
#F4E2C9
;
}
|
拖拽的时候有阴影效果更好看些
该插件还提供了拖拽时候动画,让拖拽变得更炫,很简单加多一个参数就行animation: 150,拖拽时间内执行完动画的时间。里面是用css3动画的ie9以下浏览器 含ie9浏览器不支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<
html
xmlns
=
"http://www.w3.org/1999/xhtml"
>
<
head
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>无标题文档
title
>
head
>
<
link
href
=
"app.css"
rel
=
"stylesheet"
type
=
"text/css"
/>
<
script
src
=
"Sortable.js"
>
script
>
<
body
>
<
ul
id
=
"foo"
class
=
"block__list block__list_words"
>
<
li
>1
li
>
<
li
>2
li
>
<
li
>3
li
>
<
li
>4
li
>
<
li
>5
li
>
<
li
>6
li
>
<
li
>7
li
>
<
li
>8
li
>
ul
>
<
script
>
Sortable.create(document.getElementById('foo'),
{
animation: 150, //动画参数
});
script
>
body
>
html
>
|
这个插件不仅仅提供拖拽功能,还提供了拖拽完之后排序,当然排序的思维很简单,判断鼠标按下去拖拽的那个节点和拖拽到目标节点的两个元素发生ondragover的时候判断他们的dom节点位置,并且互换dom位置就形成了排序。拖拽完成只有 Sortable.js 插件还提供了几个事件接口,我们看看那dome,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
<
html
xmlns
=
"http://www.w3.org/1999/xhtml"
>
<
head
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>无标题文档
title
>
head
>
<
link
href
=
"app.css"
rel
=
"stylesheet"
type
=
"text/css"
/>
<
script
src
=
"Sortable.js"
>
script
>
<
body
>
<
ul
id
=
"foo"
class
=
"block__list block__list_words"
>
<
li
>1
li
>
<
li
>2
li
>
<
li
>3
li
>
<
li
>4
li
>
<
li
>5
li
>
<
li
>6
li
>
<
li
>7
li
>
<
li
>8
li
>
ul
>
<
script
>
Sortable.create(document.getElementById('foo'), {
animation: 150, //动画参数
onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件
console.log('onAdd.foo:', [evt.item, evt.from]);
},
onUpdate: function (evt){ //拖拽更新节点位置发生该事件
console.log('onUpdate.foo:', [evt.item, evt.from]);
},
onRemove: function (evt){ //删除拖拽节点的时候促发该事件
console.log('onRemove.foo:', [evt.item, evt.from]);
},
onStart:function(evt){ //开始拖拽出发该函数
console.log('onStart.foo:', [evt.item, evt.from]);
},
onSort:function(evt){ //发生排序发生该事件
console.log('onSort.foo:', [evt.item, evt.from]);
},
onEnd: function(evt){ //拖拽完毕之后发生该事件
console.log('onEnd.foo:', [evt.item, evt.from]);
}
});
script
>
body
>
html
>
|
我们看看上面的例子,首先看看当拖拽完成的时候他发生事件顺序
onAdd onRemove 没有触发说明当列表中的拖拽数据有增加和减少的时候才会发生该事件, 当然如果有兴趣的朋友可以看看他生事件的顺序和条件。
还有传递一个evt参数,我们看看该参数有些什么东西。主要看_dispatchEvent 这个函数 改函数的功能是:创建一个事件,事件参数主要由name 提供,并且触发该事件,其实就是模拟事件并且触发该事件。
看看改函数的关键源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
<
html
xmlns
=
"http://www.w3.org/1999/xhtml"
>
<
head
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>无标题文档
title
>
head
>
<
link
href
=
"app.css"
rel
=
"stylesheet"
type
=
"text/css"
/>
<
script
src
=
"Sortable.js"
>
script
>
<
body
>
<
ul
id
=
"foo"
class
=
"block__list block__list_words"
>
<
li
drag-id
=
"1"
>1
li
>
<
li
drag-id
=
"2"
>2
li
>
<
li
drag-id
=
"3"
>3
li
>
<
li
drag-id
=
"4"
>4
li
>
<
li
drag-id
=
"5"
>5
li
>
<
li
drag-id
=
"6"
>6
li
>
<
li
drag-id
=
"7"
>7
li
>
<
li
drag-id
=
"8"
>8
li
>
ul
>
<
script
>
Sortable.create(document.getElementById('foo'), {
animation: 150, //动画参数
onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件
console.log('onAdd.foo:', [evt.item, evt.from]);
},
onUpdate: function (evt){ //拖拽更新节点位置发生该事件
console.log('onUpdate.foo:', [evt.item, evt.from]);
},
onRemove: function (evt){ //删除拖拽节点的时候促发该事件
console.log('onRemove.foo:', [evt.item, evt.from]);
},
onStart:function(evt){ //开始拖拽出发该函数
console.log('onStart.foo:', [evt.item, evt.from]);
},
onSort:function(evt){ //发生排序发生该事件
console.log('onSort.foo:', [evt.item, evt.from]);
},
onEnd: function(evt){ //拖拽完毕之后发生该事件
console.log('onEnd.foo:', [evt.item, evt.from]);
var id_arr=''
for(var i=0, len=evt.from.children.length; i<
len
; i++){
id_arr+=','+ evt.from.children[i].getAttribute('drag-id');
}
id_arr
=id_arr.substr(1);
//然后请求后台ajax 这样就完成了拖拽排序
console.log(id_arr);
}
});
body
>
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
<
html
xmlns
=
"http://www.w3.org/1999/xhtml"
>
<
head
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>无标题文档
title
>
head
>
<
link
href
=
"app.css"
rel
=
"stylesheet"
type
=
"text/css"
/>
<
script
src
=
"Sortable.js"
>
script
>
<
body
>
<
div
class
=
"container"
style
=
"height: 520px"
>
<
div
data-force
=
"30"
class
=
"layer block"
style
=
"left: 14.5%; top: 0; width: 37%"
>
<
div
class
=
"layer title"
>List A
div
>
<
ul
id
=
"foo"
class
=
"block__list block__list_words"
>
<
li
>бегемот
li
>
<
li
>корм
li
>
<
li
>антон
li
>
<
li
>сало
li
>
<
li
>железосталь
li
>
<
li
>валик
li
>
<
li
>кровать
li
>
<
li
>краб
li
>
ul
>
div
>
<
div
data-force
=
"18"
class
=
"layer block"
style
=
"left: 58%; top: 143px; width: 40%;"
>
<
div
class
=
"layer title"
>List B
div
>
<
ul
id
=
"bar"
class
=
"block__list block__list_tags"
>
<
li
>казнить
li
>
<
li
>,
li
>
<
li
>нельзя
li
><
li
>помиловать
li
>
ul
>
div
>
div
>
<
script
>
Sortable.create(document.getElementById('foo'), {
group:"words",
animation: 150, //动画参数
onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件
console.log('onAdd.foo:', [evt.item, evt.from]);
},
onUpdate: function (evt){ //拖拽更新节点位置发生该事件
console.log('onUpdate.foo:', [evt.item, evt.from]);
},
onRemove: function (evt){ //删除拖拽节点的时候促发该事件
console.log('onRemove.foo:', [evt.item, evt.from]);
},
onStart:function(evt){ //开始拖拽出发该函数
console.log('onStart.foo:', [evt.item, evt.from]);
},
onSort:function(evt){ //发生排序发生该事件
console.log('onSort.foo:', [evt.item, evt.from]);
},
onEnd: function(evt){ //拖拽完毕之后发生该事件
console.log('onEnd.foo:', [evt.item, evt.from]);
}
});
Sortable.create(document.getElementById('bar'), {
group:"words",
animation: 150, //动画参数
onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件
console.log('onAdd.foo:', [evt.item, evt.from]);
},
onUpdate: function (evt){ //拖拽更新节点位置发生该事件
console.log('onUpdate.foo:', [evt.item, evt.from]);
},
onRemove: function (evt){ //删除拖拽节点的时候促发该事件
console.log('onRemove.foo:', [evt.item, evt.from]);
},
onStart:function(evt){ //开始拖拽出发该函数
console.log('onStart.foo:', [evt.item, evt.from]);
},
onSort:function(evt){ //发生排序发生该事件
console.log('onSort.foo:', [evt.item, evt.from]);
},
onEnd: function(evt){ //拖拽完毕之后发生该事件
console.log('onEnd.foo:', [evt.item, evt.from]);
}
});
script
>
body
>
html
>
|
当然也支持 只能从a列表拖拽到b列表 dome
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
<
html
xmlns
=
"http://www.w3.org/1999/xhtml"
>
<
head
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>无标题文档
title
>
head
>
<
link
href
=
"app.css"
rel
=
"stylesheet"
type
=
"text/css"
/>
<
script
src
=
"Sortable.js"
>
script
>
<
body
>
<
div
class
=
"container"
style
=
"height: 520px"
>
<
div
data-force
=
"30"
class
=
"layer block"
style
=
"left: 14.5%; top: 0; width: 37%"
>
<
div
class
=
"layer title"
>List A
div
>
<
ul
id
=
"foo"
class
=
"block__list block__list_words"
>
<
li
>бегемот
li
>
<
li
>корм
li
>
<
li
>антон
li
>
<
li
>сало
li
>
<
li
>железосталь
li
>
<
li
>валик
li
>
<
li
>кровать
li
>
<
li
>краб
li
>
ul
>
div
>
<
div
data-force
=
"18"
class
=
"layer block"
style
=
"left: 58%; top: 143px; width: 40%;"
>
<
div
class
=
"layer title"
>List B
div
>
<
ul
id
=
"bar"
class
=
"block__list block__list_tags"
>
<
li
>казнить
li
>
<
li
>,
li
>
<
li
>нельзя
li
><
li
>помиловать
li
>
ul
>
div
>
div
>
<
script
>
Sortable.create(document.getElementById('foo'), {
group: {
name:"words",
pull: true,
put: true
},
animation: 150, //动画参数
onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件
console.log('onAdd.foo:', [evt.item, evt.from]);
},
onUpdate: function (evt){ //拖拽更新节点位置发生该事件
console.log('onUpdate.foo:', [evt.item, evt.from]);
},
onRemove: function (evt){ //删除拖拽节点的时候促发该事件
console.log('onRemove.foo:', [evt.item, evt.from]);
},
onStart:function(evt){ //开始拖拽出发该函数
console.log('onStart.foo:', [evt.item, evt.from]);
},
onSort:function(evt){ //发生排序发生该事件
console.log('onSort.foo:', [evt.item, evt.from]);
},
onEnd: function(evt){ //拖拽完毕之后发生该事件
console.log('onEnd.foo:', [evt.item, evt.from]);
}
});
Sortable.create(document.getElementById('bar'), {
group: {
name:"words",
pull: false,
put: true
},
animation: 150, //动画参数
onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件
console.log('onAdd.foo:', [evt.item, evt.from]);
},
onUpdate: function (evt){ //拖拽更新节点位置发生该事件
console.log('onUpdate.foo:', [evt.item, evt.from]);
},
onRemove: function (evt){ //删除拖拽节点的时候促发该事件
console.log('onRemove.foo:', [evt.item, evt.from]);
},
onStart:function(evt){ //开始拖拽出发该函数
console.log('onStart.foo:', [evt.item, evt.from]);
},
onSort:function(evt){ //发生排序发生该事件
console.log('onSort.foo:', [evt.item, evt.from]);
},
onEnd: function(evt){ //拖拽完毕之后发生该事件
console.log('onEnd.foo:', [evt.item, evt.from]);
}
});
script
>
body
>
html
>
|
当然也支持克隆 从a列表可克隆dom节点拖拽添加到b俩表 只要把参数 pull: 'clone', 这样就可以了 dome
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
<
html
xmlns
=
"http://www.w3.org/1999/xhtml"
>
<
head
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>无标题文档
title
>
head
>
<
link
href
=
"app.css"
rel
=
"stylesheet"
type
=
"text/css"
/>
<
script
src
=
"Sortable.js"
>
script
>
<
body
>
<
div
class
=
"container"
style
=
"height: 520px"
>
<
div
data-force
=
"30"
class
=
"layer block"
style
=
"left: 14.5%; top: 0; width: 37%"
>
<
div
class
=
"layer title"
>List A
div
>
<
ul
id
=
"foo"
class
=
"block__list block__list_words"
>
<
li
>бегемот
li
>
<
li
>корм
li
>
<
li
>антон
li
>
<
li
>сало
li
>
<
li
>железосталь
li
>
<
li
>валик
li
>
<
li
>кровать
li
>
<
li
>краб
li
>
ul
>
div
>
<
div
data-force
=
"18"
class
=
"layer block"
style
=
"left: 58%; top: 143px; width: 40%;"
>
<
div
class
=
"layer title"
>List B
div
>
<
ul
id
=
"bar"
class
=
"block__list block__list_tags"
>
<
li
>казнить
li
>
<
li
>,
li
>
<
li
>нельзя
li
><
li
>помиловать
li
>
ul
>
div
>
div
>
<
script
>
Sortable.create(document.getElementById('foo'), {
group: {
name:"words",
pull: 'clone',
put: true
},
animation: 150, //动画参数
onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件
console.log('onAdd.foo:', [evt.item, evt.from]);
},
onUpdate: function (evt){ //拖拽更新节点位置发生该事件
console.log('onUpdate.foo:', [evt.item, evt.from]);
},
onRemove: function (evt){ //删除拖拽节点的时候促发该事件
console.log('onRemove.foo:', [evt.item, evt.from]);
},
onStart:function(evt){ //开始拖拽出发该函数
console.log('onStart.foo:', [evt.item, evt.from]);
},
onSort:function(evt){ //发生排序发生该事件
console.log('onSort.foo:', [evt.item, evt.from]);
},
onEnd: function(evt){ //拖拽完毕之后发生该事件
console.log('onEnd.foo:', [evt.item, evt.from]);
}
});
Sortable.create(document.getElementById('bar'), {
group: {
name:"words",
pull: false,
put: true
},
animation: 150, //动画参数
onAdd: function (evt){ //拖拽时候添加有新的节点的时候发生该事件
console.log('onAdd.foo:', [evt.item, evt.from]);
},
onUpdate: function (evt){ //拖拽更新节点位置发生该事件
console.log('onUpdate.foo:', [evt.item, evt.from]);
},
onRemove: function (evt){ //删除拖拽节点的时候促发该事件
console.log('onRemove.foo:', [evt.item, evt.from]);
},
onStart:function(evt){ //开始拖拽出发该函数
console.log('onStart.foo:', [evt.item, evt.from]);
},
onSort:function(evt){ //发生排序发生该事件
console.log('onSort.foo:', [evt.item, evt.from]);
},
onEnd: function(evt){ //拖拽完毕之后发生该事件
console.log('onEnd.foo:', [evt.item, evt.from]);
}
});
script
>
body
>
html
>
|
该插件也支持删除拖拽列表的节点,主要是设置filter 参数,改参数可以设置成函数,但是设置成函数的时候不还要自己定义拖拽,显得有些麻烦,所以一般设置成class,或者是tag,设置成class和tag的时候就是做拖拽列表中含有calss,tag的节点可以点击的时候可以触发onFilter函数,触发会传递一个evt参数进来evt.item 就是class或者tag的dom节点,可以通过他们的血缘关系从而删除需要删除的节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<
html
xmlns
=
"http://www.w3.org/1999/xhtml"
>
<
head
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>无标题文档
title
>
head
>
<
link
href
=
"st/app.css"
rel
=
"stylesheet"
type
=
"text/css"
/>
<
script
src
=
"Sortable3.js"
>
script
>
<
body
>
<
body
>
<
a
name
=
"e"
>
a
>
<
div
cl
|