之前寫過一篇可拖動的DIV講如何實現可拖動的元素,最后提出了幾點不足,這篇文章主要就是回答着三個問題
1. 瀏覽器兼容性
2. 邊界檢查
3. 拖動卡頓、失靈
先附上上次代碼

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Test</title> 5 <style type="text/css" > 6 html,body 7 { 8 height:100%; 9 width:100%; 10 padding:0; 11 margin:0; 12 } 13 14 .dialog 15 { 16 width:250px; 17 height:250px; 18 position:absolute; 19 background-color:#ccc; 20 -webkit-box-shadow:1px 1px 3px #292929; 21 -moz-box-shadow:1px 1px 3px #292929; 22 box-shadow:1px 1px 3px #292929; 23 margin:10px; 24 } 25 26 .dialog-title 27 { 28 color:#fff; 29 background-color:#404040; 30 font-size:12pt; 31 font-weight:bold; 32 padding:4px 6px; 33 cursor:move; 34 } 35 36 .dialog-content 37 { 38 padding:4px; 39 } 40 </style> 41 </head> 42 <body> 43 <div id="dlgTest" class="dialog"> 44 <div class="dialog-title">Dialog</div> 45 <div class="dialog-content"> 46 This is a draggable test. 47 </div> 48 </div> 49 <script type="text/javascript"> 50 var Dragging=function(validateHandler){ //參數為驗證點擊區域是否為可移動區域,如果是返回欲移動元素,負責返回null 51 var draggingObj=null; //dragging Dialog 52 var diffX=0; 53 var diffY=0; 54 55 function mouseHandler(e){ 56 switch(e.type){ 57 case 'mousedown': 58 draggingObj=validateHandler(e);//驗證是否為可點擊移動區域 59 if(draggingObj!=null){ 60 diffX=e.clientX-draggingObj.offsetLeft; 61 diffY=e.clientY-draggingObj.offsetTop; 62 } 63 break; 64 65 case 'mousemove': 66 if(draggingObj){ 67 draggingObj.style.left=(e.clientX-diffX)+'px'; 68 draggingObj.style.top=(e.clientY-diffY)+'px'; 69 } 70 break; 71 72 case 'mouseup': 73 draggingObj =null; 74 diffX=0; 75 diffY=0; 76 break; 77 } 78 }; 79 80 return { 81 enable:function(){ 82 document.addEventListener('mousedown',mouseHandler); 83 document.addEventListener('mousemove',mouseHandler); 84 document.addEventListener('mouseup',mouseHandler); 85 }, 86 disable:function(){ 87 document.removeEventListener('mousedown',mouseHandler); 88 document.removeEventListener('mousemove',mouseHandler); 89 document.removeEventListener('mouseup',mouseHandler); 90 } 91 } 92 } 93 94 function getDraggingDialog(e){ 95 var target=e.target; 96 while(target && target.className.indexOf('dialog-title')==-1){ 97 target=target.offsetParent; 98 } 99 if(target!=null){ 100 return target.offsetParent; 101 }else{ 102 return null; 103 } 104 } 105 106 Dragging(getDraggingDialog).enable(); 107 </script> 108 </body> 109 </html>
瀏覽器兼容性
這個是最好解決的問題了,看看上面代碼涉及到瀏覽器兼容性的地方無非就是event獲取及事件源獲取、事件綁定,為此特意寫兩個函數來做此事。
1 function addEvent(element, type, key, handler) {//綁定事件處理程序 2 if (element[type + key]) 3 return false; 4 if (typeof element.addEventListener != "undefined") { 5 element[type + key] = handler; 6 element.addEventListener(type, handler, false); 7 } 8 else { 9 element['e' + type + key] = handler; 10 element[type + key] = function () { 11 element['e' + type + key](window.event); //解決IE瀏覽器event及this問題 12 }; 13 element.attachEvent('on' + type, element[type + key]); 14 } 15 return true; 16 } 17 18 function removeEvent(element, type, key) {//移除事件 19 if (!element[type + key]) 20 return false; 21 22 if (typeof element.removeEventListener != "undefined") { 23 element.removeEventListener(type, element[type + key], false); 24 } 25 else { 26 element.detachEvent("on" + type, element[type + key]); 27 element['e' + type + key] = null; 28 } 29 30 element[type + key] = null; 31 return true; 32 }
使用這兩個函數用於添加和移除事件,就可以解決瀏覽器兼容性問題,有興趣的同學可以研究一下,這是根據jQuery作者John Resig寫法的改版,參數key是綁定函數的自定義唯一標識,用於removeEvent時取消綁定,改版后代碼是這樣
1 var Dragging = function (validateHandler) { //參數為驗證點擊區域是否為可移動區域,如果是返回欲移動元素,負責返回null 2 var draggingObj = null; //dragging Dialog 3 var diffX = 0; 4 var diffY = 0; 5 6 function mouseHandler(e) { 7 switch (e.type) { 8 case 'mousedown': 9 draggingObj = validateHandler(e);//驗證是否為可點擊移動區域 10 if (draggingObj != null) { 11 diffX = e.clientX - draggingObj.offsetLeft; 12 diffY = e.clientY - draggingObj.offsetTop; 13 } 14 break; 15 16 case 'mousemove': 17 if (draggingObj) { 18 draggingObj.style.left = (e.clientX - diffX) + 'px'; 19 draggingObj.style.top = (e.clientY - diffY) + 'px'; 20 } 21 break; 22 23 case 'mouseup': 24 draggingObj = null; 25 diffX = 0; 26 diffY = 0; 27 break; 28 } 29 }; 30 31 return { 32 enable: function () { 33 addEvent(document, 'mousedown', 'drag-down', mouseHandler); 34 addEvent(document, 'mousemove', 'drag-move', mouseHandler); 35 addEvent(document, 'mouseup', 'drag-up', mouseHandler); 36 }, 37 disable: function () { 38 removeEvent(document, 'mousedown', 'drag-down'); 39 removeEvent(document, 'mousemove', 'drag-move'); 40 removeEvent(document, 'mouseup', 'drag-up'); 41 } 42 } 43 } 44 45 function getDraggingDialog(e) { 46 var target = e && e.target ? e.target : window.event.srcElement; 47 while (target && target.className.indexOf('dialog-title') == -1) { 48 target = target.offsetParent; 49 } 50 if (target != null) { 51 return target.offsetParent; 52 } else { 53 return null; 54 } 55 } 56 57 Dragging(getDraggingDialog).enable();
邊界處理
這個問題說起來也簡單,可以在函數調用的時候傳入邊界值,每次移動的時候判斷是否出了邊界,這樣改動一下
1 var Dragging = function (conf) { //參數為驗證點擊區域是否為可移動區域,如果是返回欲移動元素,負責返回null 2 var draggingObj = null; //dragging Dialog 3 var diffX = 0, diffY = 0; 4 5 var minX = conf.left != undefined ? conf.left : Number.NEGATIVE_INFINITY; 6 var maxX = conf.right != undefined ? conf.right : Number.POSITIVE_INFINITY; 7 var minY = conf.top != undefined ? conf.top : Number.NEGATIVE_INFINITY; 8 var maxY = conf.bottom != undefined ? conf.bottom : Number.POSITIVE_INFINITY; 9 10 var draggingObjWidth = 0, 11 draggingObjHeight = 0; 12 13 function mouseHandler(e) { 14 switch (e.type) { 15 case 'mousedown': 16 draggingObj = conf.validateHandler(e);//驗證是否為可點擊移動區域 17 if (draggingObj != null) { 18 diffX = e.clientX - draggingObj.offsetLeft; 19 diffY = e.clientY - draggingObj.offsetTop; 20 var size = draggingObj.getBoundingClientRect(); 21 draggingObjWidth = size.right - size.left; 22 draggingObjHeight = size.bottom - size.top; 23 } 24 break; 25 26 case 'mousemove': 27 if (draggingObj) { 28 var x = e.clientX - diffX; 29 var y = e.clientY - diffY; 30 if (x > minX && x < maxX - draggingObjWidth) { 31 draggingObj.style.left = x + 'px'; 32 } 33 if (y > minY && y < maxY - draggingObjHeight) { 34 draggingObj.style.top = y + 'px'; 35 } 36 } 37 break; 38 39 case 'mouseup': 40 draggingObj = null; 41 diffX = 0; 42 diffY = 0; 43 break; 44 } 45 }; 46 47 return { 48 enable: function () { 49 addEvent(document, 'mousedown', 'drag-down', mouseHandler); 50 addEvent(document, 'mousemove', 'drag-move', mouseHandler); 51 addEvent(document, 'mouseup', 'drag-up', mouseHandler); 52 }, 53 disable: function () { 54 removeEvent(document, 'mousedown', 'drag-down'); 55 removeEvent(document, 'mousemove', 'drag-move'); 56 removeEvent(document, 'mouseup', 'drag-up'); 57 } 58 } 59 } 60 61 function getDraggingDialog(e) { 62 var target = e && e.target ? e.target : window.event.srcElement; 63 while (target && target.className.indexOf('dialog-title') == -1) { 64 target = target.offsetParent; 65 } 66 if (target != null) { 67 return target.offsetParent; 68 } else { 69 return null; 70 } 71 } 72 73 var config = { 74 validateHandler: getDraggingDialog, 75 top: document.documentElement.clientTop, 76 right: document.documentElement.clientWidth, 77 bottom: document.documentElement.clientHeight, 78 left: document.documentElement.clientLeft 79 } 80 81 Dragging(config).enable();
如果希望Dialog只能在可視窗口拖動,就可以像上面那樣對config參數自定義四個邊界值,如果仍然希望沒有邊界的拖動,則可以四個邊界問題不處理,但是validateHandler屬性是必須的。
拖動卡頓、失效
關於拖動卡頓在復雜的頁面有位明顯,一個重要原因就是拖動的時候計算太多導致,不要以為在若動的時候頁面就僅僅處理拖動部分的代碼,沒拖動細微的一下頁面都要進行reflow,計算布局所有頁面元素的位置,所以復雜的頁面自然會卡頓,我們能夠處理的只能是是代碼的計算盡量簡單,為了防止誤導讀者,我在上面的版本中其實已經做了此項工作,把能夠提前計算的的變量值盡量都在函數初始化、mousedown的時候做,再就是盡量使用值變量,避免JavaScript[頻繁層層搜索變量引用,看一下低效版的拖動(可不要學會)
1 function mouseHandler(e) { 2 switch (e.type) { 3 case 'mousedown': 4 draggingObj = conf.validateHandler(e);//驗證是否為可點擊移動區域 5 break; 6 7 case 'mousemove': 8 if (draggingObj) { 9 diffX = e.clientX - draggingObj.offsetLeft; //如果這兩句也不定義變量,每次使用都要取event的屬性值和draggingObj的屬性值 10 diffY = e.clientY - draggingObj.offsetTop; 11 var size = draggingObj.getBoundingClientRect(); //每移動一下都要算一下大小,實際沒必要,拖動不不會改變元素大小 12 13 if ((e.clientX - diffX) > minX && (e.clientX - diffX) < maxX - (size.right - size.left)) {//每次都要再算兩遍e.clientX - diffX 14 draggingObj.style.left = x + 'px'; 15 } 16 if ((e.clientY - diffY) > minY && (e.clientY - diffY) < maxY - (size.bottom - size.top)) {//每次都要再算兩遍e.clientY - diffY 17 draggingObj.style.top = y + 'px'; 18 } 19 } 20 break; 21 22 case 'mouseup': 23 draggingObj = null; 24 diffX = 0; 25 diffY = 0; 26 minX = 0; 27 break; 28 } 29 };
有同學會說了你都處理了為什么每次還是會拖着拖着就鼠標就出去了,然后就失效了呢?仔細看看每次失效的時候頁面上中會伴隨着文字被選中,而且仔細觀察這個真的會影響拖動,處理一下,拖動的時候不允許選中文字
.disable-select * { -moz-user-select: none; -ms-user-select: none; -webkit-user-select: none; user-select: none; }
1 function mouseHandler(e) { 2 switch (e.type) { 3 case 'mousedown': 4 draggingObj = conf.validateHandler(e);//驗證是否為可點擊移動區域 5 if (draggingObj != null) { 6 diffX = e.clientX - draggingObj.offsetLeft; 7 diffY = e.clientY - draggingObj.offsetTop; 8 9 var size = draggingObj.getBoundingClientRect(); 10 draggingObjWidth = size.right - size.left; 11 draggingObjHeight = size.bottom - size.top; 12 document.body.className += ' disable-select'; //禁止選中 13 document.body.onselectstart = function () { return false; }; 14 } 15 break; 16 17 case 'mousemove': 18 if (draggingObj) { 19 var x = e.clientX - diffX; 20 var y = e.clientY - diffY; 21 if (x > minX && x < maxX - draggingObjWidth) { 22 draggingObj.style.left = x + 'px'; 23 } 24 if (y > minY && y < maxY - draggingObjHeight) { 25 draggingObj.style.top = y + 'px'; 26 } 27 } 28 break; 29 30 case 'mouseup': 31 draggingObj = null; 32 diffX = 0; 33 diffY = 0; 34 document.body.className = document.body.className.replace(' disable-select', ''); 35 document.body.onselectstart = null; 36 break; 37 } 38 };
最后
最后的源碼就是這樣的

1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Test</title> 5 <style type="text/css"> 6 html, body 7 { 8 height: 100%; 9 width: 100%; 10 padding: 0; 11 margin: 0; 12 } 13 14 .dialog 15 { 16 width: 250px; 17 height: 250px; 18 position: absolute; 19 background-color: #ccc; 20 -webkit-box-shadow: 1px 1px 3px #292929; 21 -moz-box-shadow: 1px 1px 3px #292929; 22 box-shadow: 1px 1px 3px #292929; 23 } 24 25 .dialog-title 26 { 27 color: #fff; 28 background-color: #404040; 29 font-size: 12pt; 30 font-weight: bold; 31 padding: 4px 6px; 32 cursor: move; 33 } 34 35 .dialog-content 36 { 37 padding: 4px; 38 } 39 40 .disable-select * 41 { 42 -moz-user-select: none; 43 -ms-user-select: none; 44 -webkit-user-select: none; 45 user-select: none; 46 } 47 </style> 48 </head> 49 <body> 50 <div id="dlgTest" class="dialog"> 51 <div class="dialog-title">Dialog</div> 52 <div class="dialog-content"> 53 This is a draggable test. 54 </div> 55 </div> 56 57 <script type="text/javascript"> 58 function addEvent(element, type, key, handler) {//綁定事件處理程序 59 if (element[type + key]) 60 return false; 61 if (typeof element.addEventListener != "undefined") { 62 element[type + key] = handler; 63 element.addEventListener(type, handler, false); 64 } 65 else { 66 element['e' + type + key] = handler; 67 element[type + key] = function () { 68 element['e' + type + key](window.event); //解決IE瀏覽器event及this問題 69 }; 70 element.attachEvent('on' + type, element[type + key]); 71 } 72 return true; 73 } 74 75 function removeEvent(element, type, key) {//移除事件 76 if (!element[type + key]) 77 return false; 78 79 if (typeof element.removeEventListener != "undefined") { 80 element.removeEventListener(type, element[type + key], false); 81 } 82 else { 83 element.detachEvent("on" + type, element[type + key]); 84 element['e' + type + key] = null; 85 } 86 87 element[type + key] = null; 88 return true; 89 } 90 </script> 91 92 <script type="text/javascript"> 93 var Dragging = function (conf) { //參數為驗證點擊區域是否為可移動區域,如果是返回欲移動元素,負責返回null 94 var draggingObj = null; //dragging Dialog 95 var diffX = 0, diffY = 0; 96 97 var minX = conf.left != undefined ? conf.left : Number.NEGATIVE_INFINITY; 98 var maxX = conf.right != undefined ? conf.right : Number.POSITIVE_INFINITY; 99 var minY = conf.top != undefined ? conf.top : Number.NEGATIVE_INFINITY; 100 var maxY = conf.bottom != undefined ? conf.bottom : Number.POSITIVE_INFINITY; 101 102 var draggingObjWidth = 0, 103 draggingObjHeight = 0; 104 105 function mouseHandler(e) { 106 switch (e.type) { 107 case 'mousedown': 108 draggingObj = conf.validateHandler(e);//驗證是否為可點擊移動區域 109 if (draggingObj != null) { 110 diffX = e.clientX - draggingObj.offsetLeft; 111 diffY = e.clientY - draggingObj.offsetTop; 112 113 var size = draggingObj.getBoundingClientRect(); 114 draggingObjWidth = size.right - size.left; 115 draggingObjHeight = size.bottom - size.top; 116 document.body.className += ' disable-select'; //禁止選中 117 document.body.onselectstart = function () { return false; }; 118 } 119 break; 120 121 case 'mousemove': 122 if (draggingObj) { 123 var x = e.clientX - diffX; 124 var y = e.clientY - diffY; 125 if (x > minX && x < maxX - draggingObjWidth) { 126 draggingObj.style.left = x + 'px'; 127 } 128 if (y > minY && y < maxY - draggingObjHeight) { 129 draggingObj.style.top = y + 'px'; 130 } 131 } 132 break; 133 134 case 'mouseup': 135 draggingObj = null; 136 diffX = 0; 137 diffY = 0; 138 document.body.className = document.body.className.replace(' disable-select',''); 139 document.body.onselectstart = null; 140 break; 141 } 142 }; 143 144 return { 145 enable: function () { 146 addEvent(document, 'mousedown', 'drag-down', mouseHandler); 147 addEvent(document, 'mousemove', 'drag-move', mouseHandler); 148 addEvent(document, 'mouseup', 'drag-up', mouseHandler); 149 }, 150 disable: function () { 151 removeEvent(document, 'mousedown', 'drag-down'); 152 removeEvent(document, 'mousemove', 'drag-move'); 153 removeEvent(document, 'mouseup', 'drag-up'); 154 } 155 } 156 } 157 158 function getDraggingDialog(e) { 159 var target = e && e.target ? e.target : window.event.srcElement; 160 while (target && target.className.indexOf('dialog-title') == -1) { 161 target = target.offsetParent; 162 } 163 if (target != null) { 164 return target.offsetParent; 165 } else { 166 return null; 167 } 168 } 169 170 var config = { 171 validateHandler: getDraggingDialog, 172 top: document.documentElement.clientTop, 173 right: document.documentElement.clientWidth, 174 bottom: document.documentElement.clientHeight, 175 left: document.documentElement.clientLeft 176 }; 177 178 Dragging(config).enable(); 179 </script> 180 </body> 181 </html>
試試真的好了很多,然而鼠標要是移動的快還是會拖離,以為就是這樣了呢,但試了試jQuery的Dialog控件,拖動基本流暢,這讓人情何以堪,今天天氣好,出去找妹子了,改天研究研究jQuery是怎么寫的吧