这两天太忙了,没时间写Blog,业余时间又扒了一次google的拖拽,对比了netvibes的,差不多,让后就写了个注释,顺便学习。
google用了混淆,js里面的命名比较晕。我花了些时间把他们翻译成容易看懂的命名,并且作了注释,希望对大家有帮助。
可以这里下载我的例子:
http://www.blogjava.net/Files/iamtin/google_drag.rar
		
		
				//
				工具类,使用Util的命名空间,方便管理
				
						
				
				var
				 Util 
				=
				 
				new
				 Object();
				//
				获取http header里面的UserAgent,浏览器信息
				
						
				
				Util.getUserAgent 
				=
				 navigator.userAgent;
				//
				是否是Gecko核心的Browser,比如Mozila、Firefox
				
						
				
				Util.isGecko 
				=
				 Util.getUserAgent.indexOf(
				"
				Gecko
				"
				) 
				!=
				 
				-
				1
				;
				//
				是否是Opera
				
						
				
				Util.isOpera 
				=
				 Util.getUserAgent.indexOf(
				"
				Opera
				"
				) 
				!=
				 
				-
				1
				;
				//
				获取一个element的offset信息,其实就是相对于Body的padding以内的绝对坐标
				
						
//
				后面一个参数如果是true则获取offsetLeft,false则是offsetTop
				
						
//
				关于offset、style、client等坐标的定义参考dindin的这个帖子:http://www.jroller.com/page/dindin/?anchor=pro_javascript_12
				
						
				
				Util.getOffset 
				=
				 
				function
				 (el, isLeft) {
    
				var
				 retValue 
				=
				 
				0
				;
    
				while
				 (el 
				!=
				 
				null
				) {
        retValue 
				+=
				 el[
				"
				offset
				"
				 
				+
				 (isLeft 
				?
				 
				"
				Left
				"
				 : 
				"
				Top
				"
				)];
        el 
				=
				 el.offsetParent;
    }
    
				return
				 retValue;
};
				//
				将一个function(参数中的funcName是这个fuction的名字)绑定到一个element上,并且以这个element的上下文运行,其实是一种继承,这个可以google些文章看看
				
						
				
				Util.bindFunction 
				=
				 
				function
				 (el, fucName) {
    
				return
				 
				function
				 () {
        
				return
				 el[fucName].apply(el, arguments);
    };
};
				//
				重新计算所有的可以拖拽的element的坐标,对同一个column下面的可拖拽图层重新计算它们的高度而得出新的坐标,防止遮叠
				
						
