一步一步實現JS拖拽插件


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM