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,onmouseover,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来处理这个问题。
四、扩展:有效的拖拽元素
我们常见的一些拖拽效果很有可能是这样的:
弹框的顶部是可以进行拖拽操作的,内容区域是不可拖拽的,怎么实现这样的效果呢:
首先优化拖拽元素对象如下,增加一个目标元素target,表示被拖拽对象,在上图的登录框中,就是整个登录窗口。
被记录和设置坐标的拖拽元素就是这个目标元素,但是它并不是整个部分都是拖拽的有效部分。我们在html结构中为拖拽的有效区域添加类draggable表示有效拖拽区域:
1 <div id="obj1" class="dialog" style="position:relative;left:50px">
2 <div class="header draggable">
3 拖拽的有效元素
4 </div>
5 <div class="content">
6 拖拽对象1
7 </div>
8 </div>
然后修改Drag方法如下:
function drag(ele) {
var dragNode = (ele.querySelector(".draggable") || ele);
dom.on(dragNode, "mousedown", function (event) {
var dragElement = draggableConfig.dragElement = new DragElement(ele);
draggableConfig.mouse.setXY(event.clientX, event.clientY);
draggableConfig.dragElement
.setXY(dragElement.target.style.left, dragElement.target.style.top)
.setTargetCss({
"zIndex": draggableConfig.zIndex++,
"position": "relative"
});
}).on(dragNode, "mouseover", function () {
dom.setCss(this, draggableStyle.dragging);
}).on(dragNode, "mouseout", function () {
dom.setCss(this, draggableStyle.defaults);
});
}
主要修改的是绑定mousedown的节点变成了包含draggable类的有效元素,如果不含有draggable,则整个元素都是有效元素。
五、性能优化和总结
由于onmousemove在一直调用,会造成一些性能问题,我们可以通过setTimout来延迟绑定onmousemove事件,改进move函数如下
1 function move(event) {
2 if (draggableConfig.dragElement) {
3 var mouse = draggableConfig.mouse,
4 dragElement = draggableConfig.dragElement;
5 dragElement.setTargetCss({
6 "left": parseInt(event.clientX - mouse.x + dragElement.x) + "px",
7 "top": parseInt(event.clientY - mouse.y + dragElement.y) + "px"
8 });
9
10 dom.off(document, "mousemove", move);
11 setTimeout(function () {
12 dom.on(document, "mousemove", move);
13 }, 25);
14 }
15 }
总结:
整个拖拽插件的实现其实很简单,主要是要注意几点
1、实现思路:元素拖拽位置的改变就等于鼠标改变的距离,关键在于获取鼠标的变动和元素原本的坐标
2、通过setTimeout来延迟加载onmousemove事件来提供性能
六、jquery插件化
简单地将其封装成jquery插件,主要是相关的dom方法替换成jquery方法来操作
1 ; (function ($, window, undefined) {
2 //#region 拖拽元素类
3 function DragElement(node) {
4
5 this.target = node;
6
7 node.onselectstart = function () {
8 //防止拖拽对象内的文字被选中
9 return false;
10 }
11 }
12 DragElement.prototype = {
13 constructor: DragElement,
14 setXY: function (x, y) {
15 this.x = parseInt(x) || 0;
16 this.y = parseInt(y) || 0;
17 return this;
18 },
19 setTargetCss: function (css) {
20 $(this.target).css(css);
21 return this;
22 }
23 }
24 //#endregion
25
26 //#region 鼠标元素
27 function Mouse() {
28 this.x = 0;
29 this.y = 0;
30 }
31 Mouse.prototype.setXY = function (x, y) {
32 this.x = parseInt(x);
33 this.y = parseInt(y);
34 }
35 //#endregion
36
37 //拖拽配置
38 var draggableConfig = {
39 zIndex: 1,
40 dragElement: null,
41 mouse: new Mouse()
42 };
43
44 var draggableStyle = {
45 dragging: {
46 cursor: "move"
47 },
48 defaults: {
49 cursor: "default"
50 }
51 }
52
53 var $document = $(document);
54
55 function drag($ele) {
56 var $dragNode = $ele.find(".draggable");
57 $dragNode = $dragNode.length > 0 ? $dragNode : $ele;
58
59
60 $dragNode.on({
61 "mousedown": function (event) {
62 var dragElement = draggableConfig.dragElement = new DragElement($ele.get(0));
63
64 draggableConfig.mouse.setXY(event.clientX, event.clientY);
65 draggableConfig.dragElement
66 .setXY(dragElement.target.style.left, dragElement.target.style.top)
67 .setTargetCss({
68 "zIndex": draggableConfig.zIndex++,
69 "position": "relative"
70 });
71 },
72 "mouseover": function () {
73 $(this).css(draggableStyle.dragging);
74 },
75 "mouseout": function () {
76 $(this).css(draggableStyle.defaults);
77 }
78 })
79 }
80
81 function move(event) {
82 if (draggableConfig.dragElement) {
83 var mouse = draggableConfig.mouse,
84 dragElement = draggableConfig.dragElement;
85 dragElement.setTargetCss({
86 "left": parseInt(event.clientX - mouse.x + dragElement.x) + "px",
87 "top": parseInt(event.clientY - mouse.y + dragElement.y) + "px"
88 });
89
90 $document.off("mousemove", move);
91 setTimeout(function () {
92 $document.on("mousemove", move);
93 }, 25);
94 }
95 }
96
97 $document.on({
98 "mousemove": move,
99 "mouseup": function () {
100 draggableConfig.dragElement = null;
101 }
102 });
103
104 $.fn.drag = function (options) {
105 drag(this);
106 }
107
108 })(jQuery, window, undefined)
点击下载DEMO