//
				计算出来的坐标记录在pagePosLeft和pagePosTop两个属性里面
				
						
				
				Util.re_calcOff 
				=
				 
				function
				 (el) {
    
				for
				 (
				var
				 i 
				=
				 
				0
				; i 
				<
				 Util.dragArray.length; i
				++
				) {
        
				var
				 ele 
				=
				 Util.dragArray[i];
        ele.elm.pagePosLeft 
				=
				 Util.getOffset(ele.elm, 
				true
				);
        ele.elm.pagePosTop 
				=
				 Util.getOffset(ele.elm, 
				false
				);
    }
    
				var
				 nextSib 
				=
				 el.elm.nextSibling;
    
				while
				 (nextSib) {
        nextSib.pagePosTop 
				-=
				 el.elm.offsetHeight;
        nextSib 
				=
				 nextSib.nextSibling;
    }
};
				//
				隐藏Google Ig中间那个table,也就是拖拽的容器,配合show一般就是刷新用,解决一些浏览器的怪癖
				
						
				
				Util.hide 
				=
				 
				function
				 () {
    Util.rootElement.style.display 
				=
				 
				"
				none
				"
				;
};
				//
				显示Google Ig中间那个table,解释同上
				
						
				
				Util.show 
				=
				 
				function
				 () {
    Util.rootElement.style.display 
				=
				 
				""
				;
};
				//
				移动时显示的占位虚线框
				
						
				
				ghostElement 
				=
				 
				null
				;
				//
				获取这个虚线框,通过dom动态生成
				
						
				
				getGhostElement 
				=
				 
				function
				 () {
    
				if
				 (
				!
				ghostElement) {
        ghostElement 
				=
				 document.createElement(
				"
				DIV
				"
				);
        ghostElement.className 
				=
				 
				"
				modbox
				"
				;
        ghostElement.backgroundColor 
				=
				 
				""
				;
        ghostElement.style.border 
				=
				 
				"
				2px dashed #aaa
				"
				;
        ghostElement.innerHTML 
				=
				 
				"
				 
				"
				;
    }
    
				return
				 ghostElement;
};
				//
				初始化可以拖拽的Element的函数,与拖拽无关的我去掉了
				
						
				
				function
				 draggable(el) {
    
				//
				公用的开始拖拽的函数
				
						
				
				    
				this
				._dragStart 
				=
				 start_Drag;
    
				//
				公用的正在拖拽的函数
				
						
				
				    
				this
				._drag 
				=
				 when_Drag;
    
				//
				公用的拖拽结束的函数
				
						
				
				    
				this
				._dragEnd 
				=
				 end_Drag;
    
				//
				这个函数主要用来进行拖拽结束后的dom处理
				
						
				
				    
				this
				._afterDrag 
				=
				 after_Drag;
    
				//
				是否正在被拖动,一开始当然没有被拖动
				
						
				
				    
				this
				.isDragging 
				=
				 
				false
				;
    
				//
				将这个Element的this指针注册在elm这个变量里面,方便在自己的上下文以外调用自己的函数等,很常用的方法
				
						
				
				    
				this
				.elm 
				=
				 el;
    
				//
				触发拖拽的Element,在这里就是这个div上显示标题的那个div
				
						
				
				    
				this
				.header 
				=
				 document.getElementById(el.id 
				+
				 
				"
				_h
				"
				);
    
				//
				对于有iframe的element拖拽不同,这里检测一下并记录
				
						
				
				    
				this
				.hasIFrame 
				=
				 
				this
				.elm.getElementsByTagName(
				"
				IFRAME
				"
				).length 
				>
				 
				0
				;
    
				//
				如果找到了header就绑定drag相关的event
				
						
				
				    
				if
				 (
				this
				.header) {
        
				//
				拖拽时的叉子鼠标指针
				
						
				
				        
				this
				.header.style.cursor 
				=
				 
				"
				move
				"
				;
        
				//
				将函数绑定到header和element的this上,参照那个函数的说明
				
						
				
				        Drag.init(
				this
				.header, 
				this
				.elm);
        
				//
				下面三个语句将写好的三个函数绑定给这个elemnt的三个函数钩子上,也就实现了element从draggable继承可拖拽的函数
				
						
				
				        
				this
				.elm.onDragStart 
				=
				 Util.bindFunction(
				this
				, 
				"
				_dragStart
				"
				);
        
				this
				.elm.onDrag 
				=
				 Util.bindFunction(
				this
				, 
				"
				_drag
				"
				);
        
				this
				.elm.onDragEnd 
				=
				 Util.bindFunction(
				this
				, 
				"
				_dragEnd
				"
				);
    }
};
				//
				下面就是draggable里面用到的那4个function
				
						
