对google个性主页的拖拽效果的js的完整注释(转载)
作者:Tin
原文地址:http://www.blogjava.net/iamtin/archive/2006/04/27/43668.html
代码下载:http://www.blogjava.net/Files/iamtin/google_drag.rar
1 // 工具类,使用Util的命名空间,方便管理 2 var Util = new Object(); 3 // 获取http header里面的UserAgent,浏览器信息 4 Util.getUserAgent = navigator.userAgent; 5 // 是否是Gecko核心的Browser,比如Mozila、Firefox 6 Util.isGecko = Util.getUserAgent.indexOf( " Gecko " ) != - 1 ; 7 // 是否是Opera 8 Util.isOpera = Util.getUserAgent.indexOf( " Opera " ) != - 1 ; 9 // 获取一个element的offset信息,其实就是相对于Body的padding以内的绝对坐标 10 // 后面一个参数如果是true则获取offsetLeft,false则是offsetTop 11 // 关于offset、style、client等坐标的定义参考dindin的这个帖子:http://www.jroller.com/page/dindin/?anchor=pro_javascript_12 12 Util.getOffset = function (el, isLeft) { 13 var retValue = 0 ; 14 while (el != null ) { 15 retValue += el[ " offset " + (isLeft ? " Left " : " Top " )]; 16 el = el.offsetParent; 17 } 18 return retValue; 19 }; 20 // 将一个function(参数中的funcName是这个fuction的名字)绑定到一个element上,并且以这个element的上下文运行,其实是一种继承,这个可以google些文章看看 21 Util.bindFunction = function (el, fucName) { 22 return function () { 23 return el[fucName].apply(el, arguments); 24 }; 25 }; 26 // 重新计算所有的可以拖拽的element的坐标,对同一个column下面的可拖拽图层重新计算它们的高度而得出新的坐标,防止遮叠 27 // 计算出来的坐标记录在pagePosLeft和pagePosTop两个属性里面 28 Util.re_calcOff = function (el) { 29 for ( var i = 0 ; i < Util.dragArray.length; i ++ ) { 30 var ele = Util.dragArray[i]; 31 ele.elm.pagePosLeft = Util.getOffset(ele.elm, true ); 32 ele.elm.pagePosTop = Util.getOffset(ele.elm, false ); 33 } 34 var nextSib = el.elm.nextSibling; 35 while (nextSib) { 36 nextSib.pagePosTop -= el.elm.offsetHeight; 37 nextSib = nextSib.nextSibling; 38 } 39 }; 40 41 // 隐藏Google Ig中间那个table,也就是拖拽的容器,配合show一般就是刷新用,解决一些浏览器的怪癖 42 Util.hide = function () { 43 Util.rootElement.style.display = " none " ; 44 }; 45 // 显示Google Ig中间那个table,解释同上 46 Util.show = function () { 47 Util.rootElement.style.display = "" ; 48 }; 49 50 // 移动时显示的占位虚线框 51 ghostElement = null ; 52 // 获取这个虚线框,通过dom动态生成 53 getGhostElement = function () { 54 if ( ! ghostElement) { 55 ghostElement = document.createElement( " DIV " ); 56 ghostElement.className = " modbox " ; 57 ghostElement.backgroundColor = "" ; 58 ghostElement.style.border = " 2px dashed #aaa " ; 59 ghostElement.innerHTML = " " ; 60 } 61 return ghostElement; 62 }; 63 64 // 初始化可以拖拽的Element的函数,与拖拽无关的我去掉了 65 function draggable(el) { 66 // 公用的开始拖拽的函数 67 this ._dragStart = start_Drag; 68 // 公用的正在拖拽的函数 69 this ._drag = when_Drag; 70 // 公用的拖拽结束的函数 71 this ._dragEnd = end_Drag; 72 // 这个函数主要用来进行拖拽结束后的dom处理 73 this ._afterDrag = after_Drag; 74 // 是否正在被拖动,一开始当然没有被拖动 75 this .isDragging = false ; 76 // 将这个Element的this指针注册在elm这个变量里面,方便在自己的上下文以外调用自己的函数等,很常用的方法 77 this .elm = el; 78 // 触发拖拽的Element,在这里就是这个div上显示标题的那个div 79 this .header = document.getElementById(el.id + " _h " ); 80 // 对于有iframe的element拖拽不同,这里检测一下并记录 81 this .hasIFrame = this .elm.getElementsByTagName( " IFRAME " ).length > 0 ; 82 // 如果找到了header就绑定drag相关的event 83 if ( this .header) { 84 // 拖拽时的叉子鼠标指针 85 this .header.style.cursor = " move " ; 86 // 将函数绑定到header和element的this上,参照那个函数的说明 87 Drag.init( this .header, this .elm); 88 // 下面三个语句将写好的三个函数绑定给这个elemnt的三个函数钩子上,也就实现了element从draggable继承可拖拽的函数 89 this .elm.onDragStart = Util.bindFunction( this , " _dragStart " ); 90 this .elm.onDrag = Util.bindFunction( this , " _drag " ); 91 this .elm.onDragEnd = Util.bindFunction( this , " _dragEnd " ); 92 } 93 }; 94 95 // 下面就是draggable里面用到的那4个function 96 // 公用的开始拖拽的函数 97 function start_Drag() { 98 // 重置坐标,实现拖拽以后自己的位置马上会被填充的效果 99 Util.re_calcOff( this ); 100 // 记录原先的邻居节点,用来对比是否被移动到新的位置 101 this .origNextSibling = this .elm.nextSibling; 102 // 获取移动的时候那个灰色的虚线框 103 var _ghostElement = getGhostElement(); 104 // 获取正在移动的这个对象的高度 105 var offH = this .elm.offsetHeight; 106 if (Util.isGecko) { 107 // 修正gecko引擎的怪癖吧 108 offH -= parseInt(_ghostElement.style.borderTopWidth) * 2 ; 109 } 110 // 获取正在移动的这个对象的宽度 111 var offW = this .elm.offsetWidth; 112 // 获取left和top的坐标 113 var offLeft = Util.getOffset( this .elm, true ); 114 var offTop = Util.getOffset( this .elm, false ); 115 // 防止闪烁,现隐藏 116 Util.hide(); 117 // 将自己的宽度记录在style属性里面 118 this .elm.style.width = offW + " px " ; 119 // 将那个灰框设定得与正在拖动的对象一样高,比较形象 120 _ghostElement.style.height = offH + " px " ; 121 // 把灰框放到这个对象原先的位置上 122 this .elm.parentNode.insertBefore(_ghostElement, this .elm.nextSibling); 123 // 由于要拖动必须将被拖动的对象从原先的盒子模型里面抽出来,所以设定position为absolute,这个可以参考一下css布局方面的知识 124 this .elm.style.position = " absolute " ; 125 // 设置zIndex,让它处在最前面一层,当然其实zIndex=100是让它很靠前,如果页面里有zIndex>100的,那…… 126 this .elm.style.zIndex = 100 ; 127 // 由于position=absolute了,所以left和top实现绝对坐标定位,这就是先前计算坐标的作用,不让这个模型乱跑,要从开始拖动的地方开始移动 128 this .elm.style.left = offLeft + " px " ; 129 this .elm.style.top = offTop + " px " ; 130 // 坐标设定完毕,可以显示了,这样就不会闪烁了 131 Util.show(); 132 // 这里本来有个ig_d.G,没搞明白干什么用的,不过没有也可以用,谁知道麻烦告诉我一声,不好意思 133 // 还没有开始拖拽,这里做个记号 134 this .isDragging = false ; 135 return false ; 136 }; 137 // 在拖拽时的相应函数,由于绑定到鼠标的move这个event上,所以会传入鼠标的坐标clientX, clientY 138 function when_Drag(clientX, clientY) { 139 // 刚开始拖拽的时候将图层变透明,并标记为正在被拖拽 140 if ( ! this .isDragging) { 141 this .elm.style.filter = " alpha(opacity=70) " ; 142 this .elm.style.opacity = 0.7 ; 143 this .isDragging = true ; 144 } 145 // 被拖拽到的新的column(当然也可以是原来那个) 146 var found = null ; 147 // 最大的距离,可能是防止溢出或者什么bug 148 var max_distance = 100000000 ; 149 // 遍历所有的可拖拽的element,寻找离当前鼠标坐标最近的那个可拖拽元素,以便后面插入 150 for ( var i = 0 ; i < Util.dragArray.length; i ++ ) { 151 var ele = Util.dragArray[i]; 152 // 利用勾股定理计算鼠标到遍历到的这个元素的距离 153 var distance = Math.sqrt(Math.pow(clientX - ele.elm.pagePosLeft, 2 ) + Math.pow(clientY - ele.elm.pagePosTop, 2 )); 154 // 自己已经浮动了,所以不计算自己的 155 if (ele == this ) { 156 continue ; 157 } 158 // 如果计算失败继续循环 159 if (isNaN(distance)) { 160 continue ; 161 } 162 // 如果更小,记录下这个距离,并将它作为found 163 if (distance < max_distance) { 164 max_distance = distance; 165 found = ele; 166 } 167 } 168 // 准备让灰框落脚 169 var _ghostElement = getGhostElement(); 170 // 如果找到了另外的落脚点 171 if (found != null && _ghostElement.nextSibling != found.elm) { 172 // 找到落脚点就先把灰框插进去,这就是我们看到的那个灰框停靠的特效,有点像吸附的感觉,哈哈 173 found.elm.parentNode.insertBefore(_ghostElement, found.elm); 174 if (Util.isOpera) { 175 // Opera的现实问题,要隐藏/显示后才能刷新出变化 176 document.body.style.display = " none " ; 177 document.body.style.display = "" ; 178 } 179 } 180 }; 181 // 拖拽完毕 182 function end_Drag() { 183 // 拖拽完毕后执行后面的钩子,执行after_Drag(),如果布局发生了变动了就记录到远程服务器,保存你拖拽后新的布局顺序 184 if ( this ._afterDrag()) { 185 // remote call to save the change 186 } 187 return true ; 188 }; 189 // 拖拽后的执行钩子 190 function after_Drag() { 191 var returnValue = false ; 192 // 防止闪烁 193 Util.hide(); 194 // 把拖拽时的position=absolute和相关的那些style都消除 195 this .elm.style.position = "" ; 196 this .elm.style.width = "" ; 197 this .elm.style.zIndex = "" ; 198 this .elm.style.filter = "" ; 199 this .elm.style.opacity = "" ; 200 // 获取灰框 201 var ele = getGhostElement(); 202 // 如果现在的邻居不是原来的邻居了 203 if (ele.nextSibling != this .origNextSibling) { 204 // 把被拖拽的这个节点插到灰框的前面 205 ele.parentNode.insertBefore( this .elm, ele.nextSibling); 206 // 标明被拖拽了新的地方 207 returnValue = true ; 208 } 209 // 移除灰框,这是这个灰框的生命周期应该就结束了 210 ele.parentNode.removeChild(ele); 211 // 修改完毕,显示 212 Util.show(); 213 if (Util.isOpera) { 214 // Opera的现实问题,要隐藏/显示后才能刷新出变化 215 document.body.style.display = " none " ; 216 document.body.style.display = "" ; 217 } 218 return returnValue; 219 }; 220 // 可拖拽Element的原形,用来将event绑定到各个钩子,这部分市比较通用的,netvibes也是基本完全相同的实现 221 // 这部分推荐看dindin的这个,也会帮助理解,http://www.jroller.com/page/dindin/?anchor=pro_javascript_12 222 var Drag = { 223 // 对这个element的引用,一次只能拖拽一个Element 224 obj: null , 225 // element是被拖拽的对象的引用,elementHeader就是鼠标可以拖拽的区域 226 init: function (elementHeader, element) { 227 // 将start绑定到onmousedown事件,按下鼠标触发start 228 elementHeader.onmousedown = Drag.start; 229 // 将element存到header的obj里面,方便header拖拽的时候引用 230 elementHeader.obj = element; 231 // 初始化绝对的坐标,因为不是position=absolute所以不会起什么作用,但是防止后面onDrag的时候parse出错了 232 if (isNaN(parseInt(element.style.left))) { 233 element.style.left = " 0px " ; 234 } 235 if (isNaN(parseInt(element.style.top))) { 236 element.style.top = " 0px " ; 237 } 238 // 挂上空Function,初始化这几个成员,在Drag.init被调用后才帮定到实际的函数,可以参照draggable里面的内容 239 element.onDragStart = new Function(); 240 element.onDragEnd = new Function(); 241 element.onDrag = new Function(); 242 }, 243 // 开始拖拽的绑定,绑定到鼠标的移动的event上 244 start: function (event) { 245 var element = Drag.obj = this .obj; 246 // 解决不同浏览器的event模型不同的问题 247 event = Drag.fixE(event); 248 // 看看是不是左键点击 249 if (event.which != 1 ) { 250 // 除了左键都不起作用 251 return true ; 252 } 253 // 参照这个函数的解释,挂上开始拖拽的钩子 254 element.onDragStart(); 255 // 记录鼠标坐标 256 element.lastMouseX = event.clientX; 257 element.lastMouseY = event.clientY; 258 // 将Global的event绑定到被拖动的element上面来 259 document.onmouseup = Drag.end; 260 document.onmousemove = Drag.drag; 261 return false ; 262 }, 263 // Element正在被拖动的函数 264 drag: function (event) { 265 // 解决不同浏览器的event模型不同的问题 266 event = Drag.fixE(event); 267 // 看看是不是左键点击 268 if (event.which == 0 ) { 269 // 除了左键都不起作用 270 return Drag.end(); 271 } 272 // 正在被拖动的Element 273 var element = Drag.obj; 274 // 鼠标坐标 275 var _clientX = event.clientY; 276 var _clientY = event.clientX; 277 // 如果鼠标没动就什么都不作 278 if (element.lastMouseX == _clientY && element.lastMouseY == _clientX) { 279 return false ; 280 } 281 // 刚才Element的坐标 282 var _lastX = parseInt(element.style.top); 283 var _lastY = parseInt(element.style.left); 284 // 新的坐标 285 var newX, newY; 286 // 计算新的坐标:原先的坐标+鼠标移动的值差 287 newX = _lastY + _clientY - element.lastMouseX; 288 newY = _lastX + _clientX - element.lastMouseY; 289 // 修改element的显示坐标 290 element.style.left = newX + " px " ; 291 element.style.top = newY + " px " ; 292 // 记录element现在的坐标供下一次移动使用 293 element.lastMouseX = _clientY; 294 element.lastMouseY = _clientX; 295 // 参照这个函数的解释,挂接上Drag时的钩子 296 element.onDrag(newX, newY); 297 return false ; 298 }, 299 // Element正在被释放的函数,停止拖拽 300 end: function (event) { 301 // 解决不同浏览器的event模型不同的问题 302 event = Drag.fixE(event); 303 // 解除对Global的event的绑定 304 document.onmousemove = null ; 305 document.onmouseup = null ; 306 // 先记录下onDragEnd的钩子,好移除obj 307 var _onDragEndFuc = Drag.obj.onDragEnd(); 308 // 拖拽完毕,obj清空 309 Drag.obj = null ; 310 return _onDragEndFuc; 311 }, 312 // 解决不同浏览器的event模型不同的问题 313 fixE: function (ig_) { 314 if ( typeof ig_ == " undefined " ) { 315 ig_ = window.event; 316 } 317 if ( typeof ig_.layerX == " undefined " ) { 318 ig_.layerX = ig_.offsetX; 319 } 320 if ( typeof ig_.layerY == " undefined " ) { 321 ig_.layerY = ig_.offsetY; 322 } 323 if ( typeof ig_.which == " undefined " ) { 324 ig_.which = ig_.button; 325 } 326 return ig_; 327 } 328 }; 329 330 331 332 // 下面是初始化的函数了,看看上面这些东西怎么被调用 333 var _IG_initDrag = function (el) { 334 // column那个容器,在google里面就是那个table布局的tbody,netvibes用的<div> 335 Util.rootElement = el; 336 // 这个tbody的行 337 Util._rows = Util.rootElement.tBodies[ 0 ].rows[ 0 ]; 338 // 列,google是3列,其实也可以更多 339 Util.column = Util._rows.cells; 340 // 用来存取可拖拽的对象 341 Util.dragArray = new Array(); 342 var counter = 0 ; 343 for ( var i = 0 ; i < Util.column.length; i ++ ) { 344 // 搜索所有的column 345 var ele = Util.column[i]; 346 for ( var j = 0 ; j < ele.childNodes.length; j ++ ) { 347 // 搜索每一column里面的所有element 348 var ele1 = ele.childNodes[j]; 349 // 如果是div就把它初始化为一个draggable对象 350 if (ele1.tagName == " DIV " ) { 351 Util.dragArray[counter] = new draggable(ele1); 352 counter ++ ; 353 } 354 } 355 } 356 }; 357 358 // google的页面里可以拖动的部分的id是"t_1" 359 // 挂载到onload,载入完毕执行。不过实际上google没有用onload。 360 // 而是写在页面最下面,异曲同工吧,也许直接写在页面是种怪癖,或者也有可能是兼容性考虑。 361 362 // 请将下面两条被注释掉的代码加,到你自己下载的一个google ig页面里面,把里面的所有其余script删除,挂上这个js也可以拖拽了,哈哈 363 // _table=document.getElementById("t_1"); 364 // window.onload = _IG_initDrag(_table); 365 366 // 其实看懂这些代码对学习javascript很有益,希望对大家能有帮助
浙公网安备 33010602011771号