可拖动的DIV续
之前写过一篇可拖动的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是怎么写的吧