目录
 
一、js拖拽插件的原理
二、根据原理实现的最基本效果
三、代码抽象与优化
四、扩展:有效的拖拽元素
五、性能优化和总结
六、jquery插件化
js拖拽是常见的网页效果,本文将从零开始实现一个简单的js插件。
 
回到顶部
一、js拖拽插件的原理
常见的拖拽操作是什么样的呢?整过过程大概有下面几个步骤:
 
  1、用鼠标点击被拖拽的元素
 
  2、按住鼠标不放,移动鼠标
 
  3、拖拽元素到一定位置,放开鼠标
 
这里的过程涉及到三个dom事件:onmousedown,onmousemove,onmouseup。所以拖拽的基本思路就是:
 
  1、用鼠标点击被拖拽的元素触发onmousedown
 
    (1)设置当前元素的可拖拽为true,表示可以拖拽
 
    (2)记录当前鼠标的坐标x,y
 
    (3)记录当前元素的坐标x,y
 
  2、移动鼠标触发onmousemove
 
    (1)判断元素是否可拖拽,如果是则进入步骤2,否则直接返回
 
    (2)如果元素可拖拽,则设置元素的坐标
 
      元素的x坐标 = 鼠标移动的横向距离+元素本来的x坐标 = 鼠标现在的x坐标 - 鼠标之前的x坐标 + 元素本来的x坐标
 
      元素的y坐标 = 鼠标移动的横向距离+元素本来的y坐标 = 鼠标现在的y坐标 - 鼠标之前的y坐标 + 元素本来的y坐标
 
  3、放开鼠标触发onmouseup
 
    (1)将鼠标的可拖拽状态设置成false
 
回到顶部
二、根据原理实现的最基本效果
在实现基本的效果之前,有几点需要说明的:
 
  1、元素想要被拖动,它的postion属性一定要是relative或absolute
 
  2、通过event.clientX和event.clientY获取鼠标的坐标
 
  3、onmousemove是绑定在document元素上而不是拖拽元素本身,这样能解决快速拖动造成的延迟或停止移动的问题
 
代码如下:
 
 
 1 var dragObj = document.getElementById("test");
 2         dragObj.style.left = "0px";
 3         dragObj.style.top = "0px";
 4 
 5         var mouseX, mouseY, objX, objY;
 6         var dragging = false;
 7 
 8         dragObj.onmousedown = function (event) {
 9             event = event || window.event;
10 
11             dragging = true;
12             dragObj.style.position = "relative";
13 
14 
15             mouseX = event.clientX;
16             mouseY = event.clientY;
17             objX = parseInt(dragObj.style.left);
18             objY = parseInt(dragObj.style.top);
19         }
20 
21         document.onmousemove = function (event) {
22             event = event || window.event;
23             if (dragging) {
24 
25                 dragObj.style.left = parseInt(event.clientX - mouseX + objX) + "px";
26                 dragObj.style.top = parseInt(event.clientY - mouseY + objY) + "px";
27             }
28 
29         }
30 
31         document.onmouseup = function () {
32             dragging = false;
33         }
 

 

 
 
 
回到顶部
三、代码抽象与优化
上面的代码要做成插件,要将其抽象出来,基本结构如下:
 
 
1         ; (function (window, undefined) {            
2 
3             function Drag(ele) {}
4 
5             window.Drag = Drag;
6         })(window, undefined);

 

 
用自执行匿名函数将代码包起来,内部定义Drag方法并暴露到全局中,直接调用Drag,传入被拖拽的元素。
 
首先对一些常用的方法进行简单的封装:
 
 
 1         ; (function (window, undefined) {
 2             var dom = {
 3                 //绑定事件
 4                 on: function (node, eventName, handler) {
 5                     if (node.addEventListener) {
 6                         node.addEventListener(eventName, handler);
 7                     }
 8                     else {
 9                         node.attachEvent("on" + eventName, handler);
10                     }
11                 },
12                 //获取元素的样式
13                 getStyle: function (node, styleName) {
14                     var realStyle = null;
15                     if (window.getComputedStyle) {
16                         realStyle = window.getComputedStyle(node, null)[styleName];
17                     }
18                     else if (node.currentStyle) {
19                         realStyle = node.currentStyle[styleName];
20                     }
21                     return realStyle;
22                 },
23                 //获取设置元素的样式
24                 setCss: function (node, css) {
25                     for (var key in css) {
26                         node.style[key] = css[key];
27                     }
28                 }
29             };
30           
31             window.Drag = Drag;
32         })(window, undefined);

 

 
 
在一个拖拽操作中,存在着两个对象:被拖拽的对象和鼠标对象,我们定义了下面的两个对象以及它们对应的操作:
 
首先的拖拽对象,它包含一个元素节点和拖拽之前的坐标x和y:
 
 
 1             function DragElement(node) {
 2                 this.node = node;//被拖拽的元素节点
 3                 this.x = 0;//拖拽之前的x坐标
 4                 this.y = 0;//拖拽之前的y坐标
 5             }
 6             DragElement.prototype = {
 7                 constructor: DragElement,
 8                 init: function () {                    
 9                     this.setEleCss({
10                         "left": dom.getStyle(node, "left"),
11                         "top": dom.getStyle(node, "top")
12                     })
13                     .setXY(node.style.left, node.style.top);
14                 },
15                 //设置当前的坐标
16                 setXY: function (x, y) {
17                     this.x = parseInt(x) || 0;
18                     this.y = parseInt(y) || 0;
19                     return this;
20                 },
21                 //设置元素节点的样式
22                 setEleCss: function (css) {
23                     dom.setCss(this.node, css);
24                     return this;
25                 }
26             }
 

 

 
 
 
还有一个对象是鼠标,它主要包含x坐标和y坐标:
 
 
1             function Mouse() {
2                 this.x = 0;
3                 this.y = 0;
4             }
5             Mouse.prototype.setXY = function (x, y) {
6                 this.x = parseInt(x);
7                 this.y = parseInt(y);
8             }        
 

 

这是在拖拽操作中定义的两个对象。
 
 
 
如果一个页面可以有多个拖拽元素,那应该注意什么:
 
1、每个元素对应一个拖拽对象实例
 
2、每个页面只能有一个正在拖拽中的元素
 
为此,我们定义了唯一一个对象用来保存相关的配置:
 
 
1             var draggableConfig = {
2                 zIndex: 1,
3                 draggingObj: null,
4                 mouse: new Mouse()
5             };

 

 
这个对象中有三个属性:
 
(1)zIndex:用来赋值给拖拽对象的zIndex属性,有多个拖拽对象时,当两个拖拽对象重叠时,会造成当前拖拽对象有可能被挡住,通过设置zIndex使其显示在最顶层
 
(2)draggingObj:用来保存正在拖拽的对象,在这里去掉了前面的用来判断是否可拖拽的变量,通过draggingObj来判断当前是否可以拖拽以及获取相应的拖拽对象
 
(3)mouse:唯一的鼠标对象,用来保存当前鼠标的坐标等信息
 
 
 
最后是绑定onmousedown,onmouver,onmouseout事件,整合上面的代码如下:
 
 
  1         ; (function (window, undefined) {
  2             var dom = {
  3                 //绑定事件
  4                 on: function (node, eventName, handler) {
  5                     if (node.addEventListener) {
  6                         node.addEventListener(eventName, handler);
  7                     }
  8                     else {
  9                         node.attachEvent("on" + eventName, handler);
 10                     }
 11                 },
 12                 //获取元素的样式
 13                 getStyle: function (node, styleName) {
 14                     var realStyle = null;
 15                     if (window.getComputedStyle) {
 16                         realStyle = window.getComputedStyle(node, null)[styleName];
 17                     }
 18                     else if (node.currentStyle) {
 19                         realStyle = node.currentStyle[styleName];
 20                     }
 21                     return realStyle;
 22                 },
 23                 //获取设置元素的样式
 24                 setCss: function (node, css) {
 25                     for (var key in css) {
 26                         node.style[key] = css[key];
 27                     }
 28                 }
 29             };
 30 
 31             //#region 拖拽元素类
 32             function DragElement(node) {
 33                 this.node = node;
 34                 this.x = 0;
 35                 this.y = 0;
 36             }
 37             DragElement.prototype = {
 38                 constructor: DragElement,
 39                 init: function () {                    
 40                     this.setEleCss({
 41                         "left": dom.getStyle(node, "left"),
 42                         "top": dom.getStyle(node, "top")
 43                     })
 44                     .setXY(node.style.left, node.style.top);
 45                 },
 46                 setXY: function (x, y) {
 47                     this.x = parseInt(x) || 0;
 48                     this.y = parseInt(y) || 0;
 49                     return this;
 50                 },
 51                 setEleCss: function (css) {
 52                     dom.setCss(this.node, css);
 53                     return this;
 54                 }
 55             }
 56             //#endregion
 57 
 58             //#region 鼠标元素
 59             function Mouse() {
 60                 this.x = 0;
 61                 this.y = 0;
 62             }
 63             Mouse.prototype.setXY = function (x, y) {
 64                 this.x = parseInt(x);
 65                 this.y = parseInt(y);
 66             }
 67             //#endregion
 68 
 69             //拖拽配置
 70             var draggableConfig = {
 71                 zIndex: 1,
 72                 draggingObj: null,
 73                 mouse: new Mouse()
 74             };
 75 
 76             function Drag(ele) {
 77                 this.ele = ele;
 78 
 79                 function mouseDown(event) {
 80                     var ele = event.target || event.srcElement;
 81 
 82                     draggableConfig.mouse.setXY(event.clientX, event.clientY);
 83 
 84                     draggableConfig.draggingObj = new DragElement(ele);
 85                     draggableConfig.draggingObj
 86                         .setXY(ele.style.left, ele.style.top)
 87                         .setEleCss({
 88                             "zIndex": draggableConfig.zIndex++,
 89                             "position": "relative"
 90                         });
 91                 }                
 92 
 93                 ele.onselectstart = function () {
 94                     //防止拖拽对象内的文字被选中
 95                     return false;
 96                 }
 97                 dom.on(ele, "mousedown", mouseDown);
 98             }
 99 
100             dom.on(document, "mousemove", function (event) {
101                 if (draggableConfig.draggingObj) {
102                     var mouse = draggableConfig.mouse,
103                         draggingObj = draggableConfig.draggingObj;
104                     draggingObj.setEleCss({
105                         "left": parseInt(event.clientX - mouse.x + draggingObj.x) + "px",
106                         "top": parseInt(event.clientY - mouse.y + draggingObj.y) + "px"
107                     });
108                 }
109             })
110 
111             dom.on(document, "mouseup", function (event) {
112                 draggableConfig.draggingObj = null;
113             })
114 
115 
116             window.Drag = Drag;
117         })(window, undefined);

 

 
调用方法:Drag(document.getElementById("obj"));
 
注意的一点,为了防止选中拖拽元素中的文字,通过onselectstart事件处理程序return false来处理这个问题。