級別: 中級
Greg Travis , 軟件工程師
2008 年 10 月 28 日
通過本文,了解使用拖放功能移動 Web 頁面的不同部分的技巧。分別實現(xiàn)交互性的不同方面,然后再將它們組合在一起,這樣便于靈活定制頁面,也讓您的 Web 用戶非常滿意。
JavaScript是一種功能強大的語言,可用于創(chuàng)建基于 Web的應用程序。它已經(jīng)足夠穩(wěn)定和成熟,完全可以創(chuàng)建與傳統(tǒng)桌面應用程序相抗衡的程序,因為后者在穩(wěn)定性和特性豐富性方面都要勝出一籌。但JavaScript 最初只是用來向靜態(tài) Web 頁面添加某些交互性,使它不再是靜態(tài)頁面,它現(xiàn)在還用于此目的。
我將要展示的這個技巧的關鍵之處是如何恰當?shù)貥?gòu)建頁面,使它能與 JavaScript 代碼交互。通常,頁面都是通過頭腦里固有的 JavaScript代碼構(gòu)造的。但是,盡管如此,很多時候您都需要向現(xiàn)有頁面內(nèi)添加新的交互特性。而這往往需要一些技巧,因為 JavaScript代碼必須遍歷文檔結(jié)構(gòu)并在合適的位置添加代碼,而且通常還要求不影響現(xiàn)有的結(jié)構(gòu) — 和頁面上已有的 JavaScript代碼。總之,要將對系統(tǒng)的影響最小化。
![]() |
|
本文介紹了一種方法,它通過移動頁面的不同部分來 激活 頁面。具體來講,就是通過將一個部分拖放到另一個部分之上從而實現(xiàn) 可切換 部分的切換。
要激活這些部分,只需向其添加
class
參數(shù)并加載一個 JavaScript 文件。可以通過向
<body>
標記添加
onload
方法來激活代碼,此方法會在頁面加載之后立即啟動代碼。代碼會處理隨后的事情。
注意: 本文示例所對應的源代碼可以從 下載 部分獲得。
此外,可以盡量多地使用抽象來構(gòu)造代碼。程序的不同元素通常都不必要地相互纏結(jié),UI 代碼更是這樣。可切換系統(tǒng)由不同的塊構(gòu)建而成,每個塊實現(xiàn)交互性的不同部分。這些塊結(jié)合起來就能實現(xiàn)簡單無縫的界面,該界面對于 UI 的試驗和調(diào)優(yōu)都很關鍵。
![]() ![]() |
![]()
|
可切換系統(tǒng)很容易使用。先由 Web 頁面設計人員將某些部分標志為可切換的。然后就可以在任何一個可切換元素上單擊并將該元素拖放到另一個可切換元素。放開鼠標按鈕后,這兩個元素就完成了交換。
為了能清楚展示所發(fā)生的事情,可以使用一些標準的 GUI 操作。
當?shù)谝淮螁螕艨汕袚Q元素時,在光標下面會出現(xiàn)一個透明的矩形。這個矩形由
coveringDiv()
函數(shù)創(chuàng)建,它剛好能覆蓋這個可切換元素。實際上是將這個矩形拖放到另一個元素。當拖放時,只有這個透明的矩形會移動 — 初始的元素保持不動直到鼠標按鈕被松開為止。
另一個重要的操作是清晰標識出要拖動到的目標元素。當拖動透明的矩形四處移動時,光標可以經(jīng)過多個可切換元素。當光標懸浮于某個可切換元素之上時,該元素就會通過另一個透明矩形突出顯示。這種突出顯示就能清楚地標示出此元素就是拖放到的目標。當松開鼠標按鈕時,被拖動的元素和拖放到的目標元素就會互換位置,而且所有透明矩形也會消失,直到下一次切換。
正如先前提到的,必須要使代碼對已有系統(tǒng)影響最小。這就意味著頁面設計人員 —工作于 HTML 或 XML— 無需涉及可切換系統(tǒng)。這不是他們的工作。
此頁面只需具有如下三項內(nèi)容:
- JavaScript 標記
-
<body>
標記內(nèi)的onload
方法 - 標記為 swappable 的可切換區(qū)域
必須將以下標記置于頁面文件的頂部:
<script src="rearrange-your-page.js"></script> |
此標記在加載過程的早期加載,但它在 body 內(nèi)的
onload
函數(shù)調(diào)用之后才會執(zhí)行。
該方法在整個頁面加載時調(diào)用這個可切換系統(tǒng)。這一點很重要,因為此代碼的第一項功能就是在整個頁面內(nèi)搜索可切換的元素。因而,需要確保這些元素已加載。body 內(nèi)的
onload
方法應該如清單 1 所示。
清單 1. body 內(nèi)的 onload 處理程序
<body onload="swappable_start();"> ... rest of page </body> |
必須通過
class
參數(shù)這樣標記每個想要切換的區(qū)域。這是頁面作者和設計人員需要多加考慮的事情,因為他們需要將此參數(shù)添加給每個部分。參見清單 2。
<div class='swappable'> lorem ipsum lorem ipsum </div> |
代碼所需做的首要事情是尋找頁面將被激活的部分。正如之前提到的,這只要求包圍這個部分的標記具有
class
參數(shù)。要尋找這些部分,需要找到所有具有可切換
class
的標記。此函數(shù)不是標準 DOM 庫的一部分,但它很容易實現(xiàn)。清單 3 展示了一個示例實現(xiàn)。
清單 3. getElementsByClass() 的實現(xiàn)
// By Dustin Diaz function getElementsByClass(searchClass,node,tag) { var classElements = new Array(); if ( node == null ) node = document; if ( tag == null ) tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|////s)"+searchClass+"(////s|$)"); for (i = 0, j = 0; i < elsLen; i++) { if ( pattern.test(els[i].className) ) { classElements[j] = els[i]; j++; } } return classElements; } |
程序一般是通過將各功能塊結(jié)合在一起而構(gòu)建起來的。不同的程序員會有不同的實現(xiàn)方式,但作為一種規(guī)律,最好是采用多個小的功能塊而不是少數(shù)幾個大的功能塊。每個小功能塊應該實現(xiàn)一種功能并具有清楚的語義。
不過,在進行 GUI 編程時,這樣的構(gòu)建不太容易。好的 GUI 必須調(diào)整很多界面元素并將它們的行為結(jié)合起來形成一個能直觀工作的整體行為。基于事件的系統(tǒng)通常都是由復雜的交換行為聯(lián)合起來的回調(diào)集合。模塊化的交互元素很難創(chuàng)建。
可切換代碼就使用了模塊化的交互元素。前面,我提到過在可切換系統(tǒng)內(nèi)有兩種主要的交互元素:拖動元素的突出顯示和拖動到的目標的突出顯示。在代碼中,這兩個元素的實現(xiàn)是分開的。
本例很好地展示了模塊化處理交互性的技巧。正如可切換界面的描述中所提到的,這兩個交互性元素常常纏結(jié)在一起。突出顯示和突出顯示的消失都是在一個鼠標操作中發(fā)生的,而且它們的發(fā)生都對應鼠標輸入的不同方面。如果這兩個元素是在一個代碼片段中實現(xiàn)的,那么代碼可能不太容易讀懂,因為同時發(fā)生的事情很多。
為了使 GUI 的實現(xiàn)模塊化,我使用了 拖動處理程序 。這類似于內(nèi)置在 GUI 系統(tǒng)的事件處理程序。雖然事件處理程序只處理某種單一事件,拖動處理程序卻可以處理整個拖放過程。一個拖動處理程序可處理一系列事件而不只一個單一事件。下面是拖動處理程序的示例骨架,如清單 4 所示。
{ start: function( x, y ) { // ... }, move: function( x, y ) { // ... }, done: function() { // ... }, } |
這個拖動處理程序是一個對象,具有三個方法:
start
、
move
和
done
。當初始化一個拖放動作時,調(diào)用
start
方法并傳遞給這次單擊的對應坐標。當四處移動光標時,會反復調(diào)用
move
方法,然后同樣被傳遞給光標當前對應的坐標。最后,當鼠標按鈕釋放后,就會調(diào)用
done
方法。
可切換系統(tǒng)同時使用了兩個不同的拖動處理程序,這也讓您能夠干凈地處理交互的兩個不同方面,即便這兩個方面具有復雜的關系。讓其中的一個交互成為另一個交互的一部分并不合適。相反,應該能同時無縫地使用這兩個交互。
這兩個拖放處理程序的其中是
rectangle_drag_handler
。此處理程序負責移動代表被拖動元素的透明矩形。清單 5 給出了這個
start
方法。
清單 5. rectangle_drag_handler 處理程序
function rectangle_drag_handler( target ) { this.start = function( x, y ) { this.cover = coveringDiv( target ); make_translucent( this.cover, .6 ); this.cover.style.backgroundColor = "#777"; dea( this.cover ); this.dragger = new dragger( this.cover, x, y ); }; // ... } |
start
方法創(chuàng)建這個透明矩形并將其傳遞給另一個稱為
dragger
的對象。一個
dragger
就是一個對象,它能對應移動的光標移動 DOM 元素。可以將當前的光標的坐標傳遞給這個 dragger,它會更新所拖動的對象使其跟隨光標的移動。
move
方法更新這個 dragger,如清單 6 所示。
this.move = function( x, y ) { this.dragger.update( x, y ); }; |
最后,
done
方法(參見清單 7)刪除這個透明矩形,因為拖放過程現(xiàn)在已經(jīng)結(jié)束。
清單 7. rectangle_drag_handler 的 done 方法
this.move = function( x, y ) { this.done = function() { this.cover.parentNode.removeChild( this.cover ); }; } |
現(xiàn)在必須找到一種方法來同時使用這兩個拖動處理程序。這可以通過
compose_drag_handlers()
函數(shù)輕松實現(xiàn),該函數(shù)接受這兩個拖動處理程序并將其結(jié)合成一個綜合的拖動處理程序。這個綜合拖動處理程序的使用與一般的拖動處理程序一樣。這樣,這兩個原始的拖動處理程序的行為就實現(xiàn)了無縫結(jié)合。
compose_drag_handlers()
函數(shù)很容易編寫。它看上去很像是一個拖動處理程序,但每個方法都會調(diào)用這兩個原始拖動處理程序中相應的方法。這個函數(shù)如清單 8 所示。
function compose_drag_handlers( a, b ) { return { start: function( x, y ) { a.start( x, y ); b.start( x, y ); }, move: function( x, y ) { a.move( x, y ); b.move( x, y ); }, done: function() { a.done(); b.done(); }, } } |
正如您所見,拖動處理程序
a
和
b
被組合到一個綜合的拖動處理程序內(nèi)。如果要調(diào)用這個綜合處理程序的
start()
方法,實際上就是先后調(diào)用
a.start()
和
b.start()
。
您需要在名為
prepare_swappable()
的設置函數(shù)內(nèi)調(diào)用
compose_drag_handlers
,如清單 9 所示。
清單 9. prepare_swappable() 函數(shù)
function prepare_swappable( o ) { swappables.push( o ); var sdp = new rectangle_drag_handler( o ); var hdp = new highlighting_drag_handler( o ); var both = compose_drag_handlers( sdp, hdp ); install_drag_handler( o, both ); } |
除了其他功能之外,此函數(shù)最主要的功能是為可切換元素創(chuàng)建
rectangle_drag_handler
和
highlighting_drag_handler
,然后再將它們組合成一個綜合的拖動處理程序。最后,這個綜合拖動處理程序再通過調(diào)用
install_drag_handler()
來激活,這將在接下來的兩個小節(jié)中詳細介紹。
與常規(guī)的事件處理程序不同,一個拖動處理程序可以處理多個事件。盡管如此,它還是需要附加到對象,這與常規(guī)的事件處理程序相同。
安裝任何一種事件處理程序都是需要技巧的,因為正在修改的元素很可能已經(jīng)在其內(nèi)安裝了事件處理程序。如果要替換這些事件處理程序,就需要更改頁面的行為方式。
為了避免這一問題,可以使用一個名為
install_mouse_handlers()
的實用函數(shù),如清單 10 所示。
清單 10. install_mouse_handlers() 函數(shù)
function install_mouse_handlers( target, onmouseup, onmousedown, onmousemove ) { var original_handlers = { onmouseup: target.onmouseup, onmousedown: target.onmousedown, onmousemove: target.onmousemove }; target.onmouseup = onmouseup; target.onmousedown = onmousedown; target.onmousemove = onmousemove; return { restore: function() { target.onmouseup = original_handlers.onmouseup; target.onmousedown = original_handlers.onmousedown; target.onmousemove = original_handlers.onmousemove; } }; } |
install_mouse_handlers()
函數(shù)負責向特定的對象添加特定的鼠標處理程序。它返回的是一個對象,可使用該對象恢復原始的處理程序。這樣一來,當拖放過程結(jié)束后,就可以調(diào)用
restore()
函數(shù),恢復到拖放過程開始之前的狀態(tài)。
針對拖放操作的拖動處理程序使用了這三個鼠標處理程序:
onmousedown
、
onmouseup
和
onmousemove
。不過,開始時,只需安裝
mousedown
處理程序,因為此時您尚在等待激發(fā)初始化拖放過程的單擊。
當單擊發(fā)生時,就需要安裝
mousemove
和
mouseup
處理程序。而且,在此時,不再需要
mousedown
處理程序,因為已經(jīng)進行單擊。該處理程序?qū)⒈粍h除,在拖放過程完成后再恢復它。最初的
mousedown
處理程序如清單 11 所示。
var onmousedown = function( e ) { var x = e.clientX; var y = e.clientY; p.start( x, y ); var target_handler_restorer = null; var document_handler_restorer = null; var onmousemove = function( e ) { var x = e.clientX; var y = e.clientY; p.move( x, y ); }; var onmouseup = function( e ) { p.done(); target_handler_restorer.restore(); document_handler_restorer.restore(); }; target_handler_restorer = install_mouse_handlers( target, onmouseup, null, onmousemove ); document_handler_restorer = install_mouse_handlers( document, onmouseup, null, onmousemove ); e.stopPropagation(); return false; }; |
在初始化拖放序列并調(diào)用此處理程序時,它會創(chuàng)建
onmousemove
和
onmouseup
處理程序并能在目標元素內(nèi)安裝它們。當然,它還會使用一個
install_mouse_handlers()
,以便以后的卸載。
還有一點需要注意:是在
document
對象內(nèi)安裝這些處理程序的。這一點十分關鍵,因為在拖放過程中用戶可能會將光標拖過整個頁面。如果鼠標超出可切換元素的范圍 — 您很可能還想收到這些事件。同樣地,可以使用
install_mouse_handlers()
以便以后恢復它們。
![]() ![]() |
![]()
|
至此,我已經(jīng)介紹了很多不同的類和函數(shù)。其中的每一個類或函數(shù)本身都十分簡單,因此更重要的是要了解它們是如何協(xié)同工作的。
下面是對整個拖放過程的一個總結(jié):
- 單擊一個可切換元素。
-
此元素的
onmousedown
處理程序?qū)⒈徽{(diào)用,它安裝onmousemove
和onmouseup
處理程序。 - 移動鼠標,這些處理程序?qū)⒈徽{(diào)用。
- 這些處理程序反過來調(diào)用前面安裝的拖動處理程序。
- 這個拖動處理程序?qū)嶋H上是一個復合拖動處理程序,綜合了兩個不同的拖動處理程序的效果。
-
其中的一個拖動處理程序
rectangle_drag_handler
負責向光標附加一個代表被拖動元素的透明矩形。 -
另一個拖動處理程序
highlighting_drag_handler
負責突出顯示鼠標移過的那些可切換元素,以顯示可以進行元素拖動的地方。 -
當在目標元素之上釋放鼠標按鈕時,
highlighting_drag_handler
的done()
方法就會切換這兩個元素。這個拖動處理程序?qū)⒈恍遁d,只留下最初的onmousedown
處理程序,準備好開始下一輪的拖放過程。
![]() ![]() |
![]()
|
拖放操作相對簡單,但它涉及了幾個交互過程,用來跟蹤整個過程的用戶輸入和提供即時反饋。本文展示如何將模塊化的交互元素組合成統(tǒng)一整體來構(gòu)建完整的 GUI。
這種做法有很多好處。由于代碼是模塊化的,因此更容易編寫和維護。所需的函數(shù)和類的代碼沒有一個是超過 40 行的,并且它們通常更短。
每一個交互元素都會實現(xiàn)一個典型的 GUI 過程或效果,所以可在其他上下文中重用它們。可以開發(fā)這些交互元素的豐富的庫,從而通過組合各個部分構(gòu)建更復雜的 UI。
更多文章、技術交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
