第18章 高級技巧 (四)

 

18.4 拖放

拖放是一種非常流行的用戶界面模式。它的概念很簡單:點擊某個對象,並按住鼠標按鈕不放,將鼠標移動到另一個區域,然後釋放鼠標按鈕將對象 "放" 在這裏。拖放功能也流行到了 Web 上,成爲了一些更傳統的配置界面的一種候選方案。

拖放的基本概念很簡單:創建一個絕對定位的元素,使其可以用鼠標移動。這個技術源自一種叫做 "鼠標拖尾" 的經典網頁技巧。鼠標拖尾是一個或者多個圖片在頁面上跟着鼠標指針移動。單元素鼠標拖尾的基本代碼需要爲文檔設置一個 onmousemove 事件處理程序,它總是將指定元素移動到鼠標指針的位置,如下面的例子所示:

EventUtil.addHandler(document, "mousemove", function(event){

var myDiv = document.getElementById("myDiv");

myDiv.style.left = event.clientX + "px";

myDiv.style.top = event.clientY + "px";

});

在這個例子中,元素的 left 和 top 座標設置爲了 event 對象的 clientX 和 clientY 屬性,這就將元素放到了視口中指針的位置上。它的效果是一個元素始終跟隨指針在頁面上的移動。只要正確的時刻 (當鼠標按鈕按下的時候) 實現該功能,並在之後刪除它 (當釋放鼠標按鈕時),就可以實現拖放了。最簡單的拖放界面可以用以下代碼實現:

var DragDrop = function(){
	var dragging = null;
	
	function handleEvent(event){
		// 獲取事件和目標
		event = EventUtil.getEvent(event);
		var target = EventUtil.getTarget(event);
		// 確定事件類型
		switch(event.type){
			case "mousedown":
				if (target.className.indexOf("draggable") > -1){
					dragging = target;
				}
				break;
			case "mousemove":
				if (dragging !== null){
					// get event
					event = EventUtil.getEvent(event);
					// assign location
					dragging.style.left = event.clientX + "px";
					dragging.style.top = event.clientY + "px";
				}	
				break;
			case "mouseup":
				dragging = null;
				break;
		}	
	};
	// 公共接口
	return {
		enable: function(){
			EventUtil.addHandler(document, "mousedown", handleEvent);
			EventUtil.addHandler(document, "mousemove", handleEvent);
			EventUtil.addHandler(document, "mouseup", handleEvent);	
		},
		disable: function(){
			EventUtil.removeHandler(document, "mousedown", handleEvent);
			EventUtil.removeHandler(document, "mousemove", handleEvent);
			EventUtil.removeHandler(document, "mouseup", handleEvent);	
		}	
	}	
}();

DragDrop 對象封裝了拖放的所有基本功能。這是一個單例對象,並使用了模塊模式來隱藏某些實現細節。dragging 變量起初是 null, 將會存放被拖動的元素,所以當該變量不爲 null 時,就知道正在拖動某個東西。handleEvent() 函數處理拖放功能中的所有的三個鼠標事件。它首先獲取 event 對象和事件目標的引用。之後,用一個 switch 語句確定要觸發哪個事件樣式。當 mousedown 事件發生時,會檢查 target 的 class 是否包含 "draggable" 類,如果是,那麼將 target 存放到 dragging 中。這個技巧可以很方便地通過標記語言而非 JavaScript 腳本來確定可拖動的元素。

handleEvent() 的 mousemove 情況和前面的代碼一樣,不過要檢查 dragging 是否爲 null 。當它不是 null,就知道 dragging 就是要拖動的元素,這樣就會把它放到恰當的位置上。mouseup 情況就僅僅是將 dragging 重置爲 null,讓 mousemove 事件中的判斷失效。

DragDrop 還有兩個公共方法: enable() 和 disable(),它們只是相應添加和刪除所有事件處理程序。這兩個函數提供了額外的對拖放功能的控制手段。

要使用 DragDrop 對象,只要在頁面上包含這些代碼並調用 enable() 。拖放會自動針對所有包含 "draggable" 類的元素啓用,如下例所示:

<div class="draggable" style="position: absolute; background: red"></div>

注意爲了元素能被拖放,它必須是絕對定位的。

18.4.1 修繕拖動功能

當你試了上面的例子之後,你會發現元素的左上角總是和指針在一起。這個結果對用戶來說有一點不爽,因爲當鼠標開始移動的時候,元素好像是突然跳了一下。理想情況是,這個動作應該看上去好像這個元素是被指針 "拾起" 的,也就是說當在拖動元素的時候,用戶點擊的那一點就是指針應該保持的位置 (見圖 18-4) 。

要達到需要的效果,必須做一些額外的計算。你需要計算元素左上角和指針位置之間的差值。這個差值應該在 mousedown 事件發生的時候確定,並且一直保持,直到 mouseup 事件發生。通過將 event 的 clientX 和 clientY 屬性與該元素的 offsetLeft 和 offsetTop 屬性進行比較,就可以算出水平方向和垂直方向上需要多少空間,見圖 18-5 。

爲了保存 x 和 y 座標上的差值,還需要幾個變量。diffX 和 diffY 這些變量需要在 onmousemove 事件處理程序中用到,來對元素進行適當的定位,如下面的例子所示:

var DragDrop = function(){
	var dragging = null;
	var diffX = 0;
	var diffY = 0;
	
	function handleEvent(event){
		// 獲取事件和目標
		event = EventUtil.getEvent(event);
		var target = EventUtil.getTarget(event);
		// 確定事件類型
		switch(event.type){
			case "mousedown":
				if (target.className.indexOf("draggable") > -1){
					dragging = target;
					diffX = event.clientX - target.offsetLeft;
					diffY = event.clientY - target.offsetTop;
				}
				break;
			case "mousemove":
				if (dragging !== null){
					// 獲取事件
					event = EventUtil.getEvent(event);
					// 指定位置
					dragging.style.left = (event.clientX - diffX) + "px";
					dragging.style.top = (event.clientY - diffY) + "px";
				}	
				break;
			case "mouseup":
				dragging = null;
				break;
		}	
	};
	// 公共接口
	return {
		enable: function(){
			EventUtil.addHandler(document, "mousedown", handleEvent);
			EventUtil.addHandler(document, "mousemove", handleEvent);
			EventUtil.addHandler(document, "mouseup", handleEvent);	
		},
		disable: function(){
			EventUtil.removeHandler(document, "mousedown", handleEvent);
			EventUtil.removeHandler(document, "mousemove", handleEvent);
			EventUtil.removeHandler(document, "mouseup", handleEvent);	
		}	
	}	
}();
// 調用 enable();
DragDrop.enable();


diffX 和 diffY 變量是私有的,因爲只有 handleEvent() 函數需呀用到它們。當 mousedown 事件發生時,通過 clientX 減去目標的 offsetLeft,clientY 減去目標的 offsetTop ,可以計算到這兩個變量的值。當觸發了 mousemove 事件後,就可以使用這些變量從指針座標中減去,得到最終的座標。最後得到一個更加平滑的拖動體驗,更加符合用戶所期望的方式。

18.4.2  添加自定義事件

拖放功能還不能真正應用起來,除非能知道什麼時候拖動開始了。從這點上看,前面的代碼沒有提供任何方法表示拖動開始、正在拖動或者已經結束。這時,可以使用自定義事件來指示這幾個事件的發生,讓應用的其他部分與拖動功能進行交互。

由於 DragDrop 對象是一個使用了模塊模式的單例,所以需要進行一些更改來使用 EventTarget 類型。首先,創建一個新的 EventTarget 對象,然後添加 enable() 和 disable() 方法,最後返回這個對象。看以下內容:

var DragDrop = function(){
	var dragdrop = new EventTarget();
	var dragging = null;
	var diffX = 0;
	var diffY = 0;
	
	function handleEvent(event){
		// 獲取事件和目標
		event = EventUtil.getEvent(event);
		var target = EventUtil.getTarget(event);
		// 確定事件類型
		switch(event.type){
			case "mousedown":
				if (target.className.indexOf("draggable") > -1){
					console.log("mousedown.....");
					dragging = target;
					diffX = event.clientX - target.offsetLeft;
					diffY = event.clientY - target.offsetTop;
					dragdrop.fire({type: "dragstart", target: dragging, x: event.clientX, y: event.clientY});
				}
				break;
			case "mousemove":
				if (dragging !== null){
					// 獲取事件
					event = EventUtil.getEvent(event);
					// 指定位置
					dragging.style.left = (event.clientX - diffX) + "px";
					dragging.style.top = (event.clientY - diffY) + "px";
					// 觸發自定義事件
					dragdrop.fire({type: "drag", target: dragging, x: event.clientX, y: event.clientY});
				}	
				break;
			case "mouseup":
				dragdrop.fire({type: "dragend", target: dragging, x: event.clientX, y: event.clientY});
				dragging = null;
				break;
		}	
	};
	// 公共接口
	dragdrop.enable = function(){
			EventUtil.addHandler(document, "mousedown", handleEvent);
			EventUtil.addHandler(document, "mousemove", handleEvent);
			EventUtil.addHandler(document, "mouseup", handleEvent);	
	};
	dragdrop.disable = function(){
			EventUtil.removeHandler(document, "mousedown", handleEvent);
			EventUtil.removeHandler(document, "mousemove", handleEvent);
			EventUtil.removeHandler(document, "mouseup", handleEvent);	
	};	
	
	return dragdrop;
}();


這段代碼定義了3個事件:dragstart、drag 和 dragend 。它們都將被拖動的元素設置爲了 target ,並給出了 x 和 y 屬性來表示當前的位置。它們觸發於 dragdrop 對象上,之後在返回對象前給對象增加 enable() 和 disable() 方法。這些模塊模式中的細小更改令 DragDrop 對象支持了事件,如下:

DragDrop.enable();

DragDrop.addHandler("dragstart", function(event){
	var status = document.getElementById("status");
	status.innerHTML = "Started dragging " + event.target.id;	
});

DragDrop.addHandler("drag", function(event){
	var status = document.getElementById("status");
	status.innerHTML += " <br/> Dragged " + event.target.id + " to (" + event.x + "," + event.y + ")";	
});

DragDrop.addHandler("dragstart", function(event){
	var status = document.getElementById("status");
	status.innerHTML += " <br/> Dropped " + event.target.id + " at (" + event.x + "," + event.y + ")";	
});


這裏,爲 DragDrop 對象的每個事件添加了事件處理程序。還使用了一個元素來實現被拖動的元素當前的狀態和位置。一旦元素被放下了,就可以看到從它一開始被拖動之後經過的所有的中間步驟。

爲 DragDrop 添加自定義事件可以使這個對象更健壯,它將可以在網絡應用中處理複雜的拖放功能。

 

發佈了0 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章