//
				公用的开始拖拽的函数
				
						
				
				function
				 start_Drag() {
    
				//
				重置坐标,实现拖拽以后自己的位置马上会被填充的效果
				
						
				
				    Util.re_calcOff(
				this
				);
    
				//
				记录原先的邻居节点,用来对比是否被移动到新的位置
				
						
				
				    
				this
				.origNextSibling 
				=
				 
				this
				.elm.nextSibling;
    
				//
				获取移动的时候那个灰色的虚线框
				
						
				
				    
				var
				 _ghostElement 
				=
				 getGhostElement();
    
				//
				获取正在移动的这个对象的高度
				
						
				
				    
				var
				 offH 
				=
				 
				this
				.elm.offsetHeight;
    
				if
				 (Util.isGecko) {
        
				//
				修正gecko引擎的怪癖吧
				
						
				
				        offH 
				-=
				 parseInt(_ghostElement.style.borderTopWidth) 
				*
				 
				2
				;
    }
    
				//
				获取正在移动的这个对象的宽度
				
						
				
				    
				var
				 offW 
				=
				 
				this
				.elm.offsetWidth;
    
				//
				获取left和top的坐标
				
						
				
				    
				var
				 offLeft 
				=
				 Util.getOffset(
				this
				.elm, 
				true
				);
    
				var
				 offTop 
				=
				 Util.getOffset(
				this
				.elm, 
				false
				);
    
				//
				防止闪烁,现隐藏
				
						
				
				    Util.hide();
    
				//
				将自己的宽度记录在style属性里面
				
						
				
				    
				this
				.elm.style.width 
				=
				 offW 
				+
				 
				"
				px
				"
				;
    
				//
				将那个灰框设定得与正在拖动的对象一样高,比较形象
				
						
				
				    _ghostElement.style.height 
				=
				 offH 
				+
				 
				"
				px
				"
				;
    
				//
				把灰框放到这个对象原先的位置上
				
						
				
				    
				this
				.elm.parentNode.insertBefore(_ghostElement, 
				this
				.elm.nextSibling);
    
				//
				由于要拖动必须将被拖动的对象从原先的盒子模型里面抽出来,所以设定position为absolute,这个可以参考一下css布局方面的知识
				
						
				
				    
				this
				.elm.style.position 
				=
				 
				"
				absolute
				"
				;
    
				//
				设置zIndex,让它处在最前面一层,当然其实zIndex=100是让它很靠前,如果页面里有zIndex>100的,那……
				
						
				
				    
				this
				.elm.style.zIndex 
				=
				 
				100
				;
    
				//
				由于position=absolute了,所以left和top实现绝对坐标定位,这就是先前计算坐标的作用,不让这个模型乱跑,要从开始拖动的地方开始移动
				
						
				
				    
				this
				.elm.style.left 
				=
				 offLeft 
				+
				 
				"
				px
				"
				;
    
				this
				.elm.style.top 
				=
				 offTop 
				+
				 
				"
				px
				"
				;
    
				//
				坐标设定完毕,可以显示了,这样就不会闪烁了
				
						
				
				    Util.show();
    
				//
				这里本来有个ig_d.G,没搞明白干什么用的,不过没有也可以用,谁知道麻烦告诉我一声,不好意思
				
						
				
				    
				//
				还没有开始拖拽,这里做个记号
				
						
				
				    
				this
				.isDragging 
				=
				 
				false
				;
    
				return
				 
				false
				;
};
				//
				在拖拽时的相应函数,由于绑定到鼠标的move这个event上,所以会传入鼠标的坐标clientX, clientY
				
						
				
				function
				 when_Drag(clientX, clientY) {
    
				//
				刚开始拖拽的时候将图层变透明,并标记为正在被拖拽
				
						
				
				    
				if
				 (
				!
				this
				.isDragging) {
        
				this
				.elm.style.filter 
				=
				 
				"
				alpha(opacity=70)
				"
				;
        
				this
				.elm.style.opacity 
				=
				 
				0.7
				;
        
				this
				.isDragging 
				=
				 
				true
				;
    }
    
				//
				被拖拽到的新的column(当然也可以是原来那个)
				
						
				
				    
				var
				 found 
				=
				 
				null
				;
    
				//
				最大的距离,可能是防止溢出或者什么bug
				
						
				
				    
				var
				 max_distance 
				=
				 
				100000000
				;
    
				//
				遍历所有的可拖拽的element,寻找离当前鼠标坐标最近的那个可拖拽元素,以便后面插入
				
						
				
				    
				for
				 (
				var
				 i 
				=
				 
				0
				; i 
				<
				 Util.dragArray.length; i
				++
				) {
        
				var
				 ele 
				=
				 Util.dragArray[i];
        
				//
				利用勾股定理计算鼠标到遍历到的这个元素的距离
				
						
				
				        
				var
				 distance 
				=
				 Math.sqrt(Math.pow(clientX 
				-
				 ele.elm.pagePosLeft, 
				2
				) 
				+
				 Math.pow(clientY 
				-
				 ele.elm.pagePosTop, 
				2
				));
        
				//
				自己已经浮动了,所以不计算自己的
				
						
				
				        
				if
				 (ele 
				==
				 
				this
				) {
            
				continue
				;
        }
        
				//
				如果计算失败继续循环
				
						
				
				        
				if
				 (isNaN(distance)) {
            
				continue
				;
        }
        
				//
				如果更小,记录下这个距离,并将它作为found
				
						
				
				        
				if
				 (distance 
				<
				 max_distance) {
            max_distance 
				=
				 distance;
            found 
				=
				 ele;
        }
    }
    
				//
				准备让灰框落脚
				
						
				
				    
				var
				 _ghostElement 
				=
				 getGhostElement();
    
				//
				如果找到了另外的落脚点
				
						
				
				    
				if
				 (found 
				!=
				 
				null
				 
				&&
				 _ghostElement.nextSibling 
				!=
				 found.elm) {
        
				//
				找到落脚点就先把灰框插进去,这就是我们看到的那个灰框停靠的特效,有点像吸附的感觉,哈哈
				
						
				
				        found.elm.parentNode.insertBefore(_ghostElement, found.elm);
        
				if
				 (Util.isOpera) {
            
				//
				Opera的现实问题,要隐藏/显示后才能刷新出变化
				
						
				
				            document.body.style.display 
				=
				 
				"
				none
				"
				;
            document.body.style.display 
				=
				 
				""
				;
        }
    }
};
				//
				拖拽完毕
				
						
				
				function
				 end_Drag() {
    
				//
				拖拽完毕后执行后面的钩子,执行after_Drag(),如果布局发生了变动了就记录到远程服务器,保存你拖拽后新的布局顺序
				
						
				
				    
				if
				 (
				this
				._afterDrag()) {
        
				//
				remote call to save the change
				
						
				
				    }
    
				return
				 
				true
				;
};
				//
				拖拽后的执行钩子
				
						
				
				function
				 after_Drag() {
    
				var
				 returnValue 
				=
				 
				false
				;
    
				//
				防止闪烁
				
						
				
				    Util.hide();
    
				//
				把拖拽时的position=absolute和相关的那些style都消除
				
						
				
				    
				this
				.elm.style.position 
				=
				 
				""
				;
    
				this
				.elm.style.width 
				=
				 
				""
				;
    
				this
				.elm.style.zIndex 
				=
				 
				""
				;
    
				this
				.elm.style.filter 
				=
				 
				""
				;
    
				this
				.elm.style.opacity 
				=
				 
				""
				;
    
				//
				获取灰框
				
						
				
				    
				var
				 ele 
				=
				 getGhostElement();
    
				//
				如果现在的邻居不是原来的邻居了
				
						
				
				    
				if
				 (ele.nextSibling 
				!=
				 
				this
				.origNextSibling) {
        
				//
				把被拖拽的这个节点插到灰框的前面
				
						
				
				        ele.parentNode.insertBefore(
				this
				.elm, ele.nextSibling);
        
				//
				标明被拖拽了新的地方
				
						
				
				        returnValue 
				=
				 
				true
				;
    }
    
				//
				移除灰框,这是这个灰框的生命周期应该就结束了
				
						
				
				    ele.parentNode.removeChild(ele);
    
				//
				修改完毕,显示
				
						
				
				    Util.show();
    
				if
				 (Util.isOpera) {
        
				//
				Opera的现实问题,要隐藏/显示后才能刷新出变化
				
						
				
				        document.body.style.display 
				=
				 
				"
				none
				"
				;
        document.body.style.display 
				=
				 
				""
				;
    }
    
				return
				 returnValue;
};
				//
				可拖拽Element的原形,用来将event绑定到各个钩子,这部分市比较通用的,netvibes也是基本完全相同的实现
				
						
