├── README.md ├── Sortable.js ├── demo1.html ├── demo2.html ├── demo3.html └── demo5.html /README.md: -------------------------------------------------------------------------------- 1 | # Sortable 2 | Sortable 源码逐行分析 3 | -------------------------------------------------------------------------------- /Sortable.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * Sortable 3 | * @author RubaXa 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("
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 |
列表1
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 | 2137 | 2138 | // 获取模板 2139 | 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 | }); -------------------------------------------------------------------------------- /demo1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 无标题文档 6 | 7 | 8 | 9 | 10 | 20 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /demo2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 无标题文档 6 | 7 | 8 | 9 | 10 | 20 | 21 | 27 | 28 | -------------------------------------------------------------------------------- /demo3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 无标题文档 6 | 7 | 8 | 9 | 10 | 20 | 43 | 44 | -------------------------------------------------------------------------------- /demo5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 无标题文档 6 | 7 | 8 | 9 | 10 | 11 | 21 | 51 | 52 | --------------------------------------------------------------------------------