-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathSortable.js
2333 lines (1975 loc) · 109 KB
/
Sortable.js
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**!
* Sortable
* @author RubaXa <[email protected]>
* @license MIT
*/
(function (factory) {
"use strict"; //严格模式
if (typeof define === "function" && define.amd) { //兼容 require.js 写法
define(factory);
}
else if (typeof module != "undefined" && typeof module.exports != "undefined") { //兼容node写法
module.exports = factory();
}
else if (typeof Package !== "undefined") {
Sortable = factory(); // export for Meteor.js 兼容 Meteor.js 写法
}
else {
/* jshint sub:true */
window["Sortable"] = factory(); //把它挂载在window下
}
})(function () {
"use strict";
if (typeof window == "undefined" || typeof window.document == "undefined") { //判断该js是否在window或者document 下运行
return function () {
throw new Error("Sortable.js requires a window with a document"); //如果不是则抛出一个错误
};
}
var i=0;
var dragEl, //当前拖拽节点,开始拖拽节点,鼠标按下去的节点
parentEl,
ghostEl, // 拖拽镜像节点
cloneEl, //克隆节点
rootEl, //鼠标开始按下去拖拽的根节点
nextEl, //下一个节点
scrollEl,//滚动节点
scrollParentEl, //滚动的父节点
lastEl, //根节点中的最后一个自己点
lastCSS,
lastParentCSS,
oldIndex, //开始拖拽节点的索引 就是鼠标按下去拖拽节点的索引
newIndex, //拖拽完之后现在节点
activeGroup,
autoScroll = {}, //滚动对象用于存鼠标的xy轴
/*
tapEvt 触摸对象包括x与y轴与拖拽当前节点
tapEvt = {
target: dragEl,
clientX: touch.clientX,
clientY: touch.clientY
};
*/
tapEvt,
touchEvt,
moved,
/** @const */
RSPACE = /\s+/g, //全局匹配空格
expando = 'Sortable' + (new Date).getTime(), //字符串Sortable+时间戳
win = window, //缩写win
document = win.document,
parseInt = win.parseInt;
//draggable html5 拖拽属性 初始化的时候是true
var supportDraggable = !!('draggable' in document.createElement('div')),
//判断浏览器是否支持css3 这个属性pointer-events
supportCssPointerEvents = (function (el) {
el = document.createElement('x');
el.style.cssText = 'pointer-events:auto';
return el.style.pointerEvents === 'auto';
})(),
_silent = false, //默认
abs = Math.abs,
slice = [].slice,
touchDragOverListeners = [], //新建一个数组 鼠标触摸拖拽数组
//_autoScroll 相当于 被一个函数付值
/* _autoScroll = function(callback,ms){
var args,
_this;
if (args === void 0) {
args = arguments;
_this = this;
setTimeout(function () {
if (args.length === 1) {
callback.call(_this, args[0]);
} else {
callback.apply(_this, args);
}
args = void 0;
}, ms);
}
其实就是_autoScroll=function(参数){
放到 _throttle 的回调函数中 function (/参数/)
}
}*/
/***********************************************************************************************
*函数名 :_autoScroll
*函数功能描述 : 拖拽智能滚动
*函数参数 :
evt:
类型:boj, 事件对象
options:类型:obj, 参数类
rootEl:类型:obj dom节点,拖拽的目标节点
*函数返回值 : viod
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_autoScroll = _throttle(
//回调函数
function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
//每次拖拽只会调用一次该函数
//evt 是事件对象 event
//options.scroll如果为真 并且rootEl 为真的时候
// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
if (rootEl && options.scroll) {
var el,
rect,
sens = options.scrollSensitivity, //滚动灵敏度 默认是30
speed = options.scrollSpeed, //滚动速度 默认是10
x = evt.clientX, //获取鼠标在可视窗口的x值
y = evt.clientY, //获取鼠标在可视窗口的y值
winWidth = window.innerWidth, //获取可视窗口的高度和宽度 有兼容性问题 不包括滚动条
winHeight = window.innerHeight,
vx,
vy
;
// Delect scrollEl 观察滚动节点 如果滚动的父节点scrollParentEl不等于当前的根节点的时候则 可以滚动
if (scrollParentEl !== rootEl) {
scrollEl = options.scroll; //true 布尔值
scrollParentEl = rootEl; //鼠标开始按下的根节点
if (scrollEl === true) {
scrollEl = rootEl;
do {
//判断父节点,哪个父节点出现滚动条,如果有滚动条则设置改拖拽的节点滚动条父节点
if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
(scrollEl.offsetHeight < scrollEl.scrollHeight)
) {
break;
}
/* jshint boss:true */
} while (scrollEl = scrollEl.parentNode);
}
}
if (scrollEl) {
el = scrollEl;
rect = scrollEl.getBoundingClientRect();
/*
var box=document.getElementById('box'); // 获取元素
alert(box.getBoundingClientRect().top); // 元素上边距离页面上边的距离
alert(box.getBoundingClientRect().right); // 元素右边距离页面左边的距离
alert(box.getBoundingClientRect().bottom); // 元素下边距离页面上边的距离
alert(box.getBoundingClientRect().left); // 元素左边距离页面左边的距离
y:y = evt.clientY, //获取鼠标在可视窗口的y值
sens: sens = options.scrollSensitivity, //滚动灵敏度 默认是30
*/
//vx 与 vy 只是个布尔值判断 然后就得出一个值
/*
true-true=0
true-false=1
false-false=0
false-true=-1
*/
vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens); //这样判断并不是很好因为只会在边界判断事件发生,如果一开始拖拽快速超过了设置的+-sens值滚动事件将没有发生。个人感觉改成一下判断会比较好。
/*
if(rect.top+sens-y>=0){
vy=-1;
} else if(rect.bottom+sens-y<=0){
vy=1;
}else{
vy=0;
}
*/
}
if (!(vx || vy)) { //当他等于0的时候 拖拽滚动的是window
vx = (winWidth - x <= sens) - (x <= sens);
vy = (winHeight - y <= sens) - (y <= sens);
/* jshint expr:true */
(vx || vy) && (el = win);
}
if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
autoScroll.el = el;
autoScroll.vx = vx;
autoScroll.vy = vy;
//speed=10 滚动速度
clearInterval(autoScroll.pid);
if (el) {
autoScroll.pid = setInterval(function () {
if (el === win) {
win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
} else {
vy && (el.scrollTop += vy * speed); //设置元素滚动条的位置,每次滚动1*speed如果是0 则不会滚动
vx && (el.scrollLeft += vx * speed);//设置元素滚动条的位置
}
},
24);
}
}
}
//时间 毫秒
}, 30),
/***********************************************************************************************
*函数名 :_prepareGroup
*函数功能描述 : //options.group 属性变成对象 。如果group不是对象则变成对象,并且group对象的name就等于改group的值 并且添加多['pull', 'put'] 属性默认值是true
如果设置group{
pull:true, 则可以拖拽到其他列表 否则反之
put:true, 则可以从其他列表中放数据到改列表,false则反之
}
pull: 'clone', 还有一个作用是克隆,就是当这个列表拖拽到其他列表的时候不会删除改列表的节点。
*函数参数 :
options:
类型:boj, options 拖拽参数
*函数返回值 : viod
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_prepareGroup = function (options) {
var group = options.group; //把options.group 付值给group
// 先判断他group 是否是对象,如果不是则变成对象,name是他的属性
if (!group || typeof group != 'object') { //如果当前options.group; 不存在或者不是obj则把他变成一个对象
group = options.group = {name: group};
}
//判断有没有设置 'pull', 'put' 如果没有 则添加 'pull', 'put' 属性并且设置为真
['pull', 'put'].forEach(function (key) {
if (!(key in group)) { //
group[key] = true; //将为group对象添加两个属性'pull', 'put' 并且为true
}
});
//options.group 变成对象之后join方法将匹配不到任何东西
//如果他直接是数组的话这里就是把数组的值拆分成字符串连接起来
//options.group 属性变成对象 。
options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
}
;
/**
* @class Sortable
* @param {HTMLElement} el
* @param {Object} [options]
*/
//el html dom节点
//param obj 数据对象
/***********************************************************************************************
*函数名 :Sortable
*函数功能描述 : 主类,里面包含很多方法
*函数参数 : dom节点rootEl
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
function Sortable(el, options) {
//判断 param 如果不是HTMLDOM 则抛出错误
if (!(el && el.nodeType && el.nodeType === 1)) {
throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el);
}
//把dom节点存到this中 好操作 就是id 父层节点
this.el = el; // root element
this.options = options = _extend({}, options); //把options初始化的数据存到this中 好操作
// Export instance
//把 Sortable 类放在HTMLDOM节点的expando属性中
el[expando] = this;
// Default options
//初始化 defaults 数据
var defaults = {
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上
};
// Set default options
//当options类中的数据没有defaults类中的数据的时候 就把defaults类中的数据赋值给options类
for (var name in defaults) {
!(name in options) && (options[name] = defaults[name]);
}
//把group: 变成一个对象,本来是一个属性的
_prepareGroup(options);
// Bind all private methods
for (var fn in this) {
if (fn.charAt(0) === '_') {
//如果这个 Sortable 类下的函数 开始字符串还有_下划线的就把他的this指向Sortable类
this[fn] = this[fn].bind(this);
}
}
// Setup drag mode
//forceFallback 如果是false 那么给supportDraggable 函数他,然后判断浏览器是否支持draggable 拖拽如果支持是true 否则是false
this.nativeDraggable = options.forceFallback ? false : supportDraggable;
// Bind events
//添加事件 // 入口从这里开始
_on(el, 'mousedown', this._onTapStart);
_on(el, 'touchstart', this._onTapStart);
//html5 dragover 添加拖拽事件
if (this.nativeDraggable) {
//传递整个类进去
_on(el, 'dragover', this); //然后会执行这个函数handleEvent
_on(el, 'dragenter', this); //然后会执行这个函数handleEvent
}
//touchDragOverListeners 添加一个false 数据到数组里。
touchDragOverListeners.push(this._onDragOver);
// Restore sorting
//sort 排序函数
//store 是null 未找到get函数不知道怎么回事 可能它是属于store.js的api
options.store && this.sort(options.store.get(this));
}
/***********************************************************************************************
*函数名 :Sortable.prototype
*函数功能描述 : 主类,的原型
*函数参数 :
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
Sortable.prototype = /** @lends Sortable.prototype */ {
constructor: Sortable, //防止继承混乱,构造方法指向他的构造函数
/***********************************************************************************************
*函数名 :_onTapStart
*函数功能描述 : 鼠标按下去函数,oldIndex统计目标节点与同级同胞的上节点总和
*函数参数 : viod
*函数返回值 : 无
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_onTapStart: function (/** Event|TouchEvent */evt) {
var _this = this,
el = this.el, //id dom节点
options = this.options, //参数类
type = evt.type, //事件类型
touch = evt.touches && evt.touches[0], //触摸屏事件
target = (touch || evt).target, //目标节点
originalTarget = target,
filter = options.filter; // null
//如果是鼠标按下去事件,但是如果不是左键按下去的话,或者disabled 为假的时候 结束该程序 disabled 为fasle
if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
return; // only left button or enabled
}
//draggable=/[uo]l/i.test(el.nodeName) ? 'li' : '>*',
// target=el
// target = _closest(target, options.draggable, el); //true
//
if (!target) {
return;
}
// get the index of the dragged element within its parent
//获取索引
oldIndex = _index(target, options.draggable);
// Check filter+
//filter 如果是函数 但是默认值filter
if (typeof filter === 'function') {
if (filter.call(this, evt, target, this)) { //并且有返回值是true 的话
//触发该函数
_dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex); //则触发oFilter事件
evt.preventDefault(); //停止默认事件
return; // cancel dnd
}
}
else if (filter) {
//// JavaScript数组some()方法测试数组中的某个元素是否通过由提供的功能来实现测试 ,只要有一个真则返回真
/*
例子
if (!Array.prototype.some)
{
Array.prototype.some = function(fun )
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this &&
fun.call(thisp, this[i], i, this))
return true;
}
return false;
};
}
function isBigEnough(element, index, array) {
return (element >= 10);
}
var retval = [2, 5, 8, 1, 4].some(isBigEnough);
document.write("Returned value is : " + retval );
var retval = [12, 5, 8, 1, 4].some(isBigEnough);
document.write("<br />Returned value is : " + retval );
*/
filter = filter.split(',').some(function (criteria) { //如果filter是字符串,则会用split 拆分成数组并且遍历他只有一个class 对的上则_closest 匹配tag和class 如果设置的filter中有calss 和拖拽元素上面的clss相同,或者tag相同,则会触发oFilter函数
criteria = _closest(originalTarget, criteria.trim(), el);//_closest
if (criteria) {
_dispatchEvent(_this, criteria, 'filter', target, el, oldIndex); //调用自定义事件
return true;
}
});
if (filter) {
evt.preventDefault();
return; // cancel dnd
}
}
//handle 存在
//originalTarget
//handle 这个参数是设置该标签,或者该class可以拖拽 但是不要设置 id的节点和子节点相同的tag不然会有bug
if (options.handle && !_closest(originalTarget, options.handle, el)) {
return;
}
// Prepare `dragstart`
// 到这里
this._prepareDragStart(evt, touch, target);
},
/***********************************************************************************************
*函数名 :_onTapStart
*函数功能描述 : 开始准备拖
*函数参数 : evt:
类型:obj,事件对象
touch:
类型:obj,触摸事件对象,判断是否是触摸事件还是鼠标事件
target: 类型:dom-obj,目标节点
*函数返回值 : 无
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
//evt pc 的事件对象
//touch 移动的的事件对象
//target 目标节点
var _this = this,
el = _this.el, //id节点,就是父层节点
options = _this.options, //参数类
ownerDocument = el.ownerDocument, //整个文档
dragStartFn; //声明开始拖拽函数
//target 目标节点存在 dragEl 当前拖拽的节点 并且目标节点的父节点是id的节点的时候
if (target && !dragEl && (target.parentNode === el)) {
tapEvt = evt; //事件对象
rootEl = el; //拖拽的根节点 就是传进来的id那个节点
dragEl = target; //目标节点 当前的拖拽节点 鼠标按下去拖拽的节点
parentEl = dragEl.parentNode; //目标节点 当前的拖拽节点 的父节点 就是 dragEl.parentNode ==rootEl
nextEl = dragEl.nextSibling; //目标节点 的下一个节点
activeGroup = options.group; //Object {name: "words", pull: true, put: true}
//开始拖拽函数
dragStartFn = function () {
// Delayed drag has been triggered 延迟拖动已被触发
// we can re-enable the events: touchmove/mousemove 我们可以重新启用touchmove / MouseMove事件:
//解绑事件,关闭_dragStartTimer 定时器 取消dragStartFn 函数执行
_this._disableDelayedDrag();
// Make the element draggable 使元件拖动
//把当前的拖拽节点的draggable 属性设置为真,让他支持html5拖拽事件
dragEl.draggable = true;
// Chosen item dragEl 目标节点 类 _this.options.chosenClass='sortable-chosen'
//为拖拽的节点添加一个class
_toggleClass(dragEl, _this.options.chosenClass, true);
// Bind the events: dragstart/dragend 绑定事件拖曳开始dragend
_this._triggerDragStart(touch);
};
// Disable "draggable" ignore="a, img"
options.ignore.split(',').forEach(function (criteria) {
// criteria 遍历数组的当前target
//criteria.trim() 去除空格
/*
el.draggable //html5拖拽属性
function _disableDraggable(el) {
el.draggable = false;
}
*/
// 该函数功能是把当前拖拽对象的a和img节点的html5 拖拽属性改为false
_find(dragEl, criteria.trim(), _disableDraggable);
});
_on(ownerDocument, 'mouseup', _this._onDrop); //在ownerDocument 文档上面当发生鼠标抬起的时候,添加_onDrop函数
_on(ownerDocument, 'touchend', _this._onDrop);//在ownerDocument 文档上面当发生触摸抬起的时候,添加_onDrop函数
_on(ownerDocument, 'touchcancel', _this._onDrop);//在ownerDocument 文档上面当发生触摸划过抬起的时候,解绑_onDrop函数
//delay 初始值为0
if (options.delay) {
/*
这里里面的程序块添加了事件只有调用_disableDelayedDrag,添加了一个定时器执行一次dragStartFn函数,这个函数又马上解绑_disableDelayedDrag事件,关闭定时器,整个思路是只让程序发生一次,并且马上解绑事件,销毁该事件。这样思维有些特别
*/
// If the user moves the pointer or let go the click or touch 如果用户移动指针或单击“单击”或“触摸”
// before the delay has been reached: //之前的延迟已达到
// disable the delayed drag //禁用延迟拖动
_on(ownerDocument, 'mouseup', _this._disableDelayedDrag); //当鼠标抬起的时候在文档上添加_disableDelayedDrag事件
_on(ownerDocument, 'touchend', _this._disableDelayedDrag); //触摸抬起的时候在文档上添加_disableDelayedDrag事件
_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); //触摸划过抬起的时候在文档上添加_disableDelayedDrag事件
_on(ownerDocument, 'mousemove', _this._disableDelayedDrag); //当鼠标移动mousemove的时候在文档上添加_disableDelayedDrag事件
_on(ownerDocument, 'touchmove', _this._disableDelayedDrag); //触摸移动的时候在文档上添加_disableDelayedDrag事件
_this._dragStartTimer = setTimeout(dragStartFn, options.delay); //执行dragStartFn函数
} else {
//开始拖拽
dragStartFn();
}
}
},
/***********************************************************************************************
*函数名 :_disableDelayedDrag
*函数功能描述 : 禁用延迟拖拽 当拖拽延时的时候,把所有事件解绑,并且关闭定时器。
*函数参数 :
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_disableDelayedDrag: function () {
var ownerDocument = this.el.ownerDocument;
clearTimeout(this._dragStartTimer); //关闭定时器
_off(ownerDocument, 'mouseup', this._disableDelayedDrag);//当鼠标抬起的时候在文档上解绑_disableDelayedDrag事件
_off(ownerDocument, 'touchend', this._disableDelayedDrag);//触摸抬起的时候在文档上解绑_disableDelayedDrag事件
_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);//当触摸划过抬起的时候在文档上解绑_disableDelayedDrag事件
_off(ownerDocument, 'mousemove', this._disableDelayedDrag);//当鼠移动起的时候在文档上解绑_disableDelayedDrag事件
_off(ownerDocument, 'touchmove', this._disableDelayedDrag);//触摸的时候在文档上解绑_disableDelayedDrag事件
},
/***********************************************************************************************
*函数名 :_triggerDragStart
*函数功能描述 : 为拖拽前做好准本,包括判断是否是触摸设备,或者pc,或者没有dragend
*函数参数 :
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_triggerDragStart: function (/** Touch */touch) {
//按下去的值
if (touch) {
// Touch device support 触摸设备支持
tapEvt = {
target: dragEl,
clientX: touch.clientX,
clientY: touch.clientY
};
this._onDragStart(tapEvt, 'touch'); //触摸设备
}
else if (!this.nativeDraggable) {
this._onDragStart(tapEvt, true); //pc设备
}
else {
//如果当前的html还没有设置拖拽属性则先设置拖拽属性
_on(dragEl, 'dragend', this);
_on(rootEl, 'dragstart', this._onDragStart);
}
try {
if (document.selection) {
// Timeout neccessary for IE9
setTimeout(function () {
document.selection.empty(); //取消选中
});
} else {
window.getSelection().removeAllRanges();//取消选中
}
} catch (err) {
}
},
_dragStarted: function () {
if (rootEl && dragEl) { //如果鼠标按下去的拖拽节点存在和拖拽的根节点存在
// Apply effect
//为拖拽节点添加一个class名字是'sortable-ghost'
_toggleClass(dragEl, this.options.ghostClass, true);
//Sortable类赋值给Sortable.active 属性
Sortable.active = this;
// Drag start event
//开始拖拽 并且会相应onStart 接口函数
_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
}
},
_emulateDragOver: function () {
if (touchEvt) {
if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) {
return;
}
this._lastX = touchEvt.clientX;
this._lastY = touchEvt.clientY;
if (!supportCssPointerEvents) {
_css(ghostEl, 'display', 'none');
}
var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
parent = target,
groupName = ' ' + this.options.group.name + '',
i = touchDragOverListeners.length;
if (parent) {
do {
if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
while (i--) {
touchDragOverListeners[i]({
clientX: touchEvt.clientX,
clientY: touchEvt.clientY,
target: target,
rootEl: parent
});
}
break;
}
target = parent; // store last element
}
/* jshint boss:true */
while (parent = parent.parentNode);
}
if (!supportCssPointerEvents) {
_css(ghostEl, 'display', '');
}
}
},
/*
tapEvt = {
target: dragEl,
clientX: touch.clientX,
clientY: touch.clientY
};
*/
/***********************************************************************************************
*函数名 :_onTouchMove
*函数功能描述 : 触摸移动拖拽动画事件ghostEl,把拖拽移动的xy值给ghostEl节点
*函数参数 : viod
*函数返回值 : 无
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_onTouchMove: function (/**TouchEvent*/evt) {
//evt 事件对象
if (tapEvt) {
// only set the status to dragging, when we are actually dragging
if (!Sortable.active) { //Sortable.active 不存在则执行_dragStarted函数 设置拖拽动态
this._dragStarted();
}
// as well as creating the ghost element on the document body
// 创建一个ghostEl dom节点,并且是克隆拖拽节点的rootEl下面就是id那个dom节点,添加在,并且设置了一些属性,高,宽,top,left,透明度,鼠标样式,
this._appendGhost();
var touch = evt.touches ? evt.touches[0] : evt, //判断是否是触摸事件还是pc鼠标事件
dx = touch.clientX - tapEvt.clientX, //鼠标移动的x位置减去鼠标按下去的位置。
dy = touch.clientY - tapEvt.clientY,//鼠标移动的y位置减去鼠标按下去的位置。
//3d 特效 x是左右,y是上下,z是放大缩小 设置3d效果
translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
moved = true;
touchEvt = touch; //事件对象
_css(ghostEl, 'webkitTransform', translate3d); //设置3d效果
_css(ghostEl, 'mozTransform', translate3d); //设置3d效果
_css(ghostEl, 'msTransform', translate3d) ; //设置3d效果
_css(ghostEl, 'transform', translate3d); //设置3d效果
evt.preventDefault(); // 阻止默认事件
}
},
/***********************************************************************************************
*函数名 :_appendGhost
*函数功能描述 : 创建一个ghostEl dom节点,并且是克隆拖拽节点的rootEl下面就是id那个dom节点,添加在,并且设置了一些属性,高,宽,top,left,透明度,鼠标样式,
*函数参数 : viod
*函数返回值 : 无
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_appendGhost: function () {
if (!ghostEl) { // 如果ghostEl 是空的,或者是假,或者是undefined,或者是0,则执行下面程序
/*getBoundingClientRect()
其实跟 o_dom.getBoundingClientRect().left= o_dom.offsetLeft; 他们值相等
这个方法返回一个矩形对象,包含四个属性:left、top、right和bottom。分别表示元素各边与页面上边和左边的距离。
*/
var rect = dragEl.getBoundingClientRect(),
css = _css(dragEl), //返回当前obj 所有的style的属性
options = this.options, //this.options 参数
ghostRect; //一个空变量
ghostEl = dragEl.cloneNode(true); //克隆dragEl 当前拖拽的节点
//options.ghostClass='sortable-ghost'
_toggleClass(ghostEl, options.ghostClass, false);
//fallbackClass= 'sortable-fallback
_toggleClass(ghostEl, options.fallbackClass, true);
//给新创建的节点的left和top和该节点的left和top值相等,所以要减去marginTop,marginLeft
_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
_css(ghostEl, 'width', rect.width); //宽和高和拖拽节点相同
_css(ghostEl, 'height', rect.height);
_css(ghostEl, 'opacity', '0.8'); //透明度为0.8
_css(ghostEl, 'position', 'fixed'); // 固定定位
_css(ghostEl, 'zIndex', '100000'); //层为100000
_css(ghostEl, 'pointerEvents', 'none'); //pointer-events:none顾名思意,就是鼠标事件拜拜的意思。元素应用了该CSS属性,链接啊,点击啊什么的都变成了“浮云牌酱油”。
//把ghostEl 添加到拖拽的根节点那
options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
// Fixing dimensions. 固定尺寸 但是我觉这样写多此一举,因为上面已经设置高宽了,然后再乘以2,再减去一般结果还是一样的
ghostRect = ghostEl.getBoundingClientRect();
_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
}
},
/***********************************************************************************************
*函数名 :_onDragStart
*函数功能描述 : 拖拽开始 为document添加触摸事件与鼠标事件
*函数参数 :
evt:
类型:obj, 事件对象
useFallback:类型:string, Boolean 值
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
//html5拖拽属性。dataTransfer对象有两个主要的方法:getData()方法和setData()方法。
var dataTransfer = evt.dataTransfer,
options = this.options;
//解绑文档上面的一些事件
this._offUpEvents();
//Object {name: "words", pull: true, put: true}
//activeGroup={name: "words", pull: true, put: true}
if (activeGroup.pull == 'clone') { //如果 参数是clone 则可以克隆节点而不是拖拽节点过去
cloneEl = dragEl.cloneNode(true); //cloneNode(false) 克隆复制节点,参数如果是false则不复制里面的html,true则会复制整个dom包括里面的html
//设置cloneEl 节点隐藏
_css(cloneEl, 'display', 'none');
//插入加点,在当前拖拽的dom节点前面插入一个节点
rootEl.insertBefore(cloneEl, dragEl);
}
if (useFallback) { //如果是触摸则添加触摸事件
if (useFallback === 'touch') {
// Bind touch events
//添加触摸移动事件
_on(document, 'touchmove', this._onTouchMove);
//添加触摸抬起事件
_on(document, 'touchend', this._onDrop);
//添加触摸划过结束事件
_on(document, 'touchcancel', this._onDrop);
} else {
// Old brwoser
//pc 添加鼠标移动事件
_on(document, 'mousemove', this._onTouchMove);
//pc 添加鼠标抬起事件
_on(document, 'mouseup', this._onDrop);
}
this._loopId = setInterval(this._emulateDragOver, 50);
}
else {
//html5拖拽属性。dataTransfer对象有两个主要的方法:getData()方法和setData()方法。
if (dataTransfer) {
dataTransfer.effectAllowed = 'move';//move :只允许值为”move”的dropEffect。
/*
setData: function (dataTransfer, dragEl) { dataTransfer.setData('Text', dragEl.textContent);}
设置拖拽时候拖拽信息
*/
options.setData && options.setData.call(this, dataTransfer, dragEl);
}
_on(document, 'drop', this); //添加拖拽结束事件
setTimeout(this._dragStarted, 0); //pc拖拽事件
}
},
/***********************************************************************************************
*函数名 :_onDragOver
*函数功能描述 : 拖拽元素进进入拖拽区域, 判断拖拽节点与拖拽碰撞的节点,交换他们的dom节点位置,并执行动画。
*函数参数 :evt
*函数返回值 :
*作者 :
*函数创建日期 :
*函数修改日期 :
*修改人 :
*修改原因 :
*版本 :
*历史版本 :
***********************************************************************************************/
_onDragOver: function (/**Event*/evt) {
var el = this.el,
target,
dragRect,
revert,
options = this.options,
group = options.group,
groupPut = group.put,
isOwner = (activeGroup === group),
canSort = options.sort;
if (evt.preventDefault !== void 0) {
evt.preventDefault(); //阻止默认事件
!options.dragoverBubble && evt.stopPropagation();//终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播
}
moved = true;
//activeGroup={name: "words", pull: true, put: true}
//activeGroup=true
//options.disabled=false
//isOwner=true 因为isOwner=true 则执行canSort || (revert = !rootEl.contains(dragEl))
//如果父节点包含子节点则返回true ,contains,所以当canSort 是假时候(revert = !rootEl.contains(dragEl)
//revert = !rootEl.contains(dragEl) 取反赋值
//这里的if需要一个假才能拖拽
//(activeGroup.name === group.name) ==true;
//(evt.rootEl === void 0 || evt.rootEl === this.el) ==true
//所以 该功能是 给设置sort参数提供的
if (
activeGroup &&
!options.disabled &&
(
isOwner? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
: activeGroup.pull && groupPut && (
(activeGroup.name === group.name) || // by Name
(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
)
) &&