//
				这部分推荐看dindin的这个,也会帮助理解,http://www.jroller.com/page/dindin/?anchor=pro_javascript_12
				
						
				
				var
				 Drag 
				=
				 {
    
				//
				对这个element的引用,一次只能拖拽一个Element
				
						
				
				    obj:
				null
				, 
    
				//
				element是被拖拽的对象的引用,elementHeader就是鼠标可以拖拽的区域
				
						
				
				    init:
				function
				 (elementHeader, element) {
        
				//
				将start绑定到onmousedown事件,按下鼠标触发start
				
						
				
				        elementHeader.onmousedown 
				=
				 Drag.start;
        
				//
				将element存到header的obj里面,方便header拖拽的时候引用
				
						
				
				        elementHeader.obj 
				=
				 element;
        
				//
				初始化绝对的坐标,因为不是position=absolute所以不会起什么作用,但是防止后面onDrag的时候parse出错了
				
						
				
				        
				if
				 (isNaN(parseInt(element.style.left))) {
            element.style.left 
				=
				 
				"
				0px
				"
				;
        }
        
				if
				 (isNaN(parseInt(element.style.top))) {
            element.style.top 
				=
				 
				"
				0px
				"
				;
        }
        
				//
				挂上空Function,初始化这几个成员,在Drag.init被调用后才帮定到实际的函数,可以参照draggable里面的内容
				
						
				
				        element.onDragStart 
				=
				 
				new
				 Function();
        element.onDragEnd 
				=
				 
				new
				 Function();
        element.onDrag 
				=
				 
				new
				 Function();
    },
    
				//
				开始拖拽的绑定,绑定到鼠标的移动的event上
				
						
				
				    start:
				function
				 (event) {
        
				var
				 element 
				=
				 Drag.obj 
				=
				 
				this
				.obj;
        
				//
				解决不同浏览器的event模型不同的问题
				
						
				
				        event 
				=
				 Drag.fixE(event);
        
				//
				看看是不是左键点击
				
						
				
				        
				if
				 (event.which 
				!=
				 
				1
				) {
            
				//
				除了左键都不起作用
				
						
				
				            
				return
				 
				true
				;
        }
        
				//
				参照这个函数的解释,挂上开始拖拽的钩子
				
						
				
				        element.onDragStart();
        
				//
				记录鼠标坐标
				
						
				
				        element.lastMouseX 
				=
				 event.clientX;
        element.lastMouseY 
				=
				 event.clientY;
        
				//
				将Global的event绑定到被拖动的element上面来
				
						
				
				        document.onmouseup 
				=
				 Drag.end;
        document.onmousemove 
				=
				 Drag.drag;
        
				return
				 
				false
				;
    }, 
    
				//
				Element正在被拖动的函数
				
						
				
				    drag:
				function
				 (event) {
        
				//
				解决不同浏览器的event模型不同的问题
				
						
				
				        event 
				=
				 Drag.fixE(event);
        
				//
				看看是不是左键点击
				
						
				
				        
				if
				 (event.which 
				==
				 
				0
				) {
            
				//
				除了左键都不起作用
				
						
				
				            
				return
				 Drag.end();
        }
        
				//
				正在被拖动的Element
				
						
				
				        
				var
				 element 
				=
				 Drag.obj;
        
				//
				鼠标坐标
				
						
				
				        
				var
				 _clientX 
				=
				 event.clientY;
        
				var
				 _clientY 
				=
				 event.clientX;
        
				//
				如果鼠标没动就什么都不作
				
						
				
				        
				if
				 (element.lastMouseX 
				==
				 _clientY 
				&&
				 element.lastMouseY 
				==
				 _clientX) {
            
				return
				 
				false
				;
        }
        
				//
				刚才Element的坐标
				
						
				
				        
				var
				 _lastX 
				=
				 parseInt(element.style.top);
        
				var
				 _lastY 
				=
				 parseInt(element.style.left);
        
				//
				新的坐标
				
						
				
				        
				var
				 newX, newY;
        
				//
				计算新的坐标:原先的坐标+鼠标移动的值差
				
						
				
				        newX 
				=
				 _lastY 
				+
				 _clientY 
				-
				 element.lastMouseX;
        newY 
				=
				 _lastX 
				+
				 _clientX 
				-
				 element.lastMouseY;
        
				//
				修改element的显示坐标
				
						
				
				        element.style.left 
				=
				 newX 
				+
				 
				"
				px
				"
				;
        element.style.top 
				=
				 newY 
				+
				 
				"
				px
				"
				;
        
				//
				记录element现在的坐标供下一次移动使用
				
						
				
				        element.lastMouseX 
				=
				 _clientY;
        element.lastMouseY 
				=
				 _clientX;
        
				//
				参照这个函数的解释,挂接上Drag时的钩子
				
						
				
				        element.onDrag(newX, newY);
        
				return
				 
				false
				;
    },
    
				//
				Element正在被释放的函数,停止拖拽
				
						
				
				    end:
				function
				 (event) {
        
				//
				解决不同浏览器的event模型不同的问题
				
						
				
				        event 
				=
				 Drag.fixE(event);
        
				//
				解除对Global的event的绑定
				
						
				
				        document.onmousemove 
				=
				 
				null
				;
        document.onmouseup 
				=
				 
				null
				;
        
				//
				先记录下onDragEnd的钩子,好移除obj
				
						
				
				        
				var
				 _onDragEndFuc 
				=
				 Drag.obj.onDragEnd();
        
				//
				拖拽完毕,obj清空
				
						
				
				        Drag.obj 
				=
				 
				null
				;
        
				return
				 _onDragEndFuc;
    }, 
    
				//
				解决不同浏览器的event模型不同的问题
				
						
				
				    fixE:
				function
				 (ig_) {
        
				if
				 (
				typeof
				 ig_ 
				==
				 
				"
				undefined
				"
				) {
            ig_ 
				=
				 window.event;
        }
        
				if
				 (
				typeof
				 ig_.layerX 
				==
				 
				"
				undefined
				"
				) {
            ig_.layerX 
				=
				 ig_.offsetX;
        }
        
				if
				 (
				typeof
				 ig_.layerY 
				==
				 
				"
				undefined
				"
				) {
            ig_.layerY 
				=
				 ig_.offsetY;
        }
        
				if
				 (
				typeof
				 ig_.which 
				==
				 
				"
				undefined
				"
				) {
            ig_.which 
				=
				 ig_.button;
        }
        
				return
				 ig_;
    }
};
				//
				下面是初始化的函数了,看看上面这些东西怎么被调用
				
						
				
				var
				 _IG_initDrag 
				=
				 
				function
				 (el) {
    
				//
				column那个容器,在google里面就是那个table布局的tbody,netvibes用的<div>
				
						
				
				    Util.rootElement 
				=
				 el;
    
				//
				这个tbody的行
				
						
				
				    Util._rows 
				=
				 Util.rootElement.tBodies[
				0
				].rows[
				0
				];
    
				//
				列,google是3列,其实也可以更多
				
						
				
				    Util.column 
				=
				 Util._rows.cells;
    
				//
				用来存取可拖拽的对象
				
						
				
				    Util.dragArray 
				=
				 
				new
				 Array();
    
				var
				 counter 
				=
				 
				0
				;
    
				for
				 (
				var
				 i 
				=
				 
				0
				; i 
				<
				 Util.column.length; i
				++
				) {
        
				//
				搜索所有的column
				
						
				
				        
				var
				 ele 
				=
				 Util.column[i];
        
				for
				 (
				var
				 j 
				=
				 
				0
				; j 
				<
				 ele.childNodes.length; j
				++
				) {
            
				//
				搜索每一column里面的所有element
				
						
				
				            
				var
				 ele1 
				=
				 ele.childNodes[j];
            
				//
				如果是div就把它初始化为一个draggable对象
				
						
				
				            
				if
				 (ele1.tagName 
				==
				 
				"
				DIV
				"
				) {
                Util.dragArray[counter] 
				=
				 
				new
				 draggable(ele1);
                counter
				++
				;
            }
        }
    }
};
				//
				google的页面里可以拖动的部分的id是"t_1"
				
						
//
				挂载到onload,载入完毕执行。不过实际上google没有用onload。
				
						
//
				而是写在页面最下面,异曲同工吧,也许直接写在页面是种怪癖,或者也有可能是兼容性考虑。
				
						
				
				
						
				
				//
				请将下面两条被注释掉的代码加,到你自己下载的一个google ig页面里面,把里面的所有其余script删除,挂上这个js也可以拖拽了,哈哈
				
						
//
				_table=document.getElementById("t_1");
				
						
//
				window.onload = _IG_initDrag(_table);
				
						
				
				
						
				
				//
				其实看懂这些代码对学习javascript很有益,希望对大家能有帮助