??JavaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的抢韭。
??事件薪贫,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特定的交互瞬間。
??可以使用偵聽器(或處理程序)來(lái)預(yù)定事件刻恭,以便事件發(fā)生時(shí)執(zhí)行相應(yīng)的代碼瞧省。
??這種在傳統(tǒng)軟件工程中被稱為觀察員模式的模型,支持頁(yè)面的行為(JavaScript 代碼)與頁(yè)面的外觀(HTML 和 CSS 代碼)之間的松散耦合鳍贾。
??瀏覽器的事件系統(tǒng)相對(duì)比較復(fù)雜鞍匾。盡管所有主要瀏覽器已經(jīng)實(shí)現(xiàn)了 “DOM 2 級(jí)事件”,但這個(gè)規(guī)范本身并沒有涵蓋所有事件類型骑科。
??瀏覽器對(duì)象模型(BOM)也支持一些事件橡淑,這些事件與文檔對(duì)象模型(DOM)事件之間的關(guān)系并不十分清晰,因?yàn)?BOM 事件長(zhǎng)期沒有規(guī)范可以遵循(HTML5 后來(lái)給出了詳細(xì)的說明)咆爽。
??隨著 DOM3 級(jí)的出現(xiàn)梁棠,增強(qiáng)后的 DOM 事件 API 變得更加繁瑣。使用事件有時(shí)相對(duì)簡(jiǎn)單伍掀,有時(shí)則非常復(fù)雜掰茶,難易程度會(huì)因你的需求而不同。不過蜜笤,有關(guān)事件的一些核心概念是一定要理解的濒蒋。
1、事件流
??當(dāng)瀏覽器發(fā)展到第四代時(shí)(IE4 及 Netscape Communicator 4),瀏覽器開發(fā)團(tuán)隊(duì)遇到了一個(gè)很有意思的問題:頁(yè)面的哪一部分會(huì)擁有某個(gè)特定的事件沪伙?要明白這個(gè)問題問的是什么瓮顽,可以想象畫在一張紙上的一組同心圓。如果你把手指放在圓心上围橡,那么你的手指指向的不是一個(gè)圓暖混,而是紙上的所有圓。
??兩家公司的瀏覽器開發(fā)團(tuán)隊(duì)在看待瀏覽器事件方面還是一致的翁授。如果你單擊了某個(gè)按鈕拣播,他們都認(rèn)為單擊事件不僅僅發(fā)生在按鈕上。換句話說收擦,在單擊按鈕的同時(shí)贮配,你也單擊了按鈕的容器元素,甚至也單擊了整個(gè)頁(yè)面塞赂。
??事件流描述的是從頁(yè)面中接收事件的順序泪勒。但有意思的是,IE 和 Netscape 開發(fā)團(tuán)隊(duì)居然提出了差不多是完全相反的事件流的概念宴猾。
??IE 的事件流是事件冒泡流圆存,而 Netscape Communicator 的事件流是事件捕獲流。
1.1仇哆、 事件冒泡
??IE 的事件流叫做事件冒泡(event bubbling)沦辙,即事件開始時(shí)由最具體的元素(文檔中嵌套層次最深的那個(gè)節(jié)點(diǎn))接收,然后逐級(jí)向上傳播到較為不具體的節(jié)點(diǎn)(文檔)讹剔。以下面的 HTML 頁(yè)面為例:
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
??如果你單擊了頁(yè)面中的<div>元素怕轿,那么這個(gè) click 事件會(huì)按照如下順序傳播:
????(1) <div>
????(2) <body>
????(3) <html>
????(4) document
??也就是說,click 事件首先在<div>元素上發(fā)生辟拷,而這個(gè)元素就是我們單擊的元素撞羽。然后,click 事件沿 DOM 樹向上傳播衫冻,在每一級(jí)節(jié)點(diǎn)上都會(huì)發(fā)生诀紊,直至傳播到 document 對(duì)象。下圖展示了事件冒泡的過程隅俘。
??所有現(xiàn)代瀏覽器都支持事件冒泡邻奠,但在具體實(shí)現(xiàn)上還是有一些差別贺氓。
??IE5.5 及更早版本中的事件冒泡會(huì)跳過<html>元素(從<body>直接跳到 document)罗洗。IE9、Firefox籍滴、Chrome 和 Safari 則將事件一直冒泡到 window 對(duì)象蒙畴。
1.2贰镣、 事件捕獲
??Netscape Communicator 團(tuán)隊(duì)提出的另一種事件流叫做事件捕獲(event capturing)呜象。
??事件捕獲的思想是不太具體的節(jié)點(diǎn)應(yīng)該更早接收到事件,而最具體的節(jié)點(diǎn)應(yīng)該最后接收到事件碑隆。
??事件捕獲的用意在于在事件到達(dá)預(yù)定目標(biāo)之前捕獲它恭陡。
??如果仍以前面的 HTML 頁(yè)面作為演示事件捕獲的例子,那么單擊<div>元素就會(huì)以下列順序觸發(fā) click 事件上煤。
??(1) document
??(2) <html>
??(3) <body>
??(4) <div>
??在事件捕獲過程中休玩,document 對(duì)象首先接收到 click 事件,然后事件沿 DOM 樹依次向下劫狠,一直傳播到事件的實(shí)際目標(biāo)拴疤,即<div>元素。下圖展示了事件捕獲的過程独泞。
??雖然事件捕獲是 Netscape Communicator 唯一支持的事件流模型遥赚,但 IE9、Safari阐肤、Chrome、Opera 和 Firefox 目前也都支持這種事件流模型讲坎。
??盡管“DOM2 級(jí)事件”規(guī)范要求事件應(yīng)該從 document 對(duì)象
開始傳播孕惜,但這些瀏覽器都是從 window 對(duì)象開始捕獲事件的。
??由于老版本的瀏覽器不支持晨炕,因此很少有人使用事件捕獲衫画。我們也建議讀者放心地使用事件冒泡,在有特殊需要時(shí)再使用事件捕獲瓮栗。
1.3削罩、 DOM 事件流
??“DOM2級(jí)事件”規(guī)定的事件流包括三個(gè)階段:事件捕獲階段、處于目標(biāo)階段和事件冒泡階段费奸。
??首先發(fā)生的是事件捕獲弥激,為截獲事件提供了機(jī)會(huì)。
??然后是實(shí)際的目標(biāo)接收到事件愿阐。
??最后一個(gè)階段是冒泡階段微服,可以在這個(gè)階段對(duì)事件做出響應(yīng)。
??以前面簡(jiǎn)單的 HTML 頁(yè)面為例缨历,單擊<div>元素會(huì)按照下圖所示順序觸發(fā)事件以蕴。
??在 DOM 事件流中,實(shí)際的目標(biāo)(<div>元素)在捕獲階段不會(huì)接收到事件辛孵。
??這意味著在捕獲階段丛肮,事件從 document 到<html>再到<body>后就停止了。
??下一個(gè)階段是“處于目標(biāo)”階段魄缚,于是事件在<div>上發(fā)生宝与,并在事件處理(后面將會(huì)討論這個(gè)概念)中被看成冒泡階段的一部分。
??然后,冒泡階段發(fā)生伴鳖,事件又傳播回文檔节值。
??多數(shù)支持 DOM 事件流的瀏覽器都實(shí)現(xiàn)了一種特定的行為;即使“DOM2 級(jí)事件”規(guī)范明確要求捕獲階段不會(huì)涉及事件目標(biāo)榜聂,但 IE9搞疗、Safari、Chrome须肆、Firefox 和 Opera 9.5 及更高版本都會(huì)在捕獲階段觸發(fā)事件對(duì)象上的事件匿乃。結(jié)果,就是有兩個(gè)機(jī)會(huì)在目標(biāo)對(duì)象上面操作事件豌汇。
??IE9幢炸、Opera、Firefox拒贱、Chrome 和 Safari 都支持 DOM 事件流宛徊;IE8 及更早版本不支持 DOM 事件流。
2逻澳、 事件處理程序
??事件就是用戶或?yàn)g覽器自身執(zhí)行的某種動(dòng)作闸天。諸如 click、load 和 mouseover斜做,都是事件的名字苞氮。
??而響應(yīng)某個(gè)事件的函數(shù)就叫做事件處理程序(或事件偵聽器)。
??事件處理程序的名字以"on"開頭瓤逼,因此 click 事件的事件處理程序就是 onclick笼吟,load 事件的事件處理程序就是 onload。為事件指定處理程序的方式有好幾種霸旗。
2.1贷帮、 HTML 事件處理程序
??某個(gè)元素支持的每種事件,都可以使用一個(gè)與相應(yīng)事件處理程序同名的 HTML 特性來(lái)指定诱告。
??這個(gè)特性的值應(yīng)該是能夠執(zhí)行的 JavaScript 代碼皿桑。例如,要在按鈕被單擊時(shí)執(zhí)行一些 JavaScript蔬啡,可以像下面這樣編寫代碼:
<input type="button" value="Click Me" onclick="alert('Clicked')" />
??當(dāng)單擊這個(gè)按鈕時(shí)诲侮,就會(huì)顯示一個(gè)警告框。這個(gè)操作是通過指定 onclick 特性并將一些 JavaScript 代碼作為它的值來(lái)定義的箱蟆。
??由于這個(gè)值是 JavaScript沟绪,因此不能在其中使用未經(jīng)轉(zhuǎn)義的 HTML 語(yǔ)法字符,例如和號(hào)(&)空猜、雙引號(hào)("")绽慈、小于號(hào)(<)或大于號(hào)(>)恨旱。
??為了避免使用 HTML 實(shí)體,這里使用了單引號(hào)坝疼。如果想要使用雙引號(hào)搜贤,那么就要將代碼改寫成如下所示:
<input type="button" value="Click Me" onclick="alert("Clicked")" />
??在 HTML 中定義的事件處理程序可以包含要執(zhí)行的具體動(dòng)作,也可以調(diào)用在頁(yè)面其他地方定義的腳本钝凶,如下面的例子所示:
<script type="text/javascript">
function showMessage(){
alert("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
??在這個(gè)例子中仪芒,單擊按鈕就會(huì)調(diào)用 showMessage() 函數(shù)。這個(gè)函數(shù)是在一個(gè)獨(dú)立的<script>元素中定義的耕陷,當(dāng)然也可以被包含在一個(gè)外部文件中掂名。
??事件處理程序中的代碼在執(zhí)行時(shí),有權(quán)訪問全局作用域中的任何代碼哟沫。
??這樣指定事件處理程序具有一些獨(dú)到之處饺蔑。首先,這樣會(huì)創(chuàng)建一個(gè)封裝著元素屬性值的函數(shù)嗜诀。這個(gè)函數(shù)中有一個(gè)局部變量 event猾警,也就是事件對(duì)象:
<!-- 輸出 "click" -->
<input type="button" value="Click Me" onclick="alert(event.type)">
??通過 event 變量,可以直接訪問事件對(duì)象隆敢,你不用自己定義它发皿,也不用從函數(shù)的參數(shù)列表中讀取。
??在這個(gè)函數(shù)內(nèi)部筑公,this 值等于事件的目標(biāo)元素,例如:
<!-- 輸出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(this.value)">
??關(guān)于這個(gè)動(dòng)態(tài)創(chuàng)建的函數(shù)尊浪,另一個(gè)有意思的地方是它擴(kuò)展作用域的方式匣屡。在這個(gè)函數(shù)內(nèi)部,可以像訪問局部變量一樣訪問 document 及該元素本身的成員拇涤。這個(gè)函數(shù)使用 with 像下面這樣擴(kuò)展作用域:
function(){
with(document){
with(this){
// 元素屬性值
}
}
}
??如此一來(lái)捣作,事件處理程序要訪問自己的屬性就簡(jiǎn)單多了。下面這行代碼與前面的例子效果相同:
<!-- 輸出 "Click Me" -->
<input type="button" value="Click Me" onclick="alert(value)">
??如果當(dāng)前元素是一個(gè)表單輸入元素鹅士,則作用域中還會(huì)包含訪問表單元素(父元素)的入口券躁,這個(gè)函數(shù)就變成了如下所示:
function(){
with(document){
with(this.form){
with(this){
// 元素屬性值
}
}
}
}
??實(shí)際上,這樣擴(kuò)展作用域的方式掉盅,無(wú)非就是想讓事件處理程序無(wú)需引用表單元素就能訪問其他表單字段也拜。例如:
<form method="post">
<input type="text" name="username" value="">
<input type="button" value="Echo Username" onclick="alert(username.value)">
</form>
??在上述例子中,單擊按鈕會(huì)顯示文本框中的文本趾痘。值得注意的是慢哈,這里直接引用了 username 元素。
??不過永票,在 HTML 中指定事件處理程序有兩個(gè)缺點(diǎn)卵贱。首先滥沫,存在一個(gè)時(shí)差問題。因?yàn)橛脩艨赡軙?huì)在 HTML 元素一出現(xiàn)在頁(yè)面上就觸發(fā)相應(yīng)的事件键俱,但當(dāng)時(shí)的事件處理程序有可能尚不具備執(zhí)行條件兰绣。
??以前面的例子來(lái)說明,假設(shè) showMessage() 函數(shù)是在按鈕下方编振、頁(yè)面的最底部定義的缀辩。如果用戶在頁(yè)面解析 showMessage() 函數(shù)之前就單擊了按鈕,就會(huì)引發(fā)錯(cuò)誤党觅。
??為此雌澄,很多 HTML 事件處理程序都會(huì)被封裝在一個(gè) try-catch 塊中,以便錯(cuò)誤不會(huì)浮出水面杯瞻,如下面的例子所示:
<input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}">
??這樣镐牺,如果在 showMessage() 函數(shù)有定義之前單擊了按鈕,用戶將不會(huì)看到 JavaScript 錯(cuò)誤魁莉,因?yàn)樵跒g覽器有機(jī)會(huì)處理錯(cuò)誤之前睬涧,錯(cuò)誤就被捕獲了。
??另一個(gè)缺點(diǎn)是旗唁,這樣擴(kuò)展事件處理程序的作用域鏈在不同瀏覽器中會(huì)導(dǎo)致不同結(jié)果畦浓。不同 JavaScript 引擎遵循的標(biāo)識(shí)符解析規(guī)則略有差異,很可能會(huì)在訪問非限定對(duì)象成員時(shí)出錯(cuò)检疫。
??通過 HTML 指定事件處理程序的最后一個(gè)缺點(diǎn)是 HTML 與 JavaScript 代碼緊密耦合讶请。如果要更換事件處理程序,就要改動(dòng)兩個(gè)地方:HTML 代碼和 JavaScript 代碼屎媳。而這正是許多開發(fā)人員摒棄 HTML 事件處理程序夺溢,轉(zhuǎn)而使用 JavaScript 指定事件處理程序的原因所在。
2.2烛谊、 DOM0 級(jí)事件處理程序
??通過 JavaScript 指定事件處理程序的傳統(tǒng)方式振诬,就是將一個(gè)函數(shù)賦值給一個(gè)事件處理程序?qū)傩浴?br>
??這種為事件處理程序賦值的方法是在第四代 Web 瀏覽器中出現(xiàn)的何址,而且至今仍然為所有現(xiàn)代瀏覽器所支持。原因一是簡(jiǎn)單,二是具有跨瀏覽器的優(yōu)勢(shì)祟昭。
??要使用 JavaScript 指定事件處理程序迂求,首先必須取得一個(gè)要操作的對(duì)象的引用坞古。每個(gè)元素(包括 window 和 document)都有自己的事件處理程序?qū)傩约辏@些屬性通常全部小寫,例如 onclick焙矛。將這種屬性的值設(shè)置為一個(gè)函數(shù)朵诫,就可以指定事件處理程序,如下所示:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
};
??在此薄扁,我們通過文檔對(duì)象取得了一個(gè)按鈕的引用剪返,然后為它指定了 onclick 事件處理程序废累。但要注意,在這些代碼運(yùn)行以前不會(huì)指定事件處理程序脱盲,因此如果這些代碼在頁(yè)面中位于按鈕后面邑滨,就有可能在一段時(shí)間內(nèi)怎么單擊都沒有反應(yīng)。
??使用 DOM0 級(jí)方法指定的事件處理程序被認(rèn)為是元素的方法钱反。因此掖看,這時(shí)候的事件處理程序是在元素的作用域中運(yùn)行;換句話說面哥,程序中的 this 引用當(dāng)前元素哎壳。來(lái)看一個(gè)例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(this.id); // "myBtn"
};
??單擊按鈕顯示的是元素的 ID尚卫,這個(gè) ID 是通過 this.id 取得的归榕。
??不僅僅是 ID,實(shí)際上可以在事件處理程序中通過 this 訪問元素的任何屬性和方法吱涉。以這種方式添加的事件處理程序會(huì)在事件流的冒泡階段被處理刹泄。
??也可以刪除通過 DOM0 級(jí)方法指定的事件處理程序,只要像下面這樣將事件處理程序?qū)傩缘闹翟O(shè)置為 null 即可:
btn.onclick = null; // 刪除事件處理程序
??將事件處理程序設(shè)置為 null 之后怎爵,再單擊按鈕將不會(huì)有任何動(dòng)作發(fā)生特石。
??如果你使用 HTML 指定事件處理程序,那么 onclick 屬性的值就是一個(gè)包含著在同名 HTML 特性中指定的代碼的函數(shù)鳖链。而將相應(yīng)的屬性設(shè)置為 null姆蘸,也可以刪除以這種方式指定的事件處理程序。
2.3芙委、 DOM2 級(jí)事件處理程序
??“DOM2級(jí)事件”定義了兩個(gè)方法逞敷,用于處理指定和刪除事件處理程序的操作:addEventListener() 和 removeEventListener()。
??所有 DOM 節(jié)點(diǎn)中都包含這兩個(gè)方法题山,并且它們都接受 3 個(gè)參數(shù):要處理的事件名兰粉、作為事件處理程序的函數(shù)和一個(gè)布爾值故痊。
??最后這個(gè)布爾值參數(shù)如果是 true顶瞳,表示在捕獲階段調(diào)用事件處理程序;如果是 false愕秫,表示在冒泡階段調(diào)用事件處理程序慨菱。
??要在按鈕上為 click 事件添加事件處理程序,可以使用下列代碼:
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
??上面的代碼為一個(gè)按鈕添加了 onclick 事件處理程序戴甩,而且該事件會(huì)在冒泡階段被觸發(fā)(因?yàn)樽詈笠粋€(gè)參數(shù)是 false)符喝。
??與 DOM0 級(jí)方法一樣,這里添加的事件處理程序也是在其依附的元素的作用域中運(yùn)行甜孤。使用 DOM2 級(jí)方法添加事件處理程序的主要好處是可以添加多個(gè)事件處理程序协饲。來(lái)看下面的例子畏腕。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("Hello world!");
}, false);
??這里為按鈕添加了兩個(gè)事件處理程序。這兩個(gè)事件處理程序會(huì)按照添加它們的順序觸發(fā)茉稠,因此首先會(huì)顯示元素的 ID描馅,其次會(huì)顯示"Hello world!"消息。
??通過 addEventListener() 添加的事件處理程序只能使用 removeEventListener() 來(lái)移除而线;移除時(shí)傳入的參數(shù)與添加處理程序時(shí)使用的參數(shù)相同铭污。這也意味著通過 addEventListener() 添加的匿名函數(shù)將無(wú)法移除,如下面的例子所示膀篮。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.removeEventListener("click", function(){ // 沒有用嘹狞!
alert(this.id);
}, false);
??在上述例子中,我們使用 addEventListener() 添加了一個(gè)事件處理程序誓竿。雖然調(diào)用 removeEventListener() 時(shí)看似使用了相同的參數(shù)磅网,但實(shí)際上,第二個(gè)參數(shù)與傳入 addEventListener() 中的那一個(gè)是完全不同的函數(shù)烤黍。而傳入 removeEventListener() 中的事件處理程序函數(shù)必須與傳入 addEventListener() 中的相同知市,如下面的例子所示。
var btn = document.getElementById("myBtn");
var handler = function(){
alert(this.id);
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false); // 有效速蕊!
??重寫后的這個(gè)例子沒有問題嫂丙,是因?yàn)樵?addEventListener() 和 removeEventListener() 中使用了相同的函數(shù)。
??大多數(shù)情況下规哲,都是將事件處理程序添加到事件流的冒泡階段跟啤,這樣可以最大限度地兼容各種瀏覽器。最好只在需要在事件到達(dá)目標(biāo)之前截獲它的時(shí)候?qū)⑹录幚沓绦蛱砑拥讲东@階段唉锌。如果不是特別需要隅肥,我們不建議在事件捕獲階段注冊(cè)事件處理程序。
IE9袄简、Firefox腥放、Safari、Chrome 和 Opera 支持 DOM2 級(jí)事件處理程序绿语。
2.4秃症、 IE 事件處理程序
??IE 實(shí)現(xiàn)了與 DOM 中類似的兩個(gè)方法:attachEvent() 和 detachEvent()。
??這兩個(gè)方法接受相同的兩個(gè)參數(shù):事件處理程序名稱與事件處理程序函數(shù)吕粹。
??由于 IE8 及更早版本只支持事件冒泡种柑,所以通過 attachEvent() 添加的事件處理程序都會(huì)被添加到冒泡階段。
??要使用 attachEvent() 為按鈕添加一個(gè)事件處理程序匹耕,可以使用以下代碼聚请。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
??注意,attachEvent() 的第一個(gè)參數(shù)是"onclick"稳其,而非 DOM 的 addEventListener() 方法中的"click"驶赏。
??在 IE 中使用 attachEvent() 與使用 DOM0 級(jí)方法的主要區(qū)別在于事件處理程序的作用域炸卑。在使用 DOM0 級(jí)方法的情況下链韭,事件處理程序會(huì)在其所屬元素的作用域內(nèi)運(yùn)行摘刑;在使用 attachEvent() 方法的情況下瞧掺,事件處理程序會(huì)在全局作用域中運(yùn)行,因此 this 等于 window夺巩。來(lái)看下面的例子。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert(this === window); // true
});
??在編寫跨瀏覽器的代碼時(shí)周崭,牢記這一區(qū)別非常重要柳譬。
??與 addEventListener() 類似,attachEvent() 方法也可以用來(lái)為一個(gè)元素添加多個(gè)事件處理程序续镇。來(lái)看下面的例子美澳。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
btn.attachEvent("onclick", function(){
alert("Hello world!");
});
??這里調(diào)用了兩次 attachEvent(),為同一個(gè)按鈕添加了兩個(gè)不同的事件處理程序。
??不過制跟,與 DOM 方法不同的是舅桩,這些事件處理程序不是以添加它們的順序執(zhí)行,而是以相反的順序被觸發(fā)雨膨。單擊這個(gè)例子中的按鈕擂涛,首先看到的是"Hello world!",然后才是"Clicked"聊记。
??使用 attachEvent() 添加的事件可以通過 detachEvent() 來(lái)移除撒妈,條件是必須提供相同的參數(shù)。
??與 DOM 方法一樣排监,這也意味著添加的匿名函數(shù)將不能被移除踩身。不過,只要能夠?qū)?duì)相同函數(shù)的引用傳給 detachEvent()社露,就可以移除相應(yīng)的事件處理程序挟阻。例如:
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
btn.attachEvent("onclick", handler);
btn.detachEvent("onclick", handler);
??上述例子將保存在變量 handler 中的函數(shù)作為事件處理程序。因此峭弟,后面的 detachEvent() 可以使用相同的函數(shù)來(lái)移除事件處理程序附鸽。
支持 IE 事件處理程序的瀏覽器有 IE 和 Opera。
2.5瞒瘸、 跨瀏覽器的事件處理程序
??為了以跨瀏覽器的方式處理事件坷备,不少開發(fā)人員會(huì)使用能夠隔離瀏覽器差異的 JavaScript 庫(kù),還有一些開發(fā)人員會(huì)自己開發(fā)最合適的事件處理的方法情臭。自己編寫代碼其實(shí)也不難省撑,只要恰當(dāng)?shù)厥褂媚芰z測(cè)即可。
??要保證處理事件的代碼能在大多數(shù)瀏覽器下一致地運(yùn)行俯在,只需關(guān)注冒泡階段竟秫。
??第一個(gè)要?jiǎng)?chuàng)建的方法是 addHandler(),它的職責(zé)是視情況分別使用 DOM0 級(jí)方法跷乐、DOM2 級(jí)方法或 IE 方法來(lái)添加事件肥败。這個(gè)方法屬于一個(gè)名叫 EventUtil 的對(duì)象,這里將使用這個(gè)對(duì)象來(lái)處理瀏覽器間的差異愕提。
??addHandler() 方法接受 3 個(gè)參數(shù):要操作的元素馒稍、事件名稱和事件處理程序函數(shù)。
??與 addHandler() 對(duì)應(yīng)的方法是 removeHandler()浅侨,它也接受相同的參數(shù)纽谒。這個(gè)方法的職責(zé)是移除之前添加的事件處理程序——無(wú)論該事件處理程序是采取什么方式添加到元素中的,如果其他方法無(wú)效如输,默認(rèn)采用 DOM0 級(jí)方法鼓黔。
??EventUtil 的用法如下所示央勒。
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler){
if (element.removeEventListener){
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
};
??這兩個(gè)方法首先都會(huì)檢測(cè)傳入的元素中是否存在 DOM2 級(jí)方法。如果存在 DOM2 級(jí)方法请祖,則使用該方法:傳入事件類型、事件處理程序函數(shù)和第三個(gè)參數(shù) false(表示冒泡階段)脖祈。
??如果存在的是 IE 的方法肆捕,則采取第二種方案。注意盖高,為了在 IE8 及更早版本中運(yùn)行慎陵,此時(shí)的事件類型必須加上"on"前綴。
??最后一種可能就是使用 DOM0 級(jí)方法(在現(xiàn)代瀏覽器中冲甘,應(yīng)該不會(huì)執(zhí)行這里的代碼)县踢。此時(shí)邻悬,我們使用的是方括號(hào)語(yǔ)法來(lái)將屬性名指定為事件處理程序,或者將屬性設(shè)置為 null润梯。
??可以像下面這樣使用 EventUtil 對(duì)象:
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
EventUtil.removeHandler(btn, "click", handler);
??addHandler() 和 removeHandler() 沒有考慮到所有的瀏覽器問題,例如在 IE 中的作用域問題甥厦。
??不過纺铭,使用它們添加和移除事件處理程序還是足夠了。此外還要注意刀疙,DOM0 級(jí)對(duì)每個(gè)事件只支持一個(gè)事件處理程序舶赔。好在,只支持 DOM0 級(jí)的瀏覽器已經(jīng)沒有那么多了谦秧,因此這對(duì)你而言應(yīng)該不是什么問題竟纳。
3、 事件對(duì)象
??在觸發(fā) DOM 上的某個(gè)事件時(shí)疚鲤,會(huì)產(chǎn)生一個(gè)事件對(duì)象 event锥累,這個(gè)對(duì)象中包含著所有與事件有關(guān)的信息。包括導(dǎo)致事件的元素集歇、事件的類型以及其他與特定事件相關(guān)的信息揩悄。
??例如,鼠標(biāo)操作導(dǎo)致的事件對(duì)象中鬼悠,會(huì)包含鼠標(biāo)位置的信息删性,而鍵盤操作導(dǎo)致的事件對(duì)象中,會(huì)包含與按下的鍵有關(guān)的信息焕窝。所有瀏覽器都支持 event 對(duì)象蹬挺,但支持方式不同。
3.1它掂、 DOM 中的事件對(duì)象
??兼容 DOM 的瀏覽器會(huì)將一個(gè) event 對(duì)象傳入到事件處理程序中巴帮。無(wú)論指定事件處理程序時(shí)使用什么方法(DOM0 級(jí)或 DOM2 級(jí))溯泣,都會(huì)傳入 event 對(duì)象。來(lái)看下面的例子榕茧。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.type); // "click"
};
btn.addEventListener("click", function(event){
alert(event.type); // "click"
}, false);
??這個(gè)例子中的兩個(gè)事件處理程序都會(huì)彈出一個(gè)警告框垃沦,顯示由 event.type 屬性表示的事件類型。這個(gè)屬性始終都會(huì)包含被觸發(fā)的事件類型用押,例如"click"(與傳入 addEventListener() 和 removeEventListener() 中的事件類型一致)肢簿。
??在通過 HTML 特性指定事件處理程序時(shí),變量 event 中保存著 event 對(duì)象蜻拨。請(qǐng)看下面的例子池充。
<input type="button" value="Click Me" onclick="alert(event.type)"/>
??以這種方式提供 event 對(duì)象,可以讓 HTML 特性事件處理程序與 JavaScript 函數(shù)執(zhí)行相同的操作缎讼。
??event 對(duì)象包含與創(chuàng)建它的特定事件有關(guān)的屬性和方法收夸。觸發(fā)的事件類型不一樣,可用的屬性和方法也不一樣血崭。不過卧惜,所有事件都會(huì)有下表列出的成員。
屬性/方法 | 類 型 | 讀/寫 | 說 明 |
---|---|---|---|
bubbles | Boolean | ????只讀? | 表明事件是否冒泡 |
cancelable | Boolean | 只讀 | 表明是否可以取消事件的默認(rèn)行為 |
currentTarget | Element | 只讀 | 其事件處理程序當(dāng)前正在處理事件的那個(gè)元素 |
defaultPrevented | Boolean | 只讀 | 為 true 表示已經(jīng)調(diào)用了 preventDefault()(DOM3級(jí)事件中新增) |
detail | Integer | 只讀 | 與事件相關(guān)的細(xì)節(jié)信息 |
eventPhase | Integer | 只讀 | 調(diào)用事件處理程序的階段:1表示捕獲階段夹纫,2表示“處于目標(biāo)”序苏,3表示冒泡階段 |
preventDefault() | Function | 只讀 | 取消事件的默認(rèn)行為。如果cancelable是true捷凄,則可以使用這個(gè)方法 |
stopImmediatePropagation() | Function | 只讀 | 取消事件的進(jìn)一步捕獲或冒泡忱详,同時(shí)阻止任何事件處理程序被調(diào)用(DOM3級(jí)事件中新增) |
stopPropagation() | Function | 只讀 | 取消事件的進(jìn)一步捕獲或冒泡。如果bubbles為true跺涤,則可以使用這個(gè)方法 |
target | Element | 只讀 | 事件的目標(biāo) |
trusted | Boolean | 只讀 | 為true表示事件是瀏覽器生成的匈睁。為false表示事件是由開發(fā)人員通過 JavaScript 創(chuàng)建的(DOM3級(jí)事件中新增) |
type | String | 只讀 | 被觸發(fā)的事件的類型 |
view | AbstractView | 只讀 | 與事件關(guān)聯(lián)的抽象視圖。等同于發(fā)生事件的window對(duì)象 |
??在事件處理程序內(nèi)部桶错,對(duì)象 this 始終等于 currentTarget 的值航唆,而 target 則只包含事件的實(shí)際目標(biāo)。如果直接將事件處理程序指定給了目標(biāo)元素院刁,則 this糯钙、currentTarget 和 target 包含相同的值。來(lái)看下面的例子退腥。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.currentTarget === this); // true
alert(event.target === this); // true
};
??這個(gè)例子檢測(cè)了 currentTarget 和 target 與 this 的值任岸。由于 click 事件的目標(biāo)是按鈕,因此這三個(gè)值是相等的狡刘。
??如果事件處理程序存在于按鈕的父節(jié)點(diǎn)中(例如 document.body)享潜,那么這些值是不相同的。再看下面的例子嗅蔬。
document.body.onclick = function(event){
alert(event.currentTarget === document.body); // true
alert(this === document.body); // true
alert(event.target === document.getElementById("myBtn")); // true
};
??當(dāng)單擊這個(gè)例子中的按鈕時(shí)剑按,this 和 currentTarget 都等于 document.body疾就,因?yàn)槭录幚沓绦蚴亲?cè)到這個(gè)元素上的。然而艺蝴,target 元素卻等于按鈕元素猬腰,因?yàn)樗?click 事件真正的目標(biāo)。
??由于按鈕上并沒有注冊(cè)事件處理程序猜敢,結(jié)果 click 事件就冒泡到了 document.body姑荷,在那里事件才得到了處理。
??在需要通過一個(gè)函數(shù)處理多個(gè)事件時(shí)锣枝,可以使用 type 屬性厢拭。例如:
var btn = document.getElementById("myBtn");
var handler = function(event){
switch(event.type){
case "click":
alert("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
??上述例子定義了一個(gè)名為 handler 的函數(shù)兰英,用于處理 3 種事件:click撇叁、mouseover 和 mouseout。
??當(dāng)單擊按鈕時(shí)畦贸,會(huì)出現(xiàn)一個(gè)與前面例子中一樣的警告框陨闹。當(dāng)按鈕移動(dòng)到按鈕上面時(shí),背景顏色應(yīng)該會(huì)變成紅色薄坏,而當(dāng)鼠標(biāo)移動(dòng)出按鈕的范圍時(shí)趋厉,背景顏色應(yīng)該會(huì)恢復(fù)為默認(rèn)值。
??這里通過檢測(cè) event.type 屬性胶坠,讓函數(shù)能夠確定發(fā)生了什么事件君账,并執(zhí)行相應(yīng)的操作。
??要阻止特定事件的默認(rèn)行為沈善,可以使用 preventDefault() 方法乡数。例如,鏈接的默認(rèn)行為就是在被單擊時(shí)會(huì)導(dǎo)航到其 href 特性指定的 URL闻牡。如果你想阻止鏈接導(dǎo)航這一默認(rèn)行為净赴,那么通過鏈接的 onclick 事件處理程序可以取消它,如下面的例子所示罩润。
var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
};
??只有 cancelable 屬性設(shè)置為 true 的事件玖翅,才可以使用 preventDefault() 來(lái)取消其默認(rèn)行為。
??另外割以,stopPropagation() 方法用于立即停止事件在 DOM 層次中的傳播金度,即取消進(jìn)一步的事件捕獲或冒泡。例如严沥,直接添加到一個(gè)按鈕的事件處理程序可以調(diào)用 stopPropagation()审姓,從而避免觸發(fā)注冊(cè)在 document.body 上面的事件處理程序,如下面的例子所示祝峻。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event){
alert("Body clicked");
};
??對(duì)于這個(gè)例子而言魔吐,如果不調(diào)用 stopPropagation()扎筒,就會(huì)在單擊按鈕時(shí)出現(xiàn)兩個(gè)警告框〕昴罚可是嗜桌,由于 click 事件根本不會(huì)傳播到 document.body,因此就不會(huì)觸發(fā)注冊(cè)在這個(gè)元素上的 onclick 事件處理程序辞色。
??事件對(duì)象的 eventPhase 屬性骨宠,可以用來(lái)確定事件當(dāng)前正位于事件流的哪個(gè)階段。
??如果是在捕獲階段調(diào)用的事件處理程序相满,那么 eventPhase 等于 1层亿;如果事件處理程序處于目標(biāo)對(duì)象上,則 eventPhase 等于 2立美;如果是在冒泡階段調(diào)用的事件處理程序匿又,eventPhase 等于 3。
??這里要注意的是建蹄,盡管“處于目標(biāo)”發(fā)生在冒泡階段碌更,但 eventPhase 仍然一直等于 2。來(lái)看下面的例子洞慎。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.eventPhase); // 2
};
document.body.addEventListener("click", function(event){
alert(event.eventPhase); // 1
}, true);
document.body.onclick = function(event){
alert(event.eventPhase); // 3
};
??當(dāng)單擊這個(gè)例子中的按鈕時(shí)痛单,首先執(zhí)行的事件處理程序是在捕獲階段觸發(fā)的添加到 document.body 中的那一個(gè),結(jié)果會(huì)彈出一個(gè)警告框顯示表示 eventPhase 的 1劲腿。接著旭绒,會(huì)觸發(fā)在按鈕上注冊(cè)的事件處理程序,此時(shí)的 eventPhase 值為 2挥吵。最后一個(gè)被觸發(fā)的事件處理程序冈爹,是在冒泡階段執(zhí)行的添加到 document.body 上的那一個(gè)租副,顯示 eventPhase 的值為 3坐慰。
??而當(dāng) eventPhase 等于 2 時(shí),this用僧、target 和 currentTarget 始終都是相等的结胀。
??只有在事件處理程序執(zhí)行期間,event 對(duì)象才會(huì)存在责循;一旦事件處理程序執(zhí)行完成糟港,event 對(duì)象就會(huì)被銷毀。
3.2院仿、 IE 中的事件對(duì)象
??與訪問 DOM 中的 event 對(duì)象不同秸抚,要訪問 IE 中的 event 對(duì)象有幾種不同的方式,取決于指定事件處理程序的方法歹垫。
??在使用 DOM0 級(jí)方法添加事件處理程序時(shí)剥汤,event 對(duì)象作為 window 對(duì)象的一個(gè)屬性存在。來(lái)看下面的例子排惨。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var event = window.event;
alert(event.type); // "click"
};
??在此吭敢,我們通過 window.event 取得了 event 對(duì)象,并檢測(cè)了被觸發(fā)事件的類型(IE 中的 type 屬性與 DOM 中的 type 屬性是相同的)暮芭÷雇眨可是欲低,如果事件處理程序是使用 attachEvent() 添加的,那么就會(huì)有一個(gè) event 對(duì)象作為參數(shù)被傳入事件處理程序函數(shù)中畜晰,如下所示伸头。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event){
alert(event.type); // "click"
});
??在像這樣使用 attachEvent() 的情況下,也可以通過 window 對(duì)象來(lái)訪問 event 對(duì)象,就像使用 DOM0 級(jí)方法時(shí)一樣汤善。不過為方便起見液茎,同一個(gè)對(duì)象也會(huì)作為參數(shù)傳遞。
??如果是通過 HTML 特性指定的事件處理程序扫步,那么還可以通過一個(gè)名叫 event 的變量來(lái)訪問 event 對(duì)象(與 DOM 中的事件模型相同)。再看一個(gè)例子匈子。
<input type="button" value="Click Me" onclick="alert(event.type)">
??IE 的 event 對(duì)象同樣也包含與創(chuàng)建它的事件相關(guān)的屬性和方法河胎。其中很多屬性和方法都有對(duì)應(yīng)的或者相關(guān)的 DOM 屬性和方法。與 DOM 的 event 對(duì)象一樣虎敦,這些屬性和方法也會(huì)因?yàn)槭录愋偷牟煌煌卧溃惺录?duì)象都會(huì)包含下表所列的屬性和方法。
屬性/方法 | 類 型 | 讀/寫 | 說 明 |
---|---|---|---|
cancelBubble | Boolean | ???讀/寫 | 默認(rèn)值為false其徙,但將其設(shè)置為true就可以取消事件冒泡(與DOM中的stopPropagation()方法的作用相同) |
returnValue | Boolean | 讀/寫 | 默認(rèn)值為true胚迫,但將其設(shè)置為false就可以取消事件的默認(rèn)行為(與DOM中的preventDefault()方法的作用相同) |
srcElement | Element | 只讀 | 事件的目標(biāo)(與DOM中的target屬性相同) |
type | String | 只讀 | 被觸發(fā)的事件的類型 |
??因?yàn)槭录幚沓绦虻淖饔糜蚴歉鶕?jù)指定它的方式來(lái)確定的,所以不能認(rèn)為 this 會(huì)始終等于事件目標(biāo)唾那。故而访锻,最好還是使用 event.srcElement 比較保險(xiǎn)。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(window.event.srcElement === this); // true
};
btn.attachEvent("onclick", function(event){
alert(event.srcElement === this); // false
});
??在第一個(gè)事件處理程序中(使用 DOM0 級(jí)方法指定的)闹获,srcElement 屬性等于 this期犬,但在第二個(gè)事件處理程序中,這兩者的值不相同避诽。
??如前所述龟虎,returnValue 屬性相當(dāng)于 DOM 中的 preventDefault() 方法,它們的作用都是取消給定事件的默認(rèn)行為沙庐。只要將 returnValue 設(shè)置為 false鲤妥,就可以阻止默認(rèn)行為。來(lái)看下面的例子轨功。
var link = document.getElementById("myLink");
link.onclick = function(){
window.event.returnValue = false;
};
??這個(gè)例子在onclick 事件處理程序中使用 returnValue 達(dá)到了阻止鏈接默認(rèn)行為的目的旭斥。與 DOM 不同的是,在此沒有辦法確定事件是否能被取消古涧。
??相應(yīng)地垂券,cancelBubble 屬性與 DOM 中的 stopPropagation() 方法作用相同,都是用來(lái)停止事件冒泡的。由于 IE 不支持事件捕獲菇爪,因而只能取消事件冒泡算芯;但 stopPropagatioin() 可以同時(shí)取消事件捕獲和冒泡。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function(){
alert("Body clicked");
};
??通過在 onclick 事件處理程序中將 cancelBubble 設(shè)置為 true凳宙,就可阻止事件通過冒泡而觸發(fā) document.body 中注冊(cè)的事件處理程序熙揍。結(jié)果,在單擊按鈕之后氏涩,只會(huì)顯示一個(gè)警告框届囚。
3.3、 跨瀏覽器的事件對(duì)象
??雖然 DOM 和 IE 中的 event 對(duì)象不同是尖,但基于它們之間的相似性依舊可以拿出跨瀏覽器的方案來(lái)意系。
??IE 中 event 對(duì)象的全部信息和方法 DOM 對(duì)象中都有,只不過實(shí)現(xiàn)方式不一樣饺汹。不過蛔添,這種對(duì)應(yīng)關(guān)系讓實(shí)現(xiàn)兩種事件模型之間的映射非常容易《荡牵可以對(duì)前面介紹的 EventUtil 對(duì)象加以增強(qiáng)迎瞧,添加如下方法以求同存異。
var EventUtil = {
addHandler: function(element, type, handler){
//省略的代碼
},
getEvent: function(event){
return event ? event : window.event;
},
getTarget: function(event){
return event.target || event.srcElement;
},
preventDefault: function(event){
if (event.preventDefault){
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler){
// 省略的代碼
},
stopPropagation: function(event){
if (event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
??以上代碼顯示逸吵,我們?yōu)?EventUtil 添加了 4 個(gè)新方法凶硅。
??第一個(gè)是 getEvent(),它返回對(duì) event 對(duì)象的引用胁塞∮匠ⅲ考慮到 IE 中事件對(duì)象的位置不同压语,可以使用這個(gè)方法來(lái)取得 event 對(duì)象啸罢,而不必?fù)?dān)心指定事件處理程序的方式。在使用這個(gè)方法時(shí)胎食,必須假設(shè)有一個(gè)事件對(duì)象傳入到事件處理程序中扰才,而且要把該變量傳給這個(gè)方法,如下所示厕怜。
btn.onclick = function(event){
event = EventUtil.getEvent(event);
};
??在兼容 DOM 的瀏覽器中衩匣,event 變量只是簡(jiǎn)單地傳入和返回。而在 IE 中粥航,event 參數(shù)是未定義的(undefined)琅捏,因此就會(huì)返回 window.event。將這一行代碼添加到事件處理程序的開頭递雀,就可以確保隨時(shí)都能使用 event 對(duì)象柄延,而不必?fù)?dān)心用戶使用的是什么瀏覽器。
??第二個(gè)方法是 getTarget()缀程,它返回事件的目標(biāo)搜吧。在這個(gè)方法內(nèi)部市俊,會(huì)檢測(cè) event 對(duì)象的 target 屬性,如果存在則返回該屬性的值滤奈;否則摆昧,返回 srcElement 屬性的值⊙殉蹋可以像下面這樣使用這個(gè)方法绅你。
btn.onclick = function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
};
??第三個(gè)方法是 preventDefault(),用于取消事件的默認(rèn)行為昭躺。在傳入 event 對(duì)象后勇吊,這個(gè)方法會(huì)檢查是否存在 preventDefault() 方法,如果存在則調(diào)用該方法窍仰。如果 preventDefault() 方法不存在汉规,則將 returnValue 設(shè)置為 false。下面是使用這個(gè)方法的例子驹吮。
var link = document.getElementById("myLink");
link.onclick = function(event){
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
};
??以上代碼可以確保在所有瀏覽器中單擊該鏈接都不會(huì)打開另一個(gè)頁(yè)面针史。首先,使用 EventUtil.getEvent() 取得 event 對(duì)象碟狞,然后將其傳入到 EventUtil.preventDefault() 以取消默認(rèn)行為啄枕。
??第四個(gè)方法是 stopPropagation(),其實(shí)現(xiàn)方式類似族沃。首先嘗試使用 DOM 方法阻止事件流频祝,否則就使用 cancelBubble 屬性。下面看一個(gè)例子脆淹。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert("Clicked");
event = EventUtil.getEvent(event);
EventUtil.stopPropagation(event);
};
document.body.onclick = function(event){
alert("Body clicked");
};
??在此常空,首先使用 EventUtil.getEvent() 取得了 event 對(duì)象,然后又將其傳入到 EventUtil.stopPropagation()盖溺。別忘了由于 IE 不支持事件捕獲漓糙,因此這個(gè)方法在跨瀏覽器的情況下,也只能用來(lái)阻止事件冒泡烘嘱。
4昆禽、 事件類型
??Web 瀏覽器中可能發(fā)生的事件有很多類型。如前所述蝇庭,不同的事件類型具有不同的信息醉鳖,而“DOM3 級(jí)事件”規(guī)定了以下幾類事件。
- UI(User Interface哮内,用戶界面)事件盗棵,當(dāng)用戶與頁(yè)面上的元素交互時(shí)觸發(fā);
- 焦點(diǎn)事件,當(dāng)元素獲得或失去焦點(diǎn)時(shí)觸發(fā)漾根;
- 鼠標(biāo)事件泰涂,當(dāng)用戶通過鼠標(biāo)在頁(yè)面上執(zhí)行操作時(shí)觸發(fā);
- 滾輪事件辐怕,當(dāng)使用鼠標(biāo)滾輪(或類似設(shè)備)時(shí)觸發(fā)逼蒙;
- 文本事件,當(dāng)在文檔中輸入文本時(shí)觸發(fā)寄疏;
- 鍵盤事件是牢,當(dāng)用戶通過鍵盤在頁(yè)面上執(zhí)行操作時(shí)觸發(fā);
- 合成事件陕截,當(dāng)為 IME(Input Method Editor驳棱,輸入法編輯器)輸入字符時(shí)觸發(fā);
- 變動(dòng)(mutation)事件农曲,當(dāng)?shù)讓?DOM 結(jié)構(gòu)發(fā)生變化時(shí)觸發(fā)社搅。
- 變動(dòng)名稱事件,當(dāng)元素或?qū)傩悦儎?dòng)時(shí)觸發(fā)乳规。此類事件已經(jīng)被廢棄形葬,沒有任何瀏覽器實(shí)現(xiàn)它們,因此本章不做介紹暮的。
??除了這幾類事件之外笙以,HTML5 也定義了一組事件,而有些瀏覽器還會(huì)在 DOM 和 BOM 中實(shí)現(xiàn)其他專有事件冻辩。這些專有的事件一般都是根據(jù)開發(fā)人員需求定制的猖腕,沒有什么規(guī)范,因此不同瀏覽器的實(shí)現(xiàn)有可能不一致恨闪。
??DOM3 級(jí)事件模塊在 DOM2 級(jí)事件模塊基礎(chǔ)上重新定義了這些事件倘感,也添加了一些新事件。包括 IE9 在內(nèi)的所有主流瀏覽器都支持 DOM2 級(jí)事件凛剥。IE9 也支持 DOM3 級(jí)事件侠仇。
4.1、 UI 事件
??UI 事件指的是那些不一定與用戶操作有關(guān)的事件犁珠。這些事件在 DOM 規(guī)范出現(xiàn)之前,都是以這種或那種形式存在的互亮,而在 DOM 規(guī)范中保留是為了向后兼容±缦恚現(xiàn)有的 UI 事件如下。
- DOMActivate:表示元素已經(jīng)被用戶操作(通過鼠標(biāo)或鍵盤)激活豹休。這個(gè)事件在 DOM3 級(jí)事件中被廢棄炊昆,但 Firefox 2+和 Chrome 支持它。考慮到不同瀏覽器實(shí)現(xiàn)的差異凤巨,不建議使用這個(gè)事件视乐。
- load:當(dāng)頁(yè)面完全加載后在 window 上面觸發(fā),當(dāng)所有框架都加載完畢時(shí)在框架集上面觸發(fā)敢茁,當(dāng)圖像加載完畢時(shí)在 <img> 元素上面觸發(fā)佑淀,或者當(dāng)嵌入的內(nèi)容加載完畢時(shí)在<object>元素上面觸發(fā)。
- unload:當(dāng)頁(yè)面完全卸載后在 window 上面觸發(fā)彰檬,當(dāng)所有框架都卸載后在框架集上面觸發(fā)伸刃,或者當(dāng)嵌入的內(nèi)容卸載完畢后在<object>元素上面觸發(fā)。
- abort:在用戶停止下載過程時(shí)逢倍,如果嵌入的內(nèi)容沒有加載完捧颅,則在<object>元素上面觸發(fā)。
- error:當(dāng)發(fā)生 JavaScript 錯(cuò)誤時(shí)在 window 上面觸發(fā)较雕,當(dāng)無(wú)法加載圖像時(shí)在 <img> 元素上面觸發(fā)碉哑,當(dāng)無(wú)法加載嵌入內(nèi)容時(shí)在<object>元素上面觸發(fā)登颓,或者當(dāng)有一或多個(gè)框架無(wú)法加載時(shí)在框架集上面觸發(fā)顶瞒。
- select:當(dāng)用戶選擇文本框(<input>或<texterea>)中的一或多個(gè)字符時(shí)觸發(fā)柴底。
- resize:當(dāng)窗口或框架的大小變化時(shí)在 window 或框架上面觸發(fā)累盗。
- scroll:當(dāng)用戶滾動(dòng)帶滾動(dòng)條的元素中的內(nèi)容時(shí)点把,在該元素上面觸發(fā)钞楼。<body>元素中包含所加載頁(yè)面的滾動(dòng)條鸿竖。
??多數(shù)這些事件都與 window 對(duì)象或表單控件相關(guān)煮盼。
??除了 DOMActivate 之外凄吏,其他事件在 DOM2 級(jí)事件中都?xì)w為 HTML 事件(DOMActivate 在 DOM2 級(jí)中仍然屬于 UI 事件)远舅。要確定瀏覽器是否支持 DOM2 級(jí)事件規(guī)定的 HTML 事件,可以使用如下代碼:
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
??注意痕钢,只有根據(jù)“DOM2 級(jí)事件”實(shí)現(xiàn)這些事件的瀏覽器才會(huì)返回 true图柏。而以非標(biāo)準(zhǔn)方式支持這些事件的瀏覽器則會(huì)返回 false。要確定瀏覽器是否支持“DOM3 級(jí)事件”定義的事件任连,可以使用如下代碼:
var isSupported = document.implementation.hasFeature("UIEvent", "3.0");
1. load 事件
??JavaScript 中最常用的一個(gè)事件就是 load蚤吹。當(dāng)頁(yè)面完全加載后(包括所有圖像、JavaScript 文件随抠、CSS 文件等外部資源)裁着,就會(huì)觸發(fā) window 上面的 load 事件。
??有兩種定義 onload 事件處理程序的方式拱她。第一種方式是使用如下所示的 JavaScript 代碼:
EventUtil.addHandler(window, "load", function(event){
alert("Loaded!");
});
??這是通過 JavaScript 來(lái)指定事件處理程序的方式二驰,使用了本章前面定義的跨瀏覽器的 EventUtil 對(duì)象。與添加其他事件一樣秉沼,這里也給事件處理程序傳入了一個(gè) event 對(duì)象桶雀。這個(gè) event 對(duì)象中不包含有關(guān)這個(gè)事件的任何附加信息矿酵,但在兼容 DOM 的瀏覽器中,event.target 屬性的值會(huì)被設(shè)置為 document矗积,而 IE 并不會(huì)為這個(gè)事件設(shè)置 srcElement 屬性全肮。
??第二種指定 onload 事件處理程序的方式是為<body>元素添加一個(gè) onload 特性,如下面的例子所示:
<!DOCTYPE html>
<html>
<head>
<title>Load Event Example</title>
</head>
<body onload="alert('Loaded!')">
</body>
</html>
??一般來(lái)說棘捣,在 window 上面發(fā)生的任何事件都可以在<body>元素中通過相應(yīng)的特性來(lái)指定辜腺,因?yàn)樵?HTML 中無(wú)法訪問 window 元素。實(shí)際上柱锹,這只是為了保證向后兼容的一種權(quán)宜之計(jì)哪自,但所有瀏覽器都能很好地支持這種方式。
??我們建議讀者盡可能使用 JavaScript 方式禁熏。
??根據(jù)“DOM2 級(jí)事件”規(guī)范壤巷,應(yīng)該在 document 而非 window 上面觸發(fā) load 事件。但是瞧毙,所有瀏覽器都在 window 上面實(shí)現(xiàn)了該事件胧华,以確保向后兼容。
??圖像上面也可以觸發(fā) load 事件宙彪,無(wú)論是在 DOM 中的圖像元素還是 HTML 中的圖像元素矩动。因此,可以在 HTML 中為任何圖像指定 onload 事件處理程序释漆,例如:
<img src="smile.gif" onload="alert('Image loaded.')">
??這樣悲没,當(dāng)例子中的圖像加載完畢后就會(huì)顯示一個(gè)警告框。同樣的功能也可以使用 JavaScript 來(lái)實(shí)現(xiàn)男图,例如:
var image = document.getElementById("myImage");
EventUtil.addHandler(image, "load", function(event){
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
});
??這里示姿,使用 JavaScript 指定了 onload 事件處理程序。同時(shí)也傳入了 event 對(duì)象逊笆,盡管它也不包含什么有用的信息栈戳。不過,事件的目標(biāo)是<img>元素难裆,因此可以通過 src 屬性訪問并顯示該信息子檀。
??在創(chuàng)建新的<img>元素時(shí),可以為其指定一個(gè)事件處理程序乃戈,以便圖像加載完畢后給出提示褂痰。此時(shí),最重要的是要在指定 src 屬性之前先指定事件偏化,如下面的例子所示脐恩。
EventUtil.addHandler(window, "load", function(){
var image = document.createElement("img");
EventUtil.addHandler(image, "load", function(event){
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
});
document.body.appendChild(image);
image.src = "smile.gif";
});
??在這個(gè)例子中,首先為 window 指定了 onload 事件處理程序侦讨。原因在于驶冒,我們是想向 DOM 中添加一個(gè)新元素,所以必須確定頁(yè)面已經(jīng)加載完畢——如果在頁(yè)面加載前操作 document.body 會(huì)導(dǎo)致錯(cuò)誤韵卤。
??然后骗污,創(chuàng)建了一個(gè)新的圖像元素,并設(shè)置了其 onload 事件處理程序沈条。最后又將這個(gè)圖像添加到頁(yè)面中需忿,還設(shè)置了它的 src 屬性。這里有一點(diǎn)需要格外注意:新圖像元素不一定要從添加到文檔后才開始下載蜡歹,只要設(shè)置了 src 屬性就會(huì)開始下載屋厘。
??同樣的功能也可以通過使用 DOM0 級(jí)的 Image 對(duì)象實(shí)現(xiàn)。在 DOM 出現(xiàn)之前月而,開發(fā)人員經(jīng)常使用 Image 對(duì)象在客戶端預(yù)先加載圖像汗洒。可以像使用<img>元素一樣使用 Image 對(duì)象父款,只不過無(wú)法將其添加到 DOM 樹中溢谤。下面來(lái)看一個(gè)例子。
EventUtil.addHandler(window, "load", function(){
var image = new Image();
EventUtil.addHandler(image, "load", function(event){
alert("Image loaded!");
});
image.src = "smile.gif";
});
??在此憨攒,我們使用 Image 構(gòu)造函數(shù)創(chuàng)建了一個(gè)新圖像的實(shí)例世杀,然后又為它指定了事件處理程序。有的瀏覽器將 Image 對(duì)象實(shí)現(xiàn)為<img>元素肝集,但并非所有瀏覽器都如此瞻坝,所以最好將它們區(qū)別對(duì)待。
??在不屬于 DOM 文檔的圖像(包括未添加到文檔的<img>元素和 Image 對(duì)象)上觸發(fā) load 事件時(shí)杏瞻,IE8 及之前版本不會(huì)生成 event 對(duì)象所刀。IE9 修復(fù)了這個(gè)問題。
??還有一些元素也以非標(biāo)準(zhǔn)的方式支持 load 事件伐憾。在 IE9+勉痴、Firefox、Opera树肃、Chrome 和 Safari 3+及更高版本中蒸矛,<script>元素也會(huì)觸發(fā) load 事件,以便開發(fā)人員確定動(dòng)態(tài)加載的 JavaScript 文件是否加載完畢胸嘴。
??與圖像不同雏掠,只有在設(shè)置了<script>元素的 src 屬性并將該元素添加到文檔后,才會(huì)開始下載 JavaScript 文件劣像。換句話說乡话,對(duì)于<script>元素而言,指定 src 屬性和指定事件處理程序的先后順序就不重要了耳奕。以下代碼展示了怎樣為<script>元素指定事件處理程序绑青。
EventUtil.addHandler(window, "load", function(){
var script = document.createElement("script");
EventUtil.addHandler(script, "load", function(event){
alert("Loaded");
});
script.src = "example.js";
document.body.appendChild(script);
});
??這個(gè)例子使用了跨瀏覽器的 EventUtil 對(duì)象為新創(chuàng)建的<script>元素指定了onload 事件處理程序诬像。此時(shí),大多數(shù)瀏覽器中 event 對(duì)象的 target 屬性引用的都是<script>節(jié)點(diǎn)闸婴,而在 Firefox 3 之前的版本中坏挠,引用的則是 document。IE8 及更早版本不支持<script>元素上的 load 事件邪乍。
??IE 和 Opera 還支持<link>元素上的 load 事件降狠,以便開發(fā)人員確定樣式表是否加載完畢。例如:
EventUtil.addHandler(window, "load", function(){
var link = document.createElement("link");
link.type = "text/css";
link.rel= "stylesheet";
EventUtil.addHandler(link, "load", function(event){
alert("css loaded");
});
link.href = "example.css";
document.getElementsByTagName("head")[0].appendChild(link);
});
??與<script>節(jié)點(diǎn)類似庇楞,在未指定 href 屬性并將<link>元素添加到文檔之前也不會(huì)開始下載樣式表榜配。
2. unload 事件
??與 load 事件對(duì)應(yīng)的是 unload 事件,這個(gè)事件在文檔被完全卸載后觸發(fā)吕晌。只要用戶從一個(gè)頁(yè)面切換到另一個(gè)頁(yè)面蛋褥,就會(huì)發(fā)生 unload 事件。而利用這個(gè)事件最多的情況是清除引用聂使,以避免內(nèi)存泄漏壁拉。
??與 load 事件類似,也有兩種指定 onunload 事件處理程序的方式弃理。第一種方式是使用 JavaScript,如下所示:
EventUtil.addHandler(window, "unload", function(event){
alert("Unloaded");
});
??此時(shí)生成的 event 對(duì)象在兼容 DOM 的瀏覽器中只包含 target 屬性(值為 document)辆苔。IE8 及之前版本則為這個(gè)事件對(duì)象提供了 srcElement 屬性扼劈。
??指定事件處理程序的第二種方式驻啤,也是為<body>元素添加一個(gè)特性(與 load 事件相似),如下面的例子所示:
<!DOCTYPE html>
<html>
<head>
<title>Unload Event Example</title>
</head>
<body onunload="alert('Unloaded!')">
</body>
</html>
??無(wú)論使用哪種方式荐吵,都要小心編寫 onunload 事件處理程序中的代碼骑冗。既然 unload 事件是在一切都被卸載之后才觸發(fā),那么在頁(yè)面加載后存在的那些對(duì)象先煎,此時(shí)就不一定存在了贼涩。此時(shí)占锯,操作 DOM 節(jié)點(diǎn)或者元素的樣式就會(huì)導(dǎo)致錯(cuò)誤歪脏。
??根據(jù)“DOM2 級(jí)事件”,應(yīng)該在<body>元素而非 window 對(duì)象上面觸發(fā) unload事件。不過,所有瀏覽器都在 window 上實(shí)現(xiàn)了 unload 事件,以確保向后兼容。
3. resize 事件
??當(dāng)瀏覽器窗口被調(diào)整到一個(gè)新的高度或?qū)挾葧r(shí),就會(huì)觸發(fā) resize 事件。這個(gè)事件在 window(窗口)上面觸發(fā)磨镶,因此可以通過 JavaScript 或者<body>元素中的 onresize 特性來(lái)指定事件處理程序脐嫂。如前所述,我們還是推薦使用如下所示的 JavaScript 方式:
EventUtil.addHandler(window, "resize", function(event){
alert("Resized");
});
??與其他發(fā)生在 window 上的事件類似,在兼容 DOM 的瀏覽器中,傳入事件處理程序中的 event 對(duì)象有一個(gè) target 屬性,值為 document咪啡;而 IE8 及之前版本則未提供任何屬性愁溜。
??關(guān)于何時(shí)會(huì)觸發(fā) resize 事件渐扮,不同瀏覽器有不同的機(jī)制耻讽。IE、Safari、Chrome 和 Opera 會(huì)在瀏覽器窗口變化了 1 像素時(shí)就觸發(fā) resize 事件,然后隨著變化不斷重復(fù)觸發(fā)锹雏。Firefox 則只會(huì)在用戶停止調(diào)整窗口大小時(shí)才會(huì)觸發(fā) resize 事件佣耐。由于存在這個(gè)差別,應(yīng)該注意不要在這個(gè)事件的處理程序中加入大計(jì)算量的代碼,因?yàn)檫@些代碼有可能被頻繁執(zhí)行,從而導(dǎo)致瀏覽器反應(yīng)明顯變慢。
??瀏覽器窗口最小化或最大化時(shí)也會(huì)觸發(fā) resize 事件。
4. scroll 事件
??雖然 scroll 事件是在 window 對(duì)象上發(fā)生的,但它實(shí)際表示的則是頁(yè)面中相應(yīng)元素的變化。
??在混雜模式下年碘,可以通過<body>元素的 scrollLeft 和 scrollTop 來(lái)監(jiān)控到這一變化涡尘;
??而在標(biāo)準(zhǔn)模式下川梅,除 Safari 之外的所有瀏覽器都會(huì)通過<html>元素來(lái)反映這一變化(Safari 仍然基于<body>跟蹤滾動(dòng)位置)潮饱,如下面的例子所示:
EventUtil.addHandler(window, "scroll", function(event){
if (document.compatMode == "CSS1Compat"){
alert(document.documentElement.scrollTop);
} else {
alert(document.body.scrollTop);
}
});
??以上代碼指定的事件處理程序會(huì)輸出頁(yè)面的垂直滾動(dòng)位置——根據(jù)呈現(xiàn)模式不同使用了不同的元素凫碌。由于 Safari 3.1 之前的版本不支持 document.compatMode苦掘,因此舊版本的瀏覽器就會(huì)滿足第二個(gè)條件递瑰。
??與 resize 事件類似乡恕,scroll 事件也會(huì)在文檔被滾動(dòng)期間重復(fù)被觸發(fā)粱胜,所以有必要盡量保持事件處理程序的代碼簡(jiǎn)單叛本。
4.2营搅、 焦點(diǎn)事件
??焦點(diǎn)事件會(huì)在頁(yè)面元素獲得或失去焦點(diǎn)時(shí)觸發(fā)休蟹。利用這些事件并與 document.hasFocus() 方法及 document.activeElement 屬性配合翔怎,可以知曉用戶在頁(yè)面上的行蹤于毙。有以下 6 個(gè)焦點(diǎn)事件介蛉。
- focus:在元素獲得焦點(diǎn)時(shí)觸發(fā)吹菱。這個(gè)事件不會(huì)冒泡输瓜;所有瀏覽器都支持它芹缔。
- blur:在元素失去焦點(diǎn)時(shí)觸發(fā)蚜点。這個(gè)事件不會(huì)冒泡陪拘;所有瀏覽器都支持它欠痴。
- focusin:在元素獲得焦點(diǎn)時(shí)觸發(fā)菩咨。這個(gè)事件與 HTML 事件 focus 等價(jià)摩钙,但它冒泡长踊。支持這個(gè)事件的瀏覽器有 IE5.5+、Safari 5.1+、Opera 11.5+和 Chrome。
- focusout:在元素失去焦點(diǎn)時(shí)觸發(fā)郎任。這個(gè)事件是 HTML 事件 blur 的通用版本杂抽。支持這個(gè)事件的瀏覽器有 IE5.5+杭朱、Safari 5.1+刃唐、Opera 11.5+和 Chrome抖甘。
- DOMFocusIn:在元素獲得焦點(diǎn)時(shí)觸發(fā)昼接。這個(gè)事件與 HTML 事件 focus 等價(jià)漂辐,但它冒泡纬纪。只有 Opera 支持這個(gè)事件。DOM3 級(jí)事件廢棄了 DOMFocusIn,選擇了 focusin。
- DOMFocusOut:在元素失去焦點(diǎn)時(shí)觸發(fā)钠惩。這個(gè)事件是 HTML 事件 blur 的通用版本蔬咬。只有 Opera 支持這個(gè)事件狐援。DOM3 級(jí)事件廢棄了 DOMFocusOut镶殷,選擇了 focusout。
??這一類事件中最主要的兩個(gè)是 focus 和 blur,它們都是 JavaScript 早期就得到所有瀏覽器支持的事件梗逮。這些事件的最大問題是它們不冒泡岁诉。因此坠韩,IE 的 focusin 和 focusout 與 Opera 的 DOMFocusIn 和 DOMFocusOut 才會(huì)發(fā)生重疊氢惋。IE 的方式最后被 DOM3 級(jí)事件采納為標(biāo)準(zhǔn)方式循未。
??當(dāng)焦點(diǎn)從頁(yè)面中的一個(gè)元素移動(dòng)到另一個(gè)元素嫂粟,會(huì)依次觸發(fā)下列事件:
????(1) focusout 在失去焦點(diǎn)的元素上觸發(fā);
????(2) focusin 在獲得焦點(diǎn)的元素上觸發(fā);
????(3) blur 在失去焦點(diǎn)的元素上觸發(fā)吃溅;
????(4) DOMFocusOut 在失去焦點(diǎn)的元素上觸發(fā)甩苛;
????(5) focus 在獲得焦點(diǎn)的元素上觸發(fā);
????(6) DOMFocusIn 在獲得焦點(diǎn)的元素上觸發(fā)。
??其中,blur梨睁、DOMFocusOut 和 focusout 的事件目標(biāo)是失去焦點(diǎn)的元素拳亿;而 focus、DOMFocusIn 和 focusin 的事件目標(biāo)是獲得焦點(diǎn)的元素。
??要確定瀏覽器是否支持這些事件,可以使用如下代碼:
var isSupported = document.implementation.hasFeature("FocusEvent", "3.0");
??即使 focus 和 blur 不冒泡,也可以在捕獲階段偵聽到它們涡真。
4.3杏节、 鼠標(biāo)與滾輪事件
??鼠標(biāo)事件是 Web 開發(fā)中最常用的一類事件,畢竟鼠標(biāo)還是最主要的定位設(shè)備。DOM3 級(jí)事件中定義了 9 個(gè)鼠標(biāo)事件,簡(jiǎn)介如下:
- click:在用戶單擊主鼠標(biāo)按鈕(一般是左邊的按鈕)或者按下回車鍵時(shí)觸發(fā)司志。這一點(diǎn)對(duì)確保易訪問性很重要拓型,意味著 onclick 事件處理程序既可以通過鍵盤也可以通過鼠標(biāo)執(zhí)行捕儒。
- dblclick:在用戶雙擊主鼠標(biāo)按鈕(一般是左邊的按鈕)時(shí)觸發(fā)扇调。從技術(shù)上說莲镣,這個(gè)事件并不是 DOM2 級(jí)事件規(guī)范中規(guī)定的半火,但鑒于它得到了廣泛支持,左移 DOM3 級(jí)事件將其納入了標(biāo)準(zhǔn)。
- mousedown:在用戶按下了任意鼠標(biāo)按鈕時(shí)觸發(fā)。不能通過鍵盤觸發(fā)這個(gè)事件。
- mouseup:在用戶設(shè)防鼠標(biāo)按鈕時(shí)觸發(fā)阅羹。不能通過鍵盤觸發(fā)這個(gè)事件轨淌。
- mouseenter:在鼠標(biāo)光標(biāo)從元素外部首次移動(dòng)到元素范圍之內(nèi)時(shí)觸發(fā)。這個(gè)事件不冒泡,而且在光標(biāo)移動(dòng)到后代元素上不會(huì)觸發(fā)语婴。DOM2 級(jí)事件并沒有定義這個(gè)事件缠导,但 DOM3 級(jí)事件將它納入了規(guī)范竹挡。IE、Firefox 9+ 和 Opera 支持這個(gè)事件。
- mouseleave:在位于元素上方的鼠標(biāo)光標(biāo)移動(dòng)到元素范圍之外時(shí)觸發(fā)隙券。這個(gè)事件不冒泡,而且在光標(biāo)移動(dòng)到后代元素上不會(huì)觸發(fā)铐刘。DOM2 級(jí)事件并沒有定義這個(gè)事件盼产,但 DOM3 級(jí)事件將它納入了規(guī)范芹关。IE、Firefox 9+ 和 Opera 支持這個(gè)事件。
- mousemove:當(dāng)鼠標(biāo)指針在元素內(nèi)部移動(dòng)時(shí)觸發(fā)虑灰。不能通過鍵盤觸發(fā)這個(gè)事件对湃。
- mouseout:在鼠標(biāo)指針位于一個(gè)元素上方,然后用戶將其移入另一個(gè)元素時(shí)觸發(fā)。又移入的另一個(gè)元素可能位于前一個(gè)元素的外部潘飘,也可能是這個(gè)元素的子元素筐高。不能通過鍵盤觸發(fā)這個(gè)事件扮宠。
- mouseover轿偎;在鼠標(biāo)指針位于一個(gè)元素外部蜓斧,然后用戶將其首次移入另一個(gè)元素邊界之內(nèi)時(shí)觸發(fā)直奋。不能通過鍵盤觸發(fā)這個(gè)事件渠旁。
??頁(yè)面上的所有元素都支持鼠標(biāo)事件。除了 mouseenter 和 mouseleave,所有鼠標(biāo)事件都會(huì)冒泡防泵,也可以被取消,而取消鼠標(biāo)事件將會(huì)影響瀏覽器的默認(rèn)行為码泞。取消鼠標(biāo)事件的默認(rèn)行為還會(huì)影響其它事件,因?yàn)槭髽?biāo)事件與其它事件是密不可分的關(guān)系。
??只有在同一個(gè)元素上相繼觸發(fā) mousedown 和 mouseup 事件,才會(huì)觸發(fā) click 事件续膳;如果 mousedown 或 mouseup 中的一個(gè)被取消改艇,就不會(huì)觸發(fā) click 事件。
??類似地,只有觸發(fā)兩次 click 事件盏缤,才會(huì)觸發(fā)一次 dblclick 事件熬甫。如果有代碼阻止了連續(xù)兩次觸發(fā) click 事件(可能是直接取消 click 事件辈双,也可能通過取消 mousedown 或 mouseup 間接實(shí)現(xiàn))湃密,那么就不會(huì)觸發(fā) dblclick 事件了赃磨。這 4 個(gè)事件觸發(fā)的順序始終如下:
????(1) mousedown
????(2) mouseup
????(3) click
????(4) mousedown
????(5) mouseup
????(6) click
????(7) dblclick
??顯然,click 和 dblclick 事件都會(huì)依賴于其他先行事件的觸發(fā);而 mousedown 和 mouseup 則不受其他事件的影響。
??IE8 及之前版本中的實(shí)現(xiàn)有一個(gè)小 bug城舞,因此在雙擊事件中绰精,會(huì)跳過第二個(gè) mousedown 和 click 事件妻往,其順序如下:
????(1) mousedown
????(2) mouseup
????(3) click
????(4) mouseup
????(5) dblclick
??IE9 修復(fù)了這個(gè) bug,之后順序就正確了。
??使用以下代碼可以檢測(cè)瀏覽器是否支持以上 DOM2 級(jí)事件(除 dbclick掌实、mouseenter 和 mouseleave 之外):
var isSupported = document.implementation.hasFeature("MouseEvents", "2.0");
??要檢測(cè)瀏覽器是否支持上面的所有事件芋齿,可以使用以下代碼:
var isSupported = document.implementation.hasFeature("MouseEvent", "3.0")
??注意扛禽,DOM3 級(jí)事件的 feature 名是"MouseEvent"拟赊,而非"MouseEvents"。
??鼠標(biāo)事件中還有一類滾輪事件墩弯。而說是一類事件羊娃,其實(shí)就是一個(gè) mousewheel 事件。這個(gè)事件跟蹤鼠標(biāo)滾輪亦歉,類似于 Mac 的觸控板。
1. 客戶區(qū)坐標(biāo)位置
??鼠標(biāo)事件都是在瀏覽器視口中的特定位置上發(fā)生的籽慢。這個(gè)位置信息保存在事件對(duì)象的 clientX 和 clientY 屬性中。所有瀏覽器都支持這兩個(gè)屬性痊远,它們的值表示事件發(fā)生時(shí)鼠標(biāo)指針在視口中的水平和垂直坐標(biāo)年堆。下圖展示了視口中客戶區(qū)坐標(biāo)位置的含義。
??可以使用類似下列代碼取得鼠標(biāo)事件的客戶端坐標(biāo)信息:
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert("Client coordinates: " + event.clientX + "," + event.clientY);
});
??這里為一個(gè)<div>元素指定了 onclick 事件處理程序熙侍。當(dāng)用戶單擊這個(gè)元素時(shí)售担,就會(huì)看到事件的客戶端坐標(biāo)信息衫仑。
??注意切黔,這些值中不包括頁(yè)面滾動(dòng)的距離荧恍,因此這個(gè)位置并不表示鼠標(biāo)在頁(yè)面上的位置投储。
2. 頁(yè)面坐標(biāo)位置
??通過客戶區(qū)坐標(biāo)能夠知道鼠標(biāo)是在視口中什么位置發(fā)生的躯泰,而頁(yè)面坐標(biāo)通過事件對(duì)象的 pageX 和 pageY 屬性,能告訴你事件是在頁(yè)面中的什么位置發(fā)生的貌笨。換句話說,這兩個(gè)屬性表示鼠標(biāo)光標(biāo)在頁(yè)面中的位置轿曙,因此坐標(biāo)是從頁(yè)面本身而非視口的左邊和頂邊計(jì)算的哮独。
??以下代碼可以取得鼠標(biāo)事件在頁(yè)面中的坐標(biāo):
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert("Page coordinates: " + event.pageX + "," + event.pageY);
});
??在頁(yè)面沒有滾動(dòng)的情況下拳芙,pageX 和 pageY 的值與 clientX 和 clientY 的值相等。
??IE8 及更早版本不支持事件對(duì)象上的頁(yè)面坐標(biāo)皮璧,不過使用客戶區(qū)坐標(biāo)和滾動(dòng)信息可以計(jì)算出來(lái)舟扎。這時(shí)候需要用到 document.body(混雜模式)或 document.documentElement(標(biāo)準(zhǔn)模式)中的 scrollLeft 和 scrollTop 屬性。計(jì)算過程如下所示:
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
var pageX = event.pageX,
pageY = event.pageY;
if (pageX === undefined){
pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
}
if (pageY === undefined){
pageY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop);
}
alert("Page coordinates: " + pageX + "," + pageY);
});
3. 屏幕坐標(biāo)位置
??鼠標(biāo)事件發(fā)生時(shí)悴务,不僅會(huì)有相對(duì)于瀏覽器窗口的位置睹限,還有一個(gè)相對(duì)于整個(gè)電腦屏幕的位置。而通過 screenX 和 screenY 屬性就可以確定鼠標(biāo)事件發(fā)生時(shí)鼠標(biāo)指針相對(duì)于整個(gè)屏幕的坐標(biāo)信息讯檐。
??可以使用類似下面的代碼取得鼠標(biāo)事件的屏幕坐標(biāo):
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
alert("Screen coordinates: " + event.screenX + "," + event.screenY);
});
??與前一個(gè)例子類似羡疗,這里也是為<div>元素指定了一個(gè) onclick 事件處理程序。當(dāng)這個(gè)元素被單擊時(shí)别洪,就會(huì)顯示出事件的屏幕坐標(biāo)信息了叨恨。
4. 修改鍵
??雖然鼠標(biāo)事件主要是使用鼠標(biāo)來(lái)觸發(fā)的,但在按下鼠標(biāo)時(shí)鍵盤上的某些鍵的狀態(tài)也可以影響到所要采取的操作挖垛。這些修改鍵就是 Shift痒钝、Ctrl、Alt 和 Meta(在 Windows 鍵盤中是 Windows 鍵痢毒,在蘋果機(jī)中是 Cmd 鍵)送矩,它們經(jīng)常被用來(lái)修改鼠標(biāo)事件的行為。
??DOM 為此規(guī)定了 4 個(gè)屬性哪替,表示這些修改鍵的狀態(tài):shiftKey栋荸、ctrlKey、altKey 和 metaKey。這些屬性中包含的都是布爾值晌块,如果相應(yīng)的鍵被按下了爱沟,則值為 true,否則值為 false摸袁。
??當(dāng)某個(gè)鼠標(biāo)事件發(fā)生時(shí)荣月,通過檢測(cè)這幾個(gè)屬性就可以確定用戶是否同時(shí)按下了其中的鍵初坠。來(lái)看下面的例子樟蠕。
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
event = EventUtil.getEvent(event);
var keys = new Array();
if (event.shiftKey){
keys.push("shift");
}
if (event.ctrlKey){
keys.push("ctrl");
}
if (event.altKey){
keys.push("alt");
}
if (event.metaKey){
keys.push("meta");
}
alert("Keys: " + keys.join(","));
});
??在這個(gè)例子中颓鲜,我們通過一個(gè) onclick 事件處理程序檢測(cè)了不同修改鍵的狀態(tài)苍息。數(shù)組 keys 中包含著被按下的修改鍵的名稱庄敛。換句話說伟叛,如果有屬性值為 true略就,就會(huì)將對(duì)應(yīng)修改鍵的名稱添加到 keys 數(shù)組中兄墅。在事件處理程序的最后踢星,有一個(gè)警告框?qū)z測(cè)到的鍵的信息顯示給用戶。
??IE9隙咸、Firefox沐悦、Safari、Chrome 和 Opera 都支持這 4 個(gè)鍵五督。IE8 及之前版本不支持 metaKey 屬性藏否。
5. 相關(guān)元素
??在發(fā)生 mouseover 和 mouseout 事件時(shí),還會(huì)涉及更多的元素充包。
??這兩個(gè)事件都會(huì)涉及把鼠標(biāo)指針從一個(gè)元素的邊界之內(nèi)移動(dòng)到另一個(gè)元素的邊界之內(nèi)副签。
??對(duì) mouseover 事件而言,事件的主目標(biāo)是獲得光標(biāo)的元素基矮,而相關(guān)元素就是那個(gè)失去光標(biāo)的元素淆储。
??對(duì) mouseout 事件而言,事件的主目標(biāo)是失去光標(biāo)的元素家浇,而相關(guān)元素則是獲得光標(biāo)的元素本砰。來(lái)看下面的例子。
<!DOCTYPE html>
<html>
<head>
<title>Related Elements Example</title>
</head>
<body>
<div id="myDiv" style="background-color:red;height:100px;width:100px;"></div>
</body>
</html>
??上述例子會(huì)在頁(yè)面上顯示一個(gè)<div>元素钢悲。如果鼠標(biāo)指針一開始位于這個(gè)<div>元素上点额,然后移出了這個(gè)元素,那么就會(huì)在<div>元素上觸發(fā) mouseout 事件譬巫,相關(guān)元素就是<body>元素咖楣。與此同時(shí),<body>元素上面會(huì)觸發(fā) mouseover 事件芦昔,而相關(guān)元素變成了<div>诱贿。
??DOM 通過 event 對(duì)象的 relatedTarget 屬性提供了相關(guān)元素的信息。這個(gè)屬性只對(duì)于 mouseover 和mouseout 事件才包含值;對(duì)于其他事件珠十,這個(gè)屬性的值是null料扰。
??IE8及之前版本不支持 relatedTarget 屬性,但提供了保存著同樣信息的不同屬性焙蹭。在 mouseover 事件觸發(fā)時(shí)晒杈,IE 的 fromElement 屬性中保存了相關(guān)元素;在 mouseout 事件觸發(fā)時(shí)孔厉,IE 的 toElement 屬性中保存著相關(guān)元素拯钻。(IE9 支持所有這些屬性。)
??可以把下面這個(gè)跨瀏覽器取得相關(guān)元素的方法添加到 EventUtil 對(duì)象中撰豺。
var EventUtil = {
// 省略了其他代碼
getRelatedTarget: function(event){
if (event.relatedTarget){
return event.relatedTarget;
} else if (event.toElement){
return event.toElement;
} else if (event.fromElement){
return event.fromElement;
} else {
return null;
}
},
// 省略了其他代碼
};
??與以前添加的跨瀏覽器方法一樣粪般,這個(gè)方法也使用了特性檢測(cè)來(lái)確定返回哪個(gè)值∥坭耄可以像下面這樣使用 EventUtil.getRelatedTarget() 方法:
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mouseout", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
alert("Moused out of " + target.tagName + " to " + relatedTarget.tagName);
});
??這個(gè)例子為<div>元素的 mouseout 事件注冊(cè)了一個(gè)事件處理程序亩歹。當(dāng)事件觸發(fā)時(shí),會(huì)有一個(gè)警告框顯示鼠標(biāo)移出和移入的元素信息凡橱。
6. 鼠標(biāo)按鈕
??只有在主鼠標(biāo)按鈕被單擊(或鍵盤回車鍵被按下)時(shí)才會(huì)觸發(fā) click 事件小作,因此檢測(cè)按鈕的信息并不是必要的。但對(duì)于 mousedown 和 mouseup 事件來(lái)說稼钩,則在其 event 對(duì)象存在一個(gè) button 屬性顾稀,表示按下或釋放的按鈕。
??DOM 的 button 屬性可能有如下 3 個(gè)值:0 表示主鼠標(biāo)按鈕变抽,1 表示中間的鼠標(biāo)按鈕(鼠標(biāo)滾輪按鈕)础拨,2 表示次鼠標(biāo)按鈕。在常規(guī)的設(shè)置中绍载,主鼠標(biāo)按鈕就是鼠標(biāo)左鍵诡宗,而次鼠標(biāo)按鈕就是鼠標(biāo)右鍵。
??IE8 及之前版本也提供了 button 屬性击儡,但這個(gè)屬性的值與 DOM 的 button 屬性有很大差異塔沃。
- 0:表示沒有按下按鈕。
- 1:表示按下了主鼠標(biāo)按鈕阳谍。
- 2:表示按下了次鼠標(biāo)按鈕蛀柴。
- 3:表示同時(shí)按下了主、次鼠標(biāo)按鈕矫夯。
- 4:表示按下了中間的鼠標(biāo)按鈕鸽疾。
- 5:表示同時(shí)按下了主鼠標(biāo)按鈕和中間的鼠標(biāo)按鈕。
- 6:表示同時(shí)按下了次鼠標(biāo)按鈕和中間的鼠標(biāo)按鈕训貌。
- 7:表示同時(shí)按下了三個(gè)鼠標(biāo)按鈕制肮。
??不難想見冒窍,DOM 模型下的 button 屬性比 IE 模型下的 button 屬性更簡(jiǎn)單也更為實(shí)用,因?yàn)橥瑫r(shí)按下多個(gè)鼠標(biāo)按鈕的情形十分罕見豺鼻。
??最常見的做法就是將 IE 模型規(guī)范化為 DOM 方式综液,畢竟除 IE8 及更早版本之外的其他瀏覽器都原生支持 DOM 模型。而對(duì)主儒飒、中谬莹、次按鈕的映射并不困難,只要將 IE 的其他選項(xiàng)分別轉(zhuǎn)換成如同按下這三個(gè)按鍵中的一個(gè)即可(同時(shí)將主按鈕作為優(yōu)先選取的對(duì)象)桩了。換句話說附帽,IE 中返回的 5 和 7 會(huì)被轉(zhuǎn)換成 DOM 模型中的 0。
??由于單獨(dú)使用能力檢測(cè)無(wú)法確定差異(兩種模型有同名的 button 屬性)圣猎,因此必須另辟蹊徑士葫。我們知道乞而,支持 DOM 版鼠標(biāo)事件的瀏覽器可以通過 hasFearture() 方法來(lái)檢測(cè)送悔,所以可以再為 EventUtil 對(duì)象添加如下 getButton() 方法。
var EventUtil = {
// 省略了其他代碼
getButton: function(event){
if (document.implementation.hasFeature("MouseEvents", "2.0")){
return event.button;
} else {
switch(event.button){
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4:
return 1;
}
}
},
// 省略了其他代碼
};
??通過檢測(cè)"MouseEvents"這個(gè)特性爪模,就可以確定 event 對(duì)象中存在的 button 屬性中是否包含正確的值欠啤。如果測(cè)試失敗,說明是 IE屋灌,就必須對(duì)相應(yīng)的值進(jìn)行規(guī)范化洁段。以下是使用該方法的示例。
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "mousedown", function(event){
event = EventUtil.getEvent(event);
alert(EventUtil.getButton(event));
});
??在上述例子中共郭,我們?yōu)橐粋€(gè)<div>元素添加了一個(gè) onmousedown 事件處理程序祠丝。當(dāng)在這個(gè)元素上按下鼠標(biāo)按鈕時(shí),會(huì)有警告框顯示按鈕的代碼除嘹。
??在使用 onmouseup 事件處理程序時(shí)写半,button 的值表示釋放的是哪個(gè)按鈕。此外尉咕,如果不是按下或釋放了主鼠標(biāo)按鈕叠蝇,Opera 不會(huì)觸發(fā) mouseup 或 mousedown 事件。
7. 更多的事件信息
??“DOM2 級(jí)事件”規(guī)范在 event 對(duì)象中還提供了 detail 屬性年缎,用于給出有關(guān)事件的更多信息悔捶。
??對(duì)于鼠標(biāo)事件來(lái)說,detail 中包含了一個(gè)數(shù)值单芜,表示在給定位置上發(fā)生了多少次單擊蜕该。在同一個(gè)元素上相繼地發(fā)生一次 mousedown 和一次 mouseup 事件算作一次單擊。
??detail 屬性從 1 開始計(jì)數(shù)洲鸠,每次單擊發(fā)生后都會(huì)遞增堂淡。如果鼠標(biāo)在 mousedown 和 mouseup 之間移動(dòng)了位置,則 detail 會(huì)被重置為 0。
??IE 也通過下列屬性為鼠標(biāo)事件提供了更多信息淤齐。
- altLeft:布爾值股囊,表示是否按下了 Alt鍵。如果 altLeft 的值為 true更啄,則 altKey 的值也為 true稚疹。
- ctrlLeft:布爾值,表示是否按下了 Ctrl 鍵祭务。如果 ctrlLeft 的值為 true内狗,則 ctrlKey 的值也為 true。
- offsetX:光標(biāo)相對(duì)于目標(biāo)元素邊界的 x 坐標(biāo)义锥。
- offsetY:光標(biāo)相對(duì)于目標(biāo)元素邊界的 y 坐標(biāo)柳沙。
- shiftLeft:布爾值,表示是否按下了 Shift 鍵拌倍。如果 shiftLeft 的值為 true赂鲤,則 shiftKey 的值也為 true。
??這些屬性的用處并不大柱恤,原因一方面是只有 IE 支持它們数初,另一方是它們提供的信息要么沒有什么價(jià)值,要么可以通過其他方式計(jì)算得來(lái)梗顺。
8. 鼠標(biāo)滾輪事件
??IE 6.0 首先實(shí)現(xiàn)了 mousewheel 事件泡孩。此后,Opera寺谤、Chrome 和 Safari 也都實(shí)現(xiàn)了這個(gè)事件仑鸥。
??當(dāng)用戶通過鼠標(biāo)滾輪與頁(yè)面交互、在垂直方向上滾動(dòng)頁(yè)面時(shí)(無(wú)論向上還是向下)变屁,就會(huì)觸發(fā) mousewheel 事件眼俊。這個(gè)事件可以在任何元素上面觸發(fā),最終會(huì)冒泡到 document(IE8)或 window(IE9敞贡、Opera泵琳、Chrome 及 Safari)對(duì)象。
??與 mousewheel 事件對(duì)應(yīng)的 event 對(duì)象除包含鼠標(biāo)事件的所有標(biāo)準(zhǔn)信息外誊役,還包含一個(gè)特殊的 wheelDelta 屬性获列。當(dāng)用戶向前滾動(dòng)鼠標(biāo)滾輪時(shí),wheelDelta 是 120 的倍數(shù)蛔垢;當(dāng)用戶向后滾動(dòng)鼠標(biāo)滾輪時(shí)击孩,wheelDelta 是-120 的倍數(shù)。下圖展示了這個(gè)屬性鹏漆。
??將 mousewheel 事件處理程序指定給頁(yè)面中的任何元素或 document 對(duì)象巩梢,即可處理鼠標(biāo)滾輪的交互操作创泄。來(lái)看下面的例子。
EventUtil.addHandler(document, "mousewheel", function(event){
event = EventUtil.getEvent(event);
alert(event.wheelDelta);
});
??上述例子會(huì)在發(fā)生 mousewheel 事件時(shí)顯示 wheelDelta 的值括蝠。多數(shù)情況下鞠抑,只要知道鼠標(biāo)滾輪滾動(dòng)的方向就夠了,而這通過檢測(cè) wheelDelta 的正負(fù)號(hào)就可以確定忌警。
??有一點(diǎn)要注意:在 Opera 9.5 之前的版本中搁拙,wheelDelta 值的正負(fù)號(hào)是顛倒的。如果你打算支持早期的 Opera 版本法绵,就需要使用瀏覽器檢測(cè)技術(shù)來(lái)確定實(shí)際的值箕速,如下面的例子所示。
EventUtil.addHandler(document, "mousewheel", function(event){
event = EventUtil.getEvent(event);
var delta = (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta);
alert(delta);
});
??上述代碼使用 client 對(duì)象檢測(cè)了瀏覽器是不是早期版本的 Opera朋譬。
??由于 mousewheel 事件非常流行盐茎,而且所有瀏覽器都支持它,所以 HTML 5 也加入了該事件徙赢。
??Firefox 支持一個(gè)名為 DOMMouseScroll 的類似事件字柠,也是在鼠標(biāo)滾輪滾動(dòng)時(shí)觸發(fā)。與 mousewheel 事件一樣犀忱,DOMMouseScroll 也被視為鼠標(biāo)事件募谎,因而包含與鼠標(biāo)事件有關(guān)的所有屬性。
??而有關(guān)鼠標(biāo)滾輪的信息則保存在 detail 屬性中阴汇,當(dāng)向前滾動(dòng)鼠標(biāo)滾輪時(shí),這個(gè)屬性的值是-3 的倍數(shù)节槐,當(dāng)向后滾動(dòng)鼠標(biāo)滾輪時(shí)搀庶,這個(gè)屬性的值是 3 的倍數(shù)。下圖展示了這個(gè)屬性铜异。
??可以將 DOMMouseScroll 事件添加到頁(yè)面中的任何元素哥倔,而且該事件會(huì)冒泡到 window 對(duì)象肛捍。因此番甩,可以像下面這樣針對(duì)這個(gè)事件來(lái)添加事件處理程序。
EventUtil.addHandler(window, "DOMMouseScroll", function(event){
event = EventUtil.getEvent(event);
alert(event.detail);
});
??這個(gè)簡(jiǎn)單的事件處理程序會(huì)在鼠標(biāo)滾輪滾動(dòng)時(shí)顯示 detail 屬性的值秩冈。
??若要給出跨瀏覽器環(huán)境下的解決方案蚂子,第一步就是創(chuàng)建一個(gè)能夠取得鼠標(biāo)滾輪增量值(delta)的方法沃测。下面是我們添加到 EventUtil 對(duì)象中的這個(gè)方法。
var EventUtil = {
// 省略了其他代碼
getWheelDelta: function(event){
if (event.wheelDelta){
return (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta);
} else {
return -event.detail * 40;
}
},
// 省略了其他代碼
};
??這里食茎,getWheelDelta() 方法首先檢測(cè)了事件對(duì)象是否包含 wheelDelta 屬性蒂破,如果是則通過瀏覽器檢測(cè)代碼確定正確的值。如果 wheelDelta 不存在别渔,則假設(shè)相應(yīng)的值保存在 detail 屬性中附迷。由于 Firefox 的值有所不同惧互,因此首先要將這個(gè)值的符號(hào)反向,然后再乘以 40喇伯,就可以保證與其他瀏覽器的值相同了喊儡。
??有了這個(gè)方法之后,就可以將相同的事件處理程序指定給 mousewheel 和 DOMMouseScroll 事件了稻据,例如:
(function(){
function handleMouseWheel(event){
event = EventUtil.getEvent(event);
var delta = EventUtil.getWheelDelta(event);
alert(delta);
}
EventUtil.addHandler(document, "mousewheel", handleMouseWheel);
EventUtil.addHandler(document, "DOMMouseScroll", handleMouseWheel);
})();
??我們將相關(guān)代碼放在了一個(gè)私有作用域中管宵,從而不會(huì)讓新定義的函數(shù)干擾全局作用域。這里定義的 handleMouseWheel() 函數(shù)可以用作兩個(gè)事件的處理程序(如果指定的事件不存在攀甚,則為該事件指定處理程序的代碼就會(huì)靜默地失斅崞印)。由于使用了 EventUtil.getWheelDelta() 方法秋度,我們定義的這個(gè)事件處理程序函數(shù)可以適用于任何一種情況炸庞。
9. 觸摸設(shè)備
??iOS 和 Android 設(shè)備的實(shí)現(xiàn)非常特別,因?yàn)檫@些設(shè)備沒有鼠標(biāo)荚斯。在面向 iPhone 和 iPod 中的 Safari 開發(fā)時(shí)埠居,要記住以下幾點(diǎn)。
- 不支持 dblclick 事件事期。雙擊瀏覽器窗口會(huì)放大畫面滥壕,而且沒有辦法改變?cè)撔袨椤?/li>
- 輕擊可單擊元素會(huì)觸發(fā) mousemove 事件。如果此操作會(huì)導(dǎo)致內(nèi)容變化兽泣,將不再有其他事件發(fā)生绎橘;如果屏幕沒有因此變化,那么會(huì)依次發(fā)生 mousedown唠倦、mouseup 和 click 事件称鳞。輕擊不可單擊的元素不會(huì)觸發(fā)任何事件〕肀牵可單擊的元素是指那些單擊可產(chǎn)生默認(rèn)操作的元素(如鏈接)冈止,或者那些已經(jīng)被指定了onclick 事件處理程序的元素。
- mousemove 事件也會(huì)觸發(fā) mouseover 和 mouseout 事件候齿。
- 兩個(gè)手指放在屏幕上且頁(yè)面隨手指移動(dòng)而滾動(dòng)時(shí)會(huì)觸發(fā) mousewheel 和 scroll 事件熙暴。
10. 無(wú)障礙性問題
??如果你的 Web 應(yīng)用程序或網(wǎng)站要確保殘疾人特別是那些使用屏幕閱讀器的人都能訪問,那么在使用鼠標(biāo)事件時(shí)就要格外小心慌盯。
??前面提到過周霉,可以通過鍵盤上的回車鍵來(lái)觸發(fā) click 事件,但其他鼠標(biāo)事件卻無(wú)法通過鍵盤來(lái)觸發(fā)润匙。
??為此诗眨,我們不建議使用 click 之外的其他鼠標(biāo)事件來(lái)展示功能或引發(fā)代碼執(zhí)行。因?yàn)檫@樣會(huì)給盲人或視障用戶造成極大不便孕讳。以下是在使用鼠標(biāo)事件時(shí)應(yīng)當(dāng)注意的幾個(gè)易訪問性問題匠楚。
- 使用 click 事件執(zhí)行代碼巍膘。有人指出通過 onmousedown 執(zhí)行代碼會(huì)讓人覺得速度更快,對(duì)視力正常的人來(lái)說這是沒錯(cuò)的芋簿。但是峡懈,在屏幕閱讀器中,由于無(wú)法觸發(fā) mousedown 事件与斤,結(jié)果就會(huì)造成代碼無(wú)法執(zhí)行肪康。
- 不要使用 onmouseover 向用戶顯示新的選項(xiàng)。原因同上撩穿,屏幕閱讀器無(wú)法觸發(fā)這個(gè)事件磷支。如果確實(shí)非要通過這種方式來(lái)顯示新選項(xiàng),可以考慮添加顯示相同信息的鍵盤快捷方式食寡。
- 不要使用 dblclick 執(zhí)行重要的操作雾狈。鍵盤無(wú)法觸發(fā)這個(gè)事件。
??遵照以上提示可以極大地提升殘疾人在訪問你的 Web 應(yīng)用程序或網(wǎng)站時(shí)的易訪問性抵皱。
4.4 鍵盤與文本事件
??用戶在使用鍵盤時(shí)會(huì)觸發(fā)鍵盤事件善榛。“DOM2 級(jí)事件”最初規(guī)定了鍵盤事件呻畸,但在最終定稿之前又刪除了相應(yīng)的內(nèi)容移盆。結(jié)果,對(duì)鍵盤事件的支持主要遵循的是 DOM0 級(jí)伤为。
??“DOM3 級(jí)事件”為鍵盤事件制定了規(guī)范咒循,IE9 率先完全實(shí)現(xiàn)了該規(guī)范。其他瀏覽器也在著手實(shí)現(xiàn)這一標(biāo)準(zhǔn)钮呀,但仍然有很多遺留的問題剑鞍。
??有 3 個(gè)鍵盤事件,簡(jiǎn)述如下爽醋。
- keydown:當(dāng)用戶按下鍵盤上的任意鍵時(shí)觸發(fā),而且如果按住不放的話便脊,會(huì)重復(fù)觸發(fā)此事件蚂四。
- keypress:當(dāng)用戶按下鍵盤上的字符鍵時(shí)觸發(fā),而且如果按住不放的話哪痰,會(huì)重復(fù)觸發(fā)此事件遂赠。按下 Esc 鍵也會(huì)觸發(fā)這個(gè)事件。Safari 3.1 之前的版本也會(huì)在用戶按下非字符鍵時(shí)觸發(fā) keypress 事件晌杰。
- keyup:當(dāng)用戶釋放鍵盤上的鍵時(shí)觸發(fā)跷睦。
??雖然所有元素都支持以上 3 個(gè)事件,但只有在用戶通過文本框輸入文本時(shí)才最常用到肋演。
??只有一個(gè)文本事件:textInput抑诸。這個(gè)事件是對(duì) keypress 的補(bǔ)充烂琴,用意是在將文本顯示給用戶之前更容易攔截文本。在文本插入文本框之前會(huì)觸發(fā) textInput 事件蜕乡。
??在用戶按了一下鍵盤上的字符鍵時(shí)奸绷,首先會(huì)觸發(fā) keydown 事件,然后緊跟著是 keypress 事件层玲,最后會(huì)觸發(fā) keyup 事件号醉。其中,keydown 和 keypress 都是在文本框發(fā)生變化之前被觸發(fā)的辛块;而 keyup 事件則是在文本框已經(jīng)發(fā)生變化之后被觸發(fā)的畔派。
??如果用戶按下了一個(gè)字符鍵不放,就會(huì)重復(fù)觸發(fā) keydown 和 keypress 事件润绵,直到用戶松開該鍵為止线椰。
??如果用戶按下的是一個(gè)非字符鍵,那么首先會(huì)觸發(fā) keydown 事件授药,然后就是 keyup 事件士嚎。如果按住這個(gè)非字符鍵不放,那么就會(huì)一直重復(fù)觸發(fā) keydown 事件悔叽,直到用戶松開這個(gè)鍵莱衩,此時(shí)會(huì)觸發(fā) keyup 事件。
??鍵盤事件與鼠標(biāo)事件一樣娇澎,都支持相同的修改鍵笨蚁。而且,鍵盤事件的事件對(duì)象中也有 shiftKey趟庄、ctrlKey括细、altKey 和 metaKey 屬性。IE 不支持 metaKey戚啥。
1. 鍵碼
??在發(fā)生 keydown 和 keyup 事件時(shí)奋单,event 對(duì)象的 keyCode 屬性中會(huì)包含一個(gè)代碼,與鍵盤上一個(gè)特定的鍵對(duì)應(yīng)猫十。
??對(duì)數(shù)字字母字符鍵览濒,keyCode 屬性的值與 ASCII 碼中對(duì)應(yīng)小寫字母或數(shù)字的編碼相同。因此拖云,數(shù)字鍵 7 的 keyCode 值為 55贷笛,而字母 A 鍵的 keyCode 值為 65——與 Shift 鍵的狀態(tài)無(wú)關(guān)。
??DOM 和 IE 的 event 對(duì)象都支持 keyCode 屬性宙项。請(qǐng)看下面這個(gè)例子:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keyup", function(event){
event = EventUtil.getEvent(event);
alert(event.keyCode);
});
??在上述例子中乏苦,用戶每次在文本框中按鍵觸發(fā) keyup 事件時(shí),都會(huì)顯示 keyCode 的值尤筐。下表列出了所有非字符鍵的鍵碼汇荐。
鍵 | 鍵 碼 | 鍵 | 鍵 碼 |
---|---|---|---|
退格(Backspace) | 8 | 數(shù)字小鍵盤1 | 97 |
制表(Tab) | 9 | 數(shù)字小鍵盤2 | 98 |
回車(Enter) | 13 | 數(shù)字小鍵盤3 | 99 |
上檔(Shift) | 16 | 數(shù)字小鍵盤4 | 100 |
控制(Ctrl) | 17 | 數(shù)字小鍵盤5 | 101 |
Alt | 18 | 數(shù)字小鍵盤6 | 102 |
暫停/中斷(Pause/Break) | 19 | 數(shù)字小鍵盤7 | 103 |
大寫鎖定(Caps Lock) | 20 | 數(shù)字小鍵盤8 | 104 |
退出(Esc) | 27 | 數(shù)字小鍵盤9 | 105 |
上翻頁(yè)(Page Up) | 33 | 數(shù)字小鍵盤0 | 96 |
下翻頁(yè)(Page Down) | 34 | 數(shù)字小鍵盤+ | 107 |
結(jié)尾(End) | 35 | 數(shù)字小鍵盤及大鍵盤上的- | 109 |
開頭(Home) | 36 | 數(shù)字小鍵盤 . | 110 |
左箭頭(Left Arrow) | 37 | 數(shù)字小鍵盤 / | 111 |
上箭頭(Up Arrow) | 38 | F1 | 112 |
右箭頭(Right Arrow) | 39 | F2 | 113 |
下箭頭(Down Arrow) | 40 | F3 | 114 |
插入(Ins) | 45 | F4 | 115 |
刪除(Del) | 46 | F5 | 116 |
左Windows鍵 | 91 | F6 | 117 |
右Windows鍵 | 92 | F7 | 118 |
上下文菜單鍵 | 93 | F8 | 119 |
數(shù)字鎖(Num Lock) | 144 | F9 | 120 |
滾動(dòng)鎖(Scroll Lock) | 145 | F10 | 121 |
左方括號(hào) | 219 | F11 | 122 |
右方括號(hào) | 221 | F12 | 123 |
大于 | 190 | 正斜杠 | 191 |
小于 | 188 | 反斜杠(\) | 220 |
等于 | 61 | 沉音符(`) | 192 |
分號(hào)(IE/Safari/Chrome中) | 186 | 單引號(hào) | 222 |
分號(hào)(Opera/FF中) | 59 |
??無(wú)論 keydown 或 keyup 事件都會(huì)存在的一些特殊情況洞就。在 Firefox和 Opera中,按分號(hào)鍵時(shí) keyCode 值為 59拢驾,也就是 ASCII 中分號(hào)的編碼奖磁;但 IE 和 Safari 返回 186,即鍵盤中按鍵的鍵碼繁疤。
2. 字符編碼
??發(fā)生 keypress 事件意味著按下的鍵會(huì)影響到屏幕中文本的顯示咖为。在所有瀏覽器中,按下能夠插入或刪除字符的鍵都會(huì)觸發(fā) keypress 事件稠腊;按下其他鍵能否觸發(fā)此事件因?yàn)g覽器而異躁染。
??由于截止到 2008年,尚無(wú)瀏覽器實(shí)現(xiàn)“DOM3 級(jí)事件”規(guī)范架忌,所以瀏覽器之間的鍵盤事件并沒有多大的差異吞彤。
??IE9、Firefox叹放、Chrome 和 Safari 的 event 對(duì)象都支持一個(gè) charCode 屬性饰恕,這個(gè)屬性只有在發(fā)生 keypress 事件時(shí)才包含值,而且這個(gè)值是按下的那個(gè)鍵所代表字符的 ASCII 編碼井仰。此時(shí)的 keyCode 通常等于 0 或者也可能等于所按鍵的鍵碼埋嵌。
??IE8及之前版本和 Opera 則是在 keyCode 中保存字符的 ASCII 編碼。要想以跨瀏覽器的方式取得字符編碼俱恶,必須首先檢測(cè) charCode 屬性是否可用雹嗦,如果不可用則使用 keyCode,如下面的例子所示合是。
var EventUtil = {
// 省略的代碼
getCharCode: function(event){
if (typeof event.charCode == "number"){
return event.charCode;
} else {
return event.keyCode;
}
},
// 省略的代碼
};
??這個(gè)方法首先檢測(cè) charCode 屬性是否包含數(shù)值(在不支持這個(gè)屬性的瀏覽器中了罪,值為 undefined),如果是聪全,則返回該值泊藕。否則,就返回 keyCode 屬性值难礼。下面是使用這個(gè)方法的示例吱七。
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
alert(EventUtil.getCharCode(event));
});
??在取得了字符編碼之后,就可以使用 String.fromCharCode() 將其轉(zhuǎn)換成實(shí)際的字符鹤竭。
3. DOM3 級(jí)變化
??盡管所有瀏覽器都實(shí)現(xiàn)了某種形式的鍵盤事件,DOM3 級(jí)事件還是做出了一些改變景醇。比如臀稚,DOM3 級(jí)事件中的鍵盤事件,不再包含 charCode 屬性三痰,而是包含兩個(gè)新屬性:key 和 char吧寺。
??其中窜管,key 屬性是為了取代 keyCode 而新增的,它的值是一個(gè)字符串稚机。在按下某個(gè)字符鍵時(shí)幕帆,key 的值就是相應(yīng)的文本字符(如“k”或“M”);在按下非字符鍵時(shí)赖条, key 的值是相應(yīng)鍵的名(如“Shift” 或 “Down”)失乾。
??而 char 屬性在按下字符鍵時(shí)的行為與 key 相同,但在按下非字符鍵時(shí)值為 null纬乍。
??IE9 支持 key 屬性碱茁,但不支持 char 屬性。Safari 5 和 Chrome 支持名為 keyIdentifier 的屬性仿贬,在按下非字符鍵(例如 Shift)的情況下與 key 的值相同纽竣。
??對(duì)于字符鍵,keyIdentifier 返回一個(gè)格式類似“U+0000”的字符串茧泪,表示 Unicode 值蜓氨。
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
var identifier = event.key || event.keyIdentifier;
if (identifier){
alert(identifier);
}
});
??由于存在跨瀏覽器問題,因此本書不推薦使用 key队伟、keyIdentifier 或 char禀综。
??DOM3 級(jí)事件還添加了一個(gè)名為 location 的屬性,這是一個(gè)數(shù)值铛楣,表示按下了什么位置上的鍵:0 表示默認(rèn)鍵盤弱左,1 表示左側(cè)位置(例如左位的 Alt 鍵),2 表示右側(cè)位置(例如右側(cè)的 Shift 鍵)棘钞,3 表示數(shù)字小鍵盤缠借,4 表示移動(dòng)設(shè)備鍵盤(也就是虛擬鍵盤),5 表示手柄(如任天堂 Wii 控制器)宜猜。
??IE9 支持這個(gè)屬性泼返。Safari 和 Chrome 支持名為 keyLocation 的等價(jià)屬性,但即有 bug——值始終是 0姨拥,除非按下了數(shù)字鍵盤(此時(shí)绅喉,值 為 3);否則叫乌,不會(huì)是 1柴罐、2、4憨奸、5革屠。
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
var loc = event.location || event.keyLocation;
if (loc){
alert(loc);
}
});
??與 key 屬性一樣,支持 location 的瀏覽器也不多,所以在跨瀏覽器開發(fā)中不推薦使用似芝。
??最后是給 event 對(duì)象添加了 getModifierState() 方法那婉。這個(gè)方法接收一個(gè)參數(shù),即等于 Shift党瓮、Control详炬、AltGraph 或 Meta 的字符串,表示要檢測(cè)的修改鍵寞奸。如果指定的修改鍵是活動(dòng)的(也就是處于被按下的狀態(tài))呛谜,這個(gè)方法返回 true,否則返回 false蝇闭。
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "keypress", function(event){
event = EventUtil.getEvent(event);
if (event.getModifierState){
alert(event.getModifierState("Shift"));
}
});
??實(shí)際上呻率,通過 event 對(duì)象的 shiftKey、altKey呻引、ctrlKey 和 metaKey 屬性已經(jīng)可以取得類似的屬性了礼仗。IE9 是唯一支持 getModifierState() 方法的瀏覽器。
4. textInput 事件
??“DOM3 級(jí)事件”規(guī)范中引入了一個(gè)新事件逻悠,名叫 textInput元践。根據(jù)規(guī)范,當(dāng)用戶在可編輯區(qū)域中輸入字符時(shí)童谒,就會(huì)觸發(fā)這個(gè)事件单旁。
??這個(gè)用于替代 keypress 的 textInput 事件的行為稍有不同。
??區(qū)別之一就是任何可以獲得焦點(diǎn)的元素都可以觸發(fā) keypress 事件饥伊,但只有可編輯區(qū)域才能觸發(fā) textInput事件象浑。
??區(qū)別之二是 textInput 事件只會(huì)在用戶按下能夠輸入實(shí)際字符的鍵時(shí)才會(huì)被觸發(fā),而 keypress 事件則在按下那些能夠影響文本顯示的鍵時(shí)也會(huì)觸發(fā)(例如退格鍵)琅豆。
??由于 textInput 事件主要考慮的是字符愉豺,因此它的 event 對(duì)象中還包含一個(gè) data 屬性,這個(gè)屬性的值就是用戶輸入的字符(而非字符編碼)茫因。換句話說蚪拦,用戶在沒有按上檔鍵的情況下按下了 S 鍵,data 的值就是"s"冻押,而如果在按住上檔鍵時(shí)按下該鍵驰贷,data 的值就是"S"。以下是一個(gè)使用 textInput 事件的例子:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "textInput", function(event){
event = EventUtil.getEvent(event);
alert(event.data);
});
??在上述例子中洛巢,插入到文本框中的字符會(huì)通過一個(gè)警告框顯示出來(lái)括袒。
??另外,event 對(duì)象上還有一個(gè)屬性稿茉,叫 inputMethod箱熬,表示把文本輸入到文本框中的方式类垦。
- 0,表示瀏覽器不確定是怎么輸入的城须。
- 1,表示是使用鍵盤輸入的米苹。
- 2糕伐,表示文本是粘貼進(jìn)來(lái)的。
- 3蘸嘶,表示文本是拖放進(jìn)來(lái)的良瞧。
- 4,表示文本是使用 IME 輸入的训唱。
- 5褥蚯,表示文本是通過在表單中選擇某一項(xiàng)輸入的。
- 6况增,表示文本是通過手寫輸入的(比如使用手寫筆)赞庶。
- 7,表示文本是通過語(yǔ)音輸入的澳骤。
- 8歧强,表示文本是通過幾種方法組合輸入的。
- 9为肮,表示文本是通過腳本輸入的摊册。
??使用這個(gè)屬性可以確定文本是如何輸入到控件中的,從而可以驗(yàn)證其有效性颊艳。支持 textInput 屬性的瀏覽器有 IE9+茅特、Safari 和 Chrome。只有 IE 支持 inputMethod 屬性棋枕。
5. 設(shè)備中的鍵盤事件
??任天堂 Wii 會(huì)在用戶按下 Wii 遙控器上的按鍵時(shí)觸發(fā)鍵盤事件白修。盡管沒有辦法訪問 Wii 遙控器中的所有按鍵,但還是有一些鍵可以觸發(fā)鍵盤事件戒悠。
??iOS 版 Safari 和 Android 版 WebKit 在使用屏幕鍵盤時(shí)會(huì)觸發(fā)鍵盤事件熬荆。
4.5、 復(fù)合事件
??復(fù)合事件(composition event)是 DOM3 級(jí)事件中新添加的一類事件绸狐,用于處理 IME 的輸入序列卤恳。
??IME(Input Method Editor,輸入法編輯器)可以讓用戶輸入在物理鍵盤上找不到的字符寒矿。例如突琳,使用拉丁文鍵盤的用戶通過 IME 照樣能輸入日文字符。IME 通常需要同時(shí)按住多個(gè)鍵符相,但最終只輸入一個(gè)字符拆融。復(fù)合事件就是針對(duì)檢測(cè)和處理這種輸入而設(shè)計(jì)的蠢琳。有以下三種復(fù)合事件。
- compositionstart:在 IME 的文本復(fù)合系統(tǒng)打開時(shí)觸發(fā)镜豹,表示要開始輸入了傲须。
- compositionupdate:在向輸入字段中插入新字符時(shí)觸發(fā)。
- compositionend:在 IME 的文本復(fù)合系統(tǒng)關(guān)閉時(shí)觸發(fā)趟脂,表示返回正常鍵盤輸入狀態(tài)泰讽。
??復(fù)合事件與文本事件在很多方面都很相似。在觸發(fā)復(fù)合事件時(shí)昔期,目標(biāo)是接收文本的輸入字段已卸。但它比文本事件的事件對(duì)象多一個(gè)屬性 data,其中包含以下幾個(gè)值中的一個(gè):
- 如果在 compositionstart 事件發(fā)生時(shí)訪問硼一,包含正在編輯的文本(例如累澡,已經(jīng)選中的需要馬上替換的文本);
- 如果在 compositionupdate 事件發(fā)生時(shí)訪問般贼,包含正插入的新字符愧哟;
- 如果在 compositionend 事件發(fā)生時(shí)訪問,包含此次輸入會(huì)話中插入的所有字符具伍。
??與文本事件一樣翅雏,必要時(shí)可以利用復(fù)合事件來(lái)篩選輸入∪搜浚可以像下面這樣使用它們:
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "compositionstart", function(event){
event = EventUtil.getEvent(event);
alert(event.data);
});
EventUtil.addHandler(textbox, "compositionupdate", function(event){
event = EventUtil.getEvent(event);
alert(event.data);
});
EventUtil.addHandler(textbox, "compositionend", function(event){
event = EventUtil.getEvent(event);
alert(event.data);
});
??IE9+是到 2011 年唯一支持復(fù)合事件的瀏覽器望几。由于缺少支持,對(duì)于需要開發(fā)跨瀏覽器應(yīng)用的開發(fā)人員萤厅,它的用處不大橄抹。要確定瀏覽器是否支持復(fù)合事件,可以使用以下代碼:
var isSupported = document.implementation.hasFeature("CompositionEvent", "3.0");
4.6惕味、 變動(dòng)事件
??DOM2 級(jí)的變動(dòng)(mutation)事件能在 DOM 中的某一部分發(fā)生變化時(shí)給出提示楼誓。變動(dòng)事件是為 XML 或 HTML DOM 設(shè)計(jì)的,并不特定于某種語(yǔ)言名挥。DOM2 級(jí)定義了如下變動(dòng)事件疟羹。
- DOMSubtreeModified:在 DOM 結(jié)構(gòu)中發(fā)生任何變化時(shí)觸發(fā)。這個(gè)事件在其他任何事件觸發(fā)后都會(huì)觸發(fā)禀倔。
- DOMNodeInserted:在一個(gè)節(jié)點(diǎn)作為子節(jié)點(diǎn)被插入到另一個(gè)節(jié)點(diǎn)中時(shí)觸發(fā)榄融。
- DOMNodeRemoved:在節(jié)點(diǎn)從其父節(jié)點(diǎn)中被移除時(shí)觸發(fā)。
- DOMNodeInsertedIntoDocument:在一個(gè)節(jié)點(diǎn)被直接插入文檔或通過子樹間接插入文檔之后觸發(fā)救湖。這個(gè)事件在 DOMNodeInserted 之后觸發(fā)愧杯。
- DOMNodeRemovedFromDocument:在一個(gè)節(jié)點(diǎn)被直接從文檔中移除或通過子樹間接從文檔中移除之前觸發(fā)。這個(gè)事件在 DOMNodeRemoved 之后觸發(fā)鞋既。
- DOMAttrModified:在特性被修改之后觸發(fā)力九。
- DOMCharacterDataModified:在文本節(jié)點(diǎn)的值發(fā)生變化時(shí)觸發(fā)耍铜。
??使用下列代碼可以檢測(cè)出瀏覽器是否支持變動(dòng)事件:
var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");
??IE8 及更早版本不支持任何變動(dòng)事件。下表列出了不同瀏覽器對(duì)不同變動(dòng)事件的支持情況跌前。
事 件 | Opera 9+ | Firefox 3+ | Safari 3+及Chrome | IE9+ |
---|---|---|---|---|
DOMSubtreeModified | - | 支持 | 支持 | 支持 |
DOMNodeInserted | 支持 | 支持 | 支持 | 支持 |
DOMNodeRemoved | 支持 | 支持 | 支持 | 支持 |
??由于 DOM3 級(jí)事件模塊作廢了很多變動(dòng)事件棕兼,所以本節(jié)只介紹那些將來(lái)仍然會(huì)得到支持的事件。
1. 刪除節(jié)點(diǎn)
??在使用 removeChild() 或 replaceChild() 從 DOM 中刪除節(jié)點(diǎn)時(shí)舒萎,首先會(huì)觸發(fā) DOMNodeRemoved 事件程储。這個(gè)事件的目標(biāo)(event.target)是被刪除的節(jié)點(diǎn),而 event.relatedNode 屬性中包含著對(duì)目標(biāo)節(jié)點(diǎn)父節(jié)點(diǎn)的引用臂寝。在這個(gè)事件觸發(fā)時(shí),節(jié)點(diǎn)尚未從其父節(jié)點(diǎn)刪除摊灭,因此其 parentNode 屬性仍然指向父節(jié)點(diǎn)(與 event.relatedNode 相同)咆贬。這個(gè)事件會(huì)冒泡,因而可以在 DOM 的任何層次上面處理它帚呼。
??如果被移除的節(jié)點(diǎn)包含子節(jié)點(diǎn)掏缎,那么在其所有子節(jié)點(diǎn)以及這個(gè)被移除的節(jié)點(diǎn)上會(huì)相繼觸發(fā) DOMNodeRemovedFromDocument 事件。但這個(gè)事件不會(huì)冒泡煤杀,所以只有直接指定給其中一個(gè)子節(jié)點(diǎn)的事件處理程序才會(huì)被調(diào)用眷蜈。這個(gè)事件的目標(biāo)是相應(yīng)的子節(jié)點(diǎn)或者那個(gè)被移除的節(jié)點(diǎn),除此之外 event 對(duì)象中不包含其他信息沈自。
??緊隨其后觸發(fā)的是 DOMSubtreeModified 事件酌儒。這個(gè)事件的目標(biāo)是被移除節(jié)點(diǎn)的父節(jié)點(diǎn);此時(shí)的 event 對(duì)象也不會(huì)提供與事件相關(guān)的其他信息枯途。
??為了理解上述事件的觸發(fā)過程忌怎,下面我們就以一個(gè)簡(jiǎn)單的 HTML 頁(yè)面為例。
<! DOCTYPE html>
<html>
<head>
<title>Node Removal Events Example</title>
</head>
<body>
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</body>
</html>
??在上述例子中酪夷,我們假設(shè)要移除<ul>元素榴啸。此時(shí),就會(huì)依次觸發(fā)以下事件晚岭。
????(1) 在<ul>元素上觸發(fā) DOMNodeRemoved 事件鸥印。relatedNode 屬性等于 document.body。
????(2) 在<ul>元素上觸發(fā) DOMNodeRemovedFromDocument 事件坦报。
????(3) 在身為<ul>元素子節(jié)點(diǎn)的每個(gè)<li>元素及文本節(jié)點(diǎn)上觸發(fā) DOMNodeRemovedFromDocument 事件库说。
????(4) 在 document.body 上觸發(fā) DOMSubtreeModified 事件,因?yàn)?lt;ul>元素是 document.body 的直接子元素燎竖。
??運(yùn)行下列代碼可以驗(yàn)證以上事件發(fā)生的順序璃弄。
EventUtil.addHandler(window, "load", function(event){
var list = document.getElementById("myList");
EventUtil.addHandler(document, "DOMSubtreeModified", function(event){
alert(event.type);
alert(event.target);
});
EventUtil.addHandler(document, "DOMNodeRemoved", function(event){
alert(event.type);
alert(event.target);
alert(event.relatedNode);
});
EventUtil.addHandler(list.firstChild, "DOMNodeRemovedFromDocument", function(event){
alert(event.type);
alert(event.target);
});
list.parentNode.removeChild(list);
});
??上述代碼為 document 添加了針對(duì) DOMSubtreeModified 和 DOMNodeRemoved 事件的處理程序,以便在頁(yè)面上處理這些事件构回。由于 DOMNodeRemovedFromDocument 不會(huì)冒泡夏块,所以我們將針對(duì)它的
事件處理程序直接添加給了<ul>元素的第一個(gè)子節(jié)點(diǎn)(在兼容 DOM 的瀏覽器中是一個(gè)文本節(jié)點(diǎn))疏咐。在設(shè)置了以上事件處理程序后,代碼從文檔中移除了<ul>元素脐供。
2. 插入節(jié)點(diǎn)
??在使用 appendChild()浑塞、replaceChild() 或 insertBefore() 向 DOM 中插入節(jié)點(diǎn)時(shí),首先會(huì)觸發(fā) DOMNodeInserted 事件政己。這個(gè)事件的目標(biāo)是被插入的節(jié)點(diǎn)酌壕,而 event.relatedNode 屬性中包含一個(gè)對(duì)父節(jié)點(diǎn)的引用。在這個(gè)事件觸發(fā)時(shí)歇由,節(jié)點(diǎn)已經(jīng)被插入到了新的父節(jié)點(diǎn)中卵牍。這個(gè)事件是冒泡的,因此可以在 DOM 的各個(gè)層次上處理它沦泌。
??緊接著糊昙,會(huì)在新插入的節(jié)點(diǎn)上面觸發(fā) DOMNodeInsertedIntoDocument 事件。這個(gè)事件不冒泡谢谦,因此必須在插入節(jié)點(diǎn)之前為它添加這個(gè)事件處理程序释牺。這個(gè)事件的目標(biāo)是被插入的節(jié)點(diǎn),除此之外 event 對(duì)象中不包含其他信息回挽。
??最后一個(gè)觸發(fā)的事件是 DOMSubtreeModified没咙,觸發(fā)于新插入節(jié)點(diǎn)的父節(jié)點(diǎn)寒瓦。
??我們?nèi)砸郧懊娴?HTML 文檔為例派撕,可以通過下列 JavaScript 代碼來(lái)驗(yàn)證上述事件的觸發(fā)順序硬霍。
EventUtil.addHandler(window, "load", function(event){
var list = document.getElementById("myList");
var item = document.createElement("li");
item.appendChild(document.createTextNode("Item 4"));
EventUtil.addHandler(document, "DOMSubtreeModified", function(event){
alert(event.type);
alert(event.target);
});
EventUtil.addHandler(document, "DOMNodeInserted", function(event){
alert(event.type);
alert(event.target);
alert(event.relatedNode);
});
EventUtil.addHandler(item, "DOMNodeInsertedIntoDocument", function(event){
alert(event.type);
alert(event.target);
});
list.appendChild(item);
});
??上述代碼首先創(chuàng)建了一個(gè)包含文本"Item 4"的新<li>元素寒屯。由于 DOMSubtreeModified 和 DOMNodeInserted 事件是冒泡的凌摄,所以把它們的事件處理程序添加到了文檔中穴张。在將列表項(xiàng)插入到其父節(jié)點(diǎn)之前鲁猩,先將 DOMNodeInsertedIntoDocument 事件的事件處理程序添加給它腾供。最后一步就是使用 appendChild() 來(lái)添加這個(gè)列表項(xiàng)憔古;此時(shí)遮怜,事件開始依次被觸發(fā)。
??首先是在新<li>元素項(xiàng)上觸發(fā) DOMNodeInserted 事件鸿市,其 relatedNode 是<ul>元素锯梁。然后是觸發(fā)新<li>元素上的 DOMNodeInsertedIntoDocument 事件,最后觸發(fā)的是<ul>元素上的 DOMSubtreeModified 事件焰情。
4.7陌凳、HTML5 事件
??DOM 規(guī)范沒有涵蓋所有瀏覽器支持的所有事件。很多瀏覽器出于不同的目的——滿足用戶需求或解決特殊問題内舟,還實(shí)現(xiàn)了一些自定義的事件合敦。
??HTML5 詳盡列出了瀏覽器應(yīng)該支持的所有事件。本節(jié)只討論其中得到瀏覽器完善支持的事件验游,但并非所有事件充岛。
1. contextmenu 事件
??Windows 95 在 PC 中引入了上下文菜單的概念保檐,即通過單擊鼠標(biāo)右鍵可以調(diào)出上下文菜單。不久崔梗, 這個(gè)概念也被引入了 Web 領(lǐng)域夜只。
??為了實(shí)現(xiàn)上下文菜單,開發(fā)人員面臨的主要問題是如何確定應(yīng)該顯示上下文菜單(在 Windows 中蒜魄,是右鍵單擊扔亥;在 Mac 中,是 Ctrl+單擊)谈为,以及如何屏蔽與該操作關(guān)聯(lián)的默認(rèn)上下文菜單旅挤。
??為解決這個(gè)問題,就出現(xiàn)了 contextmenu 這個(gè)事件伞鲫,用以表示何時(shí)應(yīng)該顯示上下文菜單谦铃,以便開發(fā)人員取消默認(rèn)的上下文菜單而提供自定義的菜單。
??由于 contextmenu 事件是冒泡的榔昔,因此可以為 document 指定一個(gè)事件處理程序,用以處理頁(yè)面中發(fā)生的所有此類事件瘪菌。
??這個(gè)事件的目標(biāo)是發(fā)生用戶操作的元素撒会。在所有瀏覽器中都可以取消這個(gè)事件: 在兼容 DOM 的瀏覽器中,使用 event.preventDefalut()师妙;在 IE 中诵肛,將 event.returnValue 的值設(shè)置為 false。
??因?yàn)?contextmenu 事件屬于鼠標(biāo)事件默穴,所以其事件對(duì)象中包含與光標(biāo)位置有關(guān)的所有屬性怔檩。通常使用 contextmenu 事件來(lái)顯示自定義的上下文菜單,而使用 onclick 事件處理程序來(lái)隱 藏該菜單蓄诽。以下面的 HTML 頁(yè)面為例薛训。
<!DOCTYPE html>
<html>
<head>
<title>ContextMenu Event Example</title>
</head>
<body>
<div id="myDiv">Right click or Ctrl+click me to get a custom context menu. Click anywhere else to get the default context menu.</div>
<ul id="myMenu" style="position:absolute;visibility:hidden;background-color: silver">
<li><a >Nicholas’ site</a></li>
<li><a >Wrox site</a></li>
<li><a >Yahoo!</a></li>
</ul>
</body>
</html>
??這里的<div>元素包含一個(gè)自定義的上下文菜單。其中仑氛,<ul>元素作為自定義上下文菜單乙埃,并且在初始時(shí)是隱藏的。實(shí)現(xiàn)這個(gè)例子的 JavaScript 代碼如下所示锯岖。
EventUtil.addHandler(window, "load", function(event){
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "contextmenu", function(event){
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
var menu = document.getElementById("myMenu");
menu.style.left = event.clientX + "px";
menu.style.top = event.clientY + "px";
menu.style.visibility = "visible";
});
EventUtil.addHandler(document, "click", function(event){
document.getElementById("myMenu").style.visibility = "hidden";
});
});
??在上述例子中介袜,我們?yōu)?lt;div>元素添加了 oncontextmenu 事件的處理程序。
??這個(gè)事件處理程序首先會(huì)取消默認(rèn)行為出吹,以保證不顯示瀏覽器默認(rèn)的上下文菜單遇伞。
??然后,再根據(jù) event 對(duì)象 clientX 和 clientY 屬性的值捶牢,來(lái)確定放置<ul>元素的位置鸠珠。
??最后一步就是通過將 visibility 屬性設(shè)置為"visible"來(lái)顯示自定義上下文菜單巍耗。
??另外,還為 document 添加了一個(gè) onclick 事件處理程序跳芳,以便用戶能夠通過鼠標(biāo)單擊來(lái)隱藏菜單(單擊也是隱藏系統(tǒng)上下文菜單的默認(rèn)操作)芍锦。
??雖然這個(gè)例子很簡(jiǎn)單,但它卻展示了 Web 上所有自定義上下文菜單的基本結(jié)構(gòu)飞盆。只需為這個(gè)例子中的上下文菜單添加一些 CSS 樣式娄琉,就可以得到非常棒的效果。
??支持 contextmenu 事件的瀏覽器有 IE吓歇、Firefox孽水、Safari、Chrome 和 Opera 11+城看。
2. beforeunload 事件
??之所以有發(fā)生在 window 對(duì)象上的 beforeunload 事件女气,是為了讓開發(fā)人員有可能在頁(yè)面卸載前阻止這一操作。
??這個(gè)事件會(huì)在瀏覽器卸載頁(yè)面之前觸發(fā)测柠,可以通過它來(lái)取消卸載并繼續(xù)使用原有頁(yè)面炼鞠。
??但是,不能徹底取消這個(gè)事件轰胁,因?yàn)槟蔷拖喈?dāng)于讓用戶無(wú)法離開當(dāng)前頁(yè)面了谒主。為此,這個(gè)事件的意圖是將控制權(quán)交給用戶赃阀。顯示的消息會(huì)告知用戶頁(yè)面行將被卸載(正因?yàn)槿绱瞬艜?huì)顯示這個(gè)消息)霎肯,詢問用戶是否真的要關(guān)閉頁(yè)面,還是希望繼續(xù)留下來(lái)榛斯。
??為了顯示這個(gè)彈出對(duì)話框观游,必須將 event.returnValue 的值設(shè)置為要顯示給用戶的字符串(對(duì) IE 及 Fiefox 而言),同時(shí)作為函數(shù)的值返回(對(duì) Safari 和 Chrome 而言)驮俗,如下面的例子所示懂缕。
EventUtil.addHandler(window, "beforeunload", function(event){
event = EventUtil.getEvent(event);
var message = "I'm really going to miss you if you go.";
event.returnValue = message;
return message;
});
??IE 和 Firefox、Safari 和 Chrome 都支持 beforeunload 事件意述,也都會(huì)彈出這個(gè)對(duì)話框詢問用戶是否真想離開提佣。Opera 11 及之前的版本不支持 beforeunload 事件。
3. DOMContentLoaded 事件
??如前所述荤崇,window 的 load 事件會(huì)在頁(yè)面中的一切都加載完畢時(shí)觸發(fā)拌屏,但這個(gè)過程可能會(huì)因?yàn)橐虞d的外部資源過多而頗費(fèi)周折。
??而 DOMContentLoaded 事件則在形成完整的 DOM 樹之后就會(huì)觸發(fā)术荤,不理會(huì)圖像倚喂、JavaScript 文件、CSS 文件或其他資源是否已經(jīng)下載完畢。
??與 load 事件不同端圈,DOMContentLoaded 支持在頁(yè)面下載的早期添加事件處理程序焦读,這也就意味著用戶能夠盡早地與頁(yè)面進(jìn)行交互。
??要處理 DOMContentLoaded 事件舱权,可以為 document 或 window 添加相應(yīng)的事件處理程序(盡管這個(gè)事件會(huì)冒泡到 window矗晃,但它的目標(biāo)實(shí)際上是 document)。來(lái)看下面的例子宴倍。
EventUtil.addHandler(document, "DOMContentLoaded", function(event){
alert("Content loaded");
});
??DOMContentLoaded 事件對(duì)象不會(huì)提供任何額外的信息(其 target 屬性是 document)张症。
??IE9+、Firefox鸵贬、Chrome俗他、Safari 3.1+和 Opera 9+都支持 DOMContentLoaded 事件,通常這個(gè)事件既可以添加事件處理程序阔逼,也可以執(zhí)行其他 DOM 操作兆衅。這個(gè)事件始終都會(huì)在 load 事件之前觸發(fā)。
??對(duì)于不支持 DOMContentLoaded 的瀏覽器嗜浮,我們建議在頁(yè)面加載期間設(shè)置一個(gè)時(shí)間為 0 毫秒的超時(shí)調(diào)用羡亩,如下面的例子所示。
setTimeout(function(){
// 在此添加事件處理程序
}, 0);
??這段代碼的實(shí)際意思就是:“在當(dāng)前 JavaScript 處理完成后立即運(yùn)行這個(gè)函數(shù)危融∠Υ海”在頁(yè)面下載和構(gòu)建期間,只有一個(gè) JavaScript 處理過程专挪,因此超時(shí)調(diào)用會(huì)在該過程結(jié)束時(shí)立即觸發(fā)。至于這個(gè)時(shí)間與 DOMContentLoaded 被觸發(fā)的時(shí)間能否同步片排,主要還是取決于用戶使用的瀏覽器和頁(yè)面中的其他代碼寨腔。
??為了確保這個(gè)方法有效,必須將其作為頁(yè)面中的第一個(gè)超時(shí)調(diào)用率寡;即便如此迫卢,也還是無(wú)法保證在所有環(huán)境中該超時(shí)調(diào)用一定會(huì)早于 load 事件被觸發(fā)。
4. readystatechange 事件
??IE 為 DOM 文檔中的某些部分提供了 readystatechange 事件冶共。這個(gè)事件的目的是提供與文檔或元素的加載狀態(tài)有關(guān)的信息乾蛤,但這個(gè)事件的行為有時(shí)候也很難預(yù)料。支持 readystatechange 事件的每個(gè)對(duì)象都有一個(gè) readyState 屬性捅僵,可能包含下列 5 個(gè)值中的一個(gè)家卖。
- uninitialized(未初始化):對(duì)象存在但尚未初始化。
- loading(正在加載):對(duì)象正在加載數(shù)據(jù)庙楚。
- loaded(加載完畢):對(duì)象加載數(shù)據(jù)完成上荡。
- interactive(交互):可以操作對(duì)象了,但還沒有完全加載馒闷。
- complete(完成):對(duì)象已經(jīng)加載完畢酪捡。
??這些狀態(tài)看起來(lái)很直觀叁征,但并非所有對(duì)象都會(huì)經(jīng)歷 readyState 的這幾個(gè)階段。換句話說逛薇,如果某個(gè)階段不適用某個(gè)對(duì)象捺疼,則該對(duì)象完全可能跳過該階段;并沒有規(guī)定哪個(gè)階段適用于哪個(gè)對(duì)象永罚。顯然啤呼,這意味著 readystatechange 事件經(jīng)常會(huì)少于 4 次,而 readyState 屬性的值也不總是連續(xù)的尤蛮。
??對(duì)于 document 而言媳友,值為"interactive"的 readyState 會(huì)在與 DOMContentLoaded 大致相同的時(shí)刻觸發(fā) readystatechange 事件。此時(shí)产捞,DOM 樹已經(jīng)加載完畢醇锚,可以安全地操作它了,因此就會(huì)進(jìn)入交互(interactive)階段坯临。但與此同時(shí)焊唬,圖像及其他外部文件不一定可用。下面來(lái)看一段處理 readystatechange 事件的代碼看靠。
EventUtil.addHandler(document, "readystatechange", function(event){
if (document.readyState == "interactive"){
alert("Content loaded");
}
});
??這個(gè)事件的 event 對(duì)象不會(huì)提供任何信息赶促,也沒有目標(biāo)對(duì)象。
??在與 load 事件一起使用時(shí)挟炬,無(wú)法預(yù)測(cè)兩個(gè)事件觸發(fā)的先后順序鸥滨。在包含較多或較大的外部資源的頁(yè)面中,會(huì)在 load 事件觸發(fā)之前先進(jìn)入交互階段谤祖;而在包含較少或較小的外部資源的頁(yè)面中婿滓,則很難說 readystatechange 事件會(huì)發(fā)生在 load 事件前面。
??讓問題變得更復(fù)雜的是粥喜,交互階段可能會(huì)早于也可能會(huì)晚于完成階段出現(xiàn)凸主,無(wú)法確保順序。在包含較多外部資源的頁(yè)面中额湘,交互階段更有可能早于完成階段出現(xiàn)卿吐;而在頁(yè)面中包含較少外部資源的情況下,完成階段先于交互階段出現(xiàn)的可能性更大锋华。因此嗡官,為了盡可能搶到先機(jī),有必要同時(shí)檢測(cè)交互和完成階段毯焕,如下面的例子所示谨湘。
EventUtil.addHandler(document, "readystatechange", function(event){
if (document.readyState == "interactive" || document.readyState == "complete"){
EventUtil.removeHandler(document, "readystatechange", arguments.callee);
alert("Content loaded");
}
});
??對(duì)于上面的代碼來(lái)說,當(dāng) readystatechange 事件觸發(fā)時(shí),會(huì)檢測(cè) document.readyState 的值紧阔,看當(dāng)前是否已經(jīng)進(jìn)入交互階段或完成階段坊罢。如果是,則移除相應(yīng)的事件處理程序以免在其他階段再執(zhí)行擅耽。
注意活孩,由于事件處理程序使用的是匿名函數(shù),因此這里使用了 arguments.callee 來(lái)引用該函數(shù)乖仇。然后憾儒,會(huì)顯示一個(gè)警告框,說明內(nèi)容已經(jīng)加載完畢乃沙。這樣編寫代碼可以達(dá)到與使用 DOMContentLoaded 十分相近的效果起趾。
??支持 readystatechange 事件的瀏覽器有 IE、Firfox 4+和 Opera警儒。
??雖然使用 readystatechange 可以十分近似地模擬 DOMContentLoaded 事件训裆,但它們本質(zhì)上還是不同的。在不同頁(yè)面中蜀铲,load 事件與 readystatechange 事件并不能保證以相同的順序觸發(fā)边琉。
??另外, <script>(在 IE 和 Opera 中)和<link>(僅 IE 中)元素也會(huì)觸發(fā) readystatechange 事件记劝,可以用來(lái)確定外部的 JavaScript 和 CSS 文件是否已經(jīng)加載完成变姨。
??與在其他瀏覽器中一樣,除非把動(dòng)態(tài)創(chuàng)建的元素添加到頁(yè)面中厌丑,否則瀏覽器不會(huì)開始下載外部資源定欧。
??基于元素觸發(fā)的 readystatechange 事件也存在同樣的問題,即 readyState 屬性無(wú)論等于 "loaded" 還是"complete"都可以表示資源已經(jīng)可用怒竿。
??有時(shí)候忧额,readyState 會(huì)停在"loaded"階段而永遠(yuǎn)不會(huì)“完成”掰邢;有時(shí)候电爹,又會(huì)跳過"loaded"階段而直接“完成”气嫁。于是,還需要像對(duì)待 document 一樣采取相同的編碼方式耍属。例如,下面展示了一段加載外部 JavaScript 文件的代碼巩检。
EventUtil.addHandler(window, "load", function(){
var script = document.createElement("script");
EventUtil.addHandler(script, "readystatechange", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.readyState == "loaded" || target.readyState == "complete"){
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("Script Loaded");
}
});
script.src = "example.js";
document.body.appendChild(script);
});
??上述例子為新創(chuàng)建的<script>節(jié)點(diǎn)指定了一個(gè)事件處理程序厚骗。事件的目標(biāo)是該節(jié)點(diǎn)本身,因此當(dāng)觸發(fā) readystatechange 事件時(shí)兢哭,要檢測(cè)目標(biāo)的 readyState 屬性是不是等于"loaded"或"complete"领舰。如果進(jìn)入了其中任何一個(gè)階段,則移除事件處理程序(以防止被執(zhí)行兩次),并顯示一個(gè)警告框冲秽。與此同時(shí)舍咖,就可以執(zhí)行已經(jīng)加載完畢的外部文件中的函數(shù)了。
??同樣的編碼方式也適用于通過<link>元素加載 CSS 文件的情況锉桑,如下面的例子所示排霉。
EventUtil.addHandler(window, "load", function(){
var link = document.createElement("link");
link.type = "text/css";
link.rel= "stylesheet";
EventUtil.addHandler(script, "readystatechange", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.readyState == "loaded" || target.readyState == "complete"){
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("CSS Loaded");
}
});
link.href = "example.css";
document.getElementsByTagName("head")[0].appendChild(link);
});
??同樣,最重要的是要一并檢測(cè) readyState 的兩個(gè)狀態(tài)民轴,并在調(diào)用了一次事件處理程序后就將其移除攻柠。
5. pageshow 和 pagehide 事件
??Firefox 和 Opera 有一個(gè)特性,名叫“往返緩存”(back-forward cache后裸,或 bfcache)瑰钮,可以在用戶使用瀏覽器的“后退”和“前進(jìn)”按鈕時(shí)加快頁(yè)面的轉(zhuǎn)換速度。
??這個(gè)緩存中不僅保存著頁(yè)面數(shù)據(jù)微驶,還保存了 DOM 和 JavaScript 的狀態(tài)浪谴;實(shí)際上是將整個(gè)頁(yè)面都保存在了內(nèi)存里。
??如果頁(yè)面位于 bfcache 中祈搜,那么再次打開該頁(yè)面時(shí)就不會(huì)觸發(fā) load 事件较店。盡管由于內(nèi)存中保存了整個(gè)頁(yè)面的狀態(tài),不觸發(fā) load 事件也不應(yīng)該會(huì)導(dǎo)致什么問題容燕,但為了更形象地說明 bfcache 的行為梁呈,F(xiàn)irefox 還是提供了一些新事件。
??第一個(gè)事件就是 pageshow蘸秘,這個(gè)事件在頁(yè)面顯示時(shí)觸發(fā)官卡,無(wú)論該頁(yè)面是否來(lái)自 bfcache。
??在重新加載的頁(yè)面中醋虏,pageshow 會(huì)在 load 事件觸發(fā)后觸發(fā)寻咒;而對(duì)于 bfcache 中的頁(yè)面,pageshow 會(huì)在頁(yè)面狀態(tài)完全恢復(fù)的那一刻觸發(fā)颈嚼。
??另外要注意的是毛秘,雖然這個(gè)事件的目標(biāo)是 document,但必須將其事件處理程序添加到 window阻课。來(lái)看下面的例子叫挟。
(function(){
var showCount = 0;
EventUtil.addHandler(window, "load", function(){
alert("Load fired");
});
EventUtil.addHandler(window, "pageshow", function(){
showCount++;
alert("Show has been fired " + showCount + " times.");
});
})();
??上述例子使用了私有作用域,以防止變量 showCount 進(jìn)入全局作用域限煞。當(dāng)頁(yè)面首次加載完成時(shí)抹恳,showCount 的值為 0。此后署驻,每當(dāng)觸發(fā) pageshow 事件奋献,showCount 的值就會(huì)遞增并通過警告框顯示出來(lái)健霹。
??如果你在離開包含以上代碼的頁(yè)面之后,又單擊“后退”按鈕返回該頁(yè)面瓶蚂,就會(huì)看到 showCount 每次遞增的值糖埋。這是因?yàn)樵撟兞康臓顟B(tài),乃至整個(gè)頁(yè)面的狀態(tài)扬跋,都被保存在了內(nèi)存中阶捆,當(dāng)你返回這個(gè)頁(yè)面時(shí),它們的狀態(tài)得到了恢復(fù)钦听。如果你單擊了瀏覽器的“刷新”按鈕洒试,那么 showCount 的值就會(huì)被重置為 0,因?yàn)轫?yè)面已經(jīng)完全重新加載了朴上。
??除了通常的屬性之外垒棋,pageshow 事件的 event 對(duì)象還包含一個(gè)名為 persisted 的布爾值屬性。
??如果頁(yè)面被保存在了 bfcache 中痪宰,則這個(gè)屬性的值為 true叼架;否則,這個(gè)屬性的值為 false衣撬」远可以像下面這樣在事件處理程序中檢測(cè)這個(gè)屬性。
(function(){
var showCount = 0;
EventUtil.addHandler(window, "load", function(){
alert("Load fired");
});
EventUtil.addHandler(window, "pageshow", function(){
showCount++;
alert("Show has been fired " + showCount + " times. Persisted? " + event.persisted);
});
})();
??通過檢測(cè) persisted 屬性具练,就可以根據(jù)頁(yè)面在 bfcache 中的狀態(tài)來(lái)確定是否需要采取其他操作乍构。
??與 pageshow 事件對(duì)應(yīng)的是 pagehide 事件,該事件會(huì)在瀏覽器卸載頁(yè)面的時(shí)候觸發(fā)扛点,而且是在 unload 事件之前觸發(fā)哥遮。
??與 pageshow 事件一樣,pagehide 在 document 上面觸發(fā)陵究,但其事件處理程序必須要添加到 window 對(duì)象眠饮。
??這個(gè)事件的 event 對(duì)象也包含 persisted 屬性,不過其用途稍有不同铜邮。來(lái)看下面的例子仪召。
EventUtil.addHandler(window, "pagehide", function(event){
alert("Hiding. Persisted? " + event.persisted);
});
??有時(shí)候,可能需要在 pagehide 事件觸發(fā)時(shí)根據(jù) persisted 的值采取不同的操作松蒜。
??對(duì)于 pageshow 事件扔茅,如果頁(yè)面是從 bfcache 中加載的,那么 persisted 的值就是 true牍鞠;對(duì)于 pagehide 事件,如果頁(yè)面在卸載之后會(huì)被保存在 bfcache 中评姨,那么 persisted 的值也會(huì)被設(shè)置為 true难述。
??因此萤晴,當(dāng)?shù)谝淮斡|發(fā) pageshow 時(shí),persisted 的值一定是 false胁后,而在第一次觸發(fā) pagehide 時(shí)店读,persisted 就會(huì)變成 true(除非頁(yè)面不會(huì)被保存在 bfcache 中)。
??支持 pageshow 和 pagehide 事件的瀏覽器有 Firefox攀芯、Safari 5+屯断、Chrome 和 Opera。IE9 及之前版本不支持這兩個(gè)事件侣诺。
??指定了 onunload 事件處理程序的頁(yè)面會(huì)被自動(dòng)排除在 bfcache 之外殖演,即使事件處理程序是空的。原因在于年鸳,onunload 最常用于撤銷在 onload 中所執(zhí)行的操作趴久,而跳過 onload 后再次顯示頁(yè)面很可能就會(huì)導(dǎo)致頁(yè)面不正常。
6. hashchange 事件
??HTML5 新增了 hashchange 事件搔确,以便在 URL 的參數(shù)列表(及 URL 中“#”號(hào)后面的所有字符串)發(fā)生變化時(shí)通知開發(fā)人員彼棍。
??之所以新增這個(gè)事件,是因?yàn)樵?Ajax 應(yīng)用中膳算,開發(fā)人員經(jīng)常要利用 URL 參數(shù)列表來(lái)保存狀態(tài)或?qū)Ш叫畔ⅰ?br>
??必須要把 hashchange 事件處理程序添加給 window 對(duì)象座硕,然后 URL 參數(shù)列表只要變化就會(huì)調(diào)用它。此時(shí)的 event 對(duì)象應(yīng)該額外包含兩個(gè)屬性:oldURL 和 newURL涕蜂。這兩個(gè)屬性分別保存著參數(shù)列表變化前后的完整 URL华匾。例如:
EventUtil.addHandler(window, "hashchange", function(event){
alert("Old URL: " + event.oldURL + "\nNew URL: " + event.newURL);
});
??支持 hashchange 事件的瀏覽器有 IE8+、Firefox 3.6+宇葱、Safari 5+瘦真、Chrome 和 Opera 10.6+。在這些瀏覽器中黍瞧,只有 Firefox 6+诸尽、Chrome 和 Opera 支持 oldURL 和 newURL 屬性。為此印颤,最好是使用 location 對(duì)象來(lái)確定當(dāng)前的參數(shù)列表您机。
EventUtil.addHandler(window, "hashchange", function(event){
alert("Current hash: " + location.hash);
});
??使用以下代碼可以檢測(cè)瀏覽器是否支持 hashchange 事件:
var isSupported = ("onhashchange" in window); // 這里有 bug
??如果 IE8 是在 IE7 文檔模式下運(yùn)行,即使功能無(wú)效它也會(huì)返回 true年局。為解決這個(gè)問題际看,可以使用以下這個(gè)更穩(wěn)妥的檢測(cè)方式:
var isSupported = ("onhashchange" in window) && (document.documentMode ===
undefined || document.documentMode > 7);
4.8、 設(shè)備事件
??智能手機(jī)和平板電腦的普及矢否,為用戶與瀏覽器交互引入了一種新的方式仲闽,而一類新事件也應(yīng)運(yùn)而生。
??設(shè)備事件(device event)可以讓開發(fā)人員確定用戶在怎樣使用設(shè)備僵朗。
??W3C 從 2011 年開始著手制定一份關(guān)于設(shè)備事件的新草案(http://dev.w3.org/geo/api/spec-source-orientation.html)赖欣,以涵蓋不斷增長(zhǎng)的設(shè)備類型并為它們定義相關(guān)的事件屑彻。本節(jié)會(huì)同時(shí)討論這份草案中涉及的 API 和特定于瀏覽器開發(fā)商的事件。
1. orientationchange 事件
??蘋果公司為移動(dòng) Safari 中添加了 orientationchange 事件顶吮,以便開發(fā)人員能夠確定用戶何時(shí)將設(shè)備由橫向查看模式切換為縱向查看模式社牲。
??移動(dòng) Safari 的 window.orientation 屬性中可能包含 3 個(gè)值:
0 表示肖像模式,90 表示向左旋轉(zhuǎn)的橫向模式(“主屏幕”按鈕在右側(cè))悴了,-90 表示向右旋轉(zhuǎn)的橫向模式(“主屏幕”按鈕在左側(cè))搏恤。
??相關(guān)文檔中還提到一個(gè)值,即 180 表示 iPhone 頭朝下湃交;但這種模式至今尚未得到支持熟空。
??只要用戶改變了設(shè)備的查看模式,就會(huì)觸發(fā) orientationchange 事件巡揍。此時(shí)的 event 對(duì)象不包含任何有價(jià)值的信息痛阻,因?yàn)槲ㄒ幌嚓P(guān)的信息可以通過 window.orientation 訪問到。下面是使用這個(gè)事件的典型示例腮敌。
EventUtil.addHandler(window, "load", function(event){
var div = document.getElementById("myDiv");
div.innerHTML = "Current orientation is " + window.orientation;
EventUtil.addHandler(window, "orientationchange", function(event){
div.innerHTML = "Current orientation is " + window.orientation;
});
});
??在上述例子中阱当,當(dāng)觸發(fā) load 事件時(shí)會(huì)顯示最初的方向信息。然后糜工,添加了處理 orientationchange 事件的處理程序弊添。只要發(fā)生這個(gè)事件,就會(huì)有表示新方向的信息更新頁(yè)面中的消息捌木。
??所有 iOS 設(shè)備都支持 orientationchange 事件和 window.orientation 屬性油坝。
??由于可以將 orientationchange 看成 window 事件,所以也可以通過指定
<body>元素的 onorientationchange 特性來(lái)指定事件處理程序刨裆。
2. MozOrientation 事件
??Firefox 3.6 為檢測(cè)設(shè)備的方向引入了一個(gè)名為 MozOrientation 的新事件澈圈。(前綴 Moz 表示這是特定于瀏覽器開發(fā)商的事件,不是標(biāo)準(zhǔn)事件帆啃。)當(dāng)設(shè)備的加速計(jì)檢測(cè)到設(shè)備方向改變時(shí)瞬女,就會(huì)觸發(fā)這個(gè)事件。
??但這個(gè)事件與 iOS 中的 orientationchange 事件不同努潘,該事件只能提供一個(gè)平面的方向變化诽偷。由于 MozOrientation 事件是在 window 對(duì)象上觸發(fā)的,所以可以使用以下代碼來(lái)處理疯坤。
EventUtil.addHandler(window, "MozOrientation", function(event){
// 響應(yīng)事件
});
??此時(shí)的 event 對(duì)象包含三個(gè)屬性:x报慕、y 和 z。這幾個(gè)屬性的值都介于 1 到-1 之間压怠,表示不同坐標(biāo)軸上的方向眠冈。
??在靜止?fàn)顟B(tài)下,x 值為 0菌瘫,y 值為 0蜗顽,z 值為 1(表示設(shè)備處于豎直狀態(tài))玄柠。如果設(shè)備向右傾斜,x 值會(huì)減薪刖恕;反之宫患,向左傾斜刊懈,x 值會(huì)增大。類似地娃闲,如果設(shè)備向遠(yuǎn)離用戶的方向傾斜虚汛,y 值會(huì)減小,向接近用戶的方向傾斜皇帮,y 值會(huì)增大卷哩。z 軸檢測(cè)垂直加速度度,1 表示靜止不動(dòng)属拾,在設(shè)備移動(dòng)時(shí)值會(huì)減小将谊。(失重狀態(tài)下值為 0。)以下是輸出這三個(gè)值的一個(gè)簡(jiǎn)單的例子渐白。
EventUtil.addHandler(window, "MozOrientation", function(event){
var output = document.getElementById("output");
output.innerHTML = "X=" + event.x + ", Y=" + event.y + ", Z=" + event.z +"<br>";
});
??只有帶加速計(jì)的設(shè)備才支持 MozOrientation 事件尊浓,包括 Macbook、Lenovo Thinkpad纯衍、Windows Mobile 和 Android 設(shè)備栋齿。請(qǐng)大家注意,這是一個(gè)實(shí)驗(yàn)性 API襟诸,將來(lái)可能會(huì)變(可能會(huì)被其他事件取代)瓦堵。
3. deviceorientation 事件
??本質(zhì)上,DeviceOrientation Event 規(guī)范定義的 deviceorientation 事件與 MozOrientation 事件類似歌亲。它也是在加速計(jì)檢測(cè)到設(shè)備方向變化時(shí)在 window 對(duì)象上觸發(fā)菇用,而且具有與 MozOrientation 事件相同的支持限制。不過应结,deviceorientation 事件的意圖是告訴開發(fā)人員設(shè)備在空間中朝向哪兒刨疼,而不是如何移動(dòng)。
??設(shè)備在三維空間中是靠 x鹅龄、y 和 z 軸來(lái)定位的揩慕。當(dāng)設(shè)備靜止放在水平表面上時(shí),這三個(gè)值都是 0扮休。x 軸方向是從左往右迎卤,y 軸方向是從下往上,z 軸方向是從后往前(如下圖所示)玷坠。
??觸發(fā) deviceorientation 事件時(shí)蜗搔,事件對(duì)象中包含著每個(gè)軸相對(duì)于設(shè)備靜止?fàn)顟B(tài)下發(fā)生變化的信息劲藐。事件對(duì)象包含以下 5 個(gè)屬性。
- alpha:在圍繞 z 軸旋轉(zhuǎn)時(shí)(即左右旋轉(zhuǎn)時(shí))樟凄,y 軸的度數(shù)差聘芜;是一個(gè)介于 0 到 360 之間的浮點(diǎn)數(shù)。
- beta:在圍繞 x 軸旋轉(zhuǎn)時(shí)(即前后旋轉(zhuǎn)時(shí))缝龄,z 軸的度數(shù)差汰现;是一個(gè)介于-180 到 180 之間的浮點(diǎn)數(shù)沿侈。
- gamma:在圍繞 y 軸旋轉(zhuǎn)時(shí)(即扭轉(zhuǎn)設(shè)備時(shí))宴合,z 軸的度數(shù)差;是一個(gè)介于 -90 到 90 之間的浮點(diǎn)數(shù)乡数。
- absolute:布爾值炼绘,表示設(shè)備是否返回一個(gè)絕對(duì)值嗅战。
- compassCalibrated:布爾值,表示設(shè)備的指南針是否校準(zhǔn)過俺亮。
??下面是一個(gè)輸出 alpha驮捍、beta 和 gamma 值的例子。
EventUtil.addHandler(window, "deviceorientation", function(event){
var output = document.getElementById("output");
output.innerHTML = "Alpha=" + event.alpha + ", Beta=" + event.beta + ", Gamma=" + event.gamma + "<br>";
});
??通過這些信息脚曾,可以響應(yīng)設(shè)備的方向厌漂,重新排列或修改屏幕上的元素。要響應(yīng)設(shè)備方向的改變而旋轉(zhuǎn)元素斟珊,可以參考如下代碼苇倡。
EventUtil.addHandler(window, "deviceorientation", function(event){
var arrow = document.getElementById("arrow");
arrow.style.webkitTransform = "rotate(" + Math.round(event.alpha) + "deg)";
});
??上述例子只能在移動(dòng) WebKit 瀏覽器中運(yùn)行,因?yàn)樗褂昧藢S械?webkitTransform 屬性(即 CSS 標(biāo)準(zhǔn)屬性 transform 的臨時(shí)版)囤踩。
??元素“arrow”會(huì)隨著 event.alpha 值的變化而旋轉(zhuǎn)旨椒,給人一種指南針的感覺。為了保證旋轉(zhuǎn)平滑堵漱,這里的 CSS3 變換使用了舍入之后的值综慎。
4. devicemotion 事件
??DeviceOrientation Event 規(guī)范還定義了一個(gè) devicemotion 事件。這個(gè)事件是要告訴開發(fā)人員設(shè)備什么時(shí)候移動(dòng)勤庐,而不僅僅是設(shè)備方向如何改變示惊。例如,通過 devicemotion 能夠檢測(cè)到設(shè)備是不是正在往下掉愉镰,或者是不是被走著的人拿在手里米罚。
??觸發(fā) devicemotion 事件時(shí),事件對(duì)象包含以下屬性丈探。
- acceleration:一個(gè)包含 x录择、y 和 z 屬性的對(duì)象,在不考慮重力的情況下,告訴你在每個(gè)方向上的加速度隘竭。
- accelerationIncludingGravity:一個(gè)包含 x塘秦、y 和 z 屬性的對(duì)象,在考慮 z 軸自然重力加速度的情況下动看,告訴你在每個(gè)方向上的加速度尊剔。
- interval:以毫秒表示的時(shí)間值,必須在另一個(gè) devicemotion 事件觸發(fā)前傳入菱皆。這個(gè)值在每個(gè)事件中應(yīng)該是一個(gè)常量赋兵。
- rotationRate:一個(gè)包含表示方向的 alpha、beta 和 gamma 屬性的對(duì)象搔预。
??如果讀取不到 acceleration、accelerationIncludingGravity 和 rotationRate 值叶组,則它們的值為 null拯田。因此,在使用這三個(gè)屬性之前甩十,應(yīng)該先檢測(cè)確定它們的值不是 null船庇。例如:
EventUtil.addHandler(window, "devicemotion", function(event){
var output = document.getElementById("output");
if (event.rotationRate !== null){
output.innerHTML += "Alpha=" + event.rotationRate.alpha + ",
Beta=" + event.rotationRate.beta + ",
Gamma=" + event.rotationRate.gamma;
}
});
??與 deviceorientation 事件類似,只有 iOS 4.2+中的 Safari侣监、Chrome 和 Android 版 WebKit 實(shí)現(xiàn)了devicemotion 事件鸭轮。
4.9、 觸摸與手勢(shì)事件
??iOS 版 Safari 為了向開發(fā)人員傳達(dá)一些特殊信息橄霉,新增了一些專有事件窃爷。因?yàn)?iOS 設(shè)備既沒有鼠標(biāo)也沒有鍵盤,所以在為移動(dòng) Safari 開發(fā)交互性網(wǎng)頁(yè)時(shí)姓蜂,常規(guī)的鼠標(biāo)和鍵盤事件根本不夠用按厘。隨著 Android 中的 WebKit 的加入,很多這樣的專有事件變成了事實(shí)標(biāo)準(zhǔn)钱慢,導(dǎo)致 W3C 開始制定 Touch Events 規(guī)范逮京。以下介紹的事件只針對(duì)觸摸設(shè)備。
1. 觸摸事件
??包含 iOS 2.0 軟件的 iPhone 3G 發(fā)布時(shí)束莫,也包含了一個(gè)新版本的 Safari 瀏覽器懒棉。這款新的移動(dòng) Safari 提供了一些與觸摸(touch)操作相關(guān)的新事件。后來(lái)览绿,Android 上的瀏覽器也實(shí)現(xiàn)了相同的事件策严。
??觸摸事件會(huì)在用戶手指放在屏幕上面時(shí)、在屏幕上滑動(dòng)時(shí)或從屏幕上移開時(shí)觸發(fā)饿敲。具體來(lái)說享钞,有以下幾個(gè)觸摸事件。
- touchstart:當(dāng)手指觸摸屏幕時(shí)觸發(fā);即使已經(jīng)有一個(gè)手指放在了屏幕上也會(huì)觸發(fā)栗竖。
- touchmove:當(dāng)手指在屏幕上滑動(dòng)時(shí)連續(xù)地觸發(fā)暑脆。在這個(gè)事件發(fā)生期間,調(diào)用 preventDefault() 可以阻止?jié)L動(dòng)狐肢。
- touchend:當(dāng)手指從屏幕上移開時(shí)觸發(fā)添吗。
- touchcancel:當(dāng)系統(tǒng)停止跟蹤觸摸時(shí)觸發(fā)。關(guān)于此事件的確切觸發(fā)時(shí)間份名,文檔中沒有明確說明碟联。
??上面這幾個(gè)事件都會(huì)冒泡,也都可以取消僵腺。雖然這些觸摸事件沒有在 DOM 規(guī)范中定義鲤孵,但它們卻是以兼容 DOM 的方式實(shí)現(xiàn)的。
??因此辰如,每個(gè)觸摸事件的 event 對(duì)象都提供了在鼠標(biāo)事件中常見的屬性:bubbles普监、cancelable、view琉兜、clientX凯正、clientY、screenX豌蟋、screenY廊散、detail、altKey梧疲、shiftKey允睹、ctrlKey 和 metaKey。
??除了常見的 DOM 屬性外幌氮,觸摸事件還包含下列三個(gè)用于跟蹤觸摸的屬性擂找。
- touches:表示當(dāng)前跟蹤的觸摸操作的 Touch 對(duì)象的數(shù)組。
- targetTouchs:特定于事件目標(biāo)的 Touch 對(duì)象的數(shù)組浩销。
- changeTouches:表示自上次觸摸以來(lái)發(fā)生了什么改變的 Touch 對(duì)象的數(shù)組贯涎。
??每個(gè) Touch 對(duì)象包含下列屬性。
- clientX:觸摸目標(biāo)在視口中的 x 坐標(biāo)慢洋。
- clientY:觸摸目標(biāo)在視口中的 y 坐標(biāo)塘雳。
- identifier:標(biāo)識(shí)觸摸的唯一 ID。
- pageX:觸摸目標(biāo)在頁(yè)面中的 x 坐標(biāo)普筹。
- pageY:觸摸目標(biāo)在頁(yè)面中的 y 坐標(biāo)败明。
- screenX:觸摸目標(biāo)在屏幕中的 x 坐標(biāo)。
- screenY:觸摸目標(biāo)在屏幕中的 y 坐標(biāo)太防。
- target:觸摸的 DOM 節(jié)點(diǎn)目標(biāo)妻顶。
??使用這些屬性可以跟蹤用戶對(duì)屏幕的觸摸操作酸员。來(lái)看下面的例子。
function handleTouchEvent(event){
// 只跟蹤一次觸摸
if (event.touches.length == 1){
var output = document.getElementById("output");
switch(event.type){
case "touchstart":
output.innerHTML = "Touch started (" + event.touches[0].clientX + "," + event.touches[0].clientY + ")";
break;
case "touchend":
output.innerHTML += "<br>Touch ended (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
break;
case "touchmove":
event.preventDefault(); //阻止?jié)L動(dòng)
output.innerHTML += "<br>Touch moved (" + event.changedTouches[0].clientX + "," + event.changedTouches[0].clientY + ")";
break;
}
}
}
EventUtil.addHandler(document, "touchstart", handleTouchEvent);
EventUtil.addHandler(document, "touchend", handleTouchEvent);
EventUtil.addHandler(document, "touchmove", handleTouchEvent);
??以上代碼會(huì)跟蹤屏幕上發(fā)生的一次觸摸操作讳嘱。為簡(jiǎn)單起見幔嗦,只會(huì)在有一次活動(dòng)觸摸操作的情況下輸出信息。
??當(dāng) touchstart 事件發(fā)生時(shí),會(huì)將觸摸的位置信息輸出到<div>元素中。
??當(dāng) touchmove 事件發(fā)生時(shí)挽鞠,會(huì)取消其默認(rèn)行為,阻止?jié)L動(dòng)(觸摸移動(dòng)的默認(rèn)行為是滾動(dòng)頁(yè)面)矛纹,然后輸出觸摸操作的變化信息。
??而 touchend 事件則會(huì)輸出有關(guān)觸摸操作的最終信息。注意,在 touchend 事件發(fā)生時(shí)因谎,touches 集合中就沒有任何 Touch 對(duì)象了,因?yàn)椴淮嬖诨顒?dòng)的觸摸操作颜懊;此時(shí)财岔,就必須轉(zhuǎn)而使用 changeTouchs 集合。
??這些事件會(huì)在文檔的所有元素上面觸發(fā)饭冬,因而可以分別操作頁(yè)面的不同部分。在觸摸屏幕上的元素時(shí)揪阶,這些事件(包括鼠標(biāo)事件)發(fā)生的順序如下:
????(1) touchstart
????(2) mouseover
????(3) mousemove(一次)
????(4) mousedown
????(5) mouseup
????(6) click
????(7) touchend
??支持觸摸事件的瀏覽器包括 iOS 版 Safari昌抠、Android 版 WebKit、bada 版 Dolfin鲁僚、OS6+中的 BlackBerry WebKit炊苫、Opera Mobile 10.1+和 LG 專有 OS 中的 Phantom 瀏覽器。目前只有 iOS 版 Safari 支持多點(diǎn)觸摸冰沙。
??桌面版 Firefox 6+和 Chrome 也支持觸摸事件侨艾。
2. 手勢(shì)事件
??iOS 2.0 中的 Safari 還引入了一組手勢(shì)事件。當(dāng)兩個(gè)手指觸摸屏幕時(shí)就會(huì)產(chǎn)生手勢(shì)拓挥,手勢(shì)通常會(huì)改變顯示項(xiàng)的大小唠梨,或者旋轉(zhuǎn)顯示項(xiàng)。有三個(gè)手勢(shì)事件侥啤,分別介紹如下当叭。
- gesturestart:當(dāng)一個(gè)手指已經(jīng)按在屏幕上而另一個(gè)手指又觸摸屏幕時(shí)觸發(fā)。
- gesturechange:當(dāng)觸摸屏幕的任何一個(gè)手指的位置發(fā)生變化時(shí)觸發(fā)盖灸。
- gestureend:當(dāng)任何一個(gè)手指從屏幕上面移開時(shí)觸發(fā)蚁鳖。
??只有兩個(gè)手指都觸摸到事件的接收容器時(shí)才會(huì)觸發(fā)這些事件。在一個(gè)元素上設(shè)置事件處理程序赁炎,意味著兩個(gè)手指必須同時(shí)位于該元素的范圍之內(nèi)醉箕,才能觸發(fā)手勢(shì)事件(這個(gè)元素就是目標(biāo))。
??由于這些事件冒泡,所以將事件處理程序放在文檔上也可以處理所有手勢(shì)事件讥裤。此時(shí)放棒,事件的目標(biāo)就是兩個(gè)手指都位于其范圍內(nèi)的那個(gè)元素。
??觸摸事件和手勢(shì)事件之間存在某種關(guān)系坞琴。當(dāng)一個(gè)手指放在屏幕上時(shí)哨查,會(huì)觸發(fā) touchstart 事件。如果另一個(gè)手指又放在了屏幕上剧辐,則會(huì)先觸發(fā) gesturestart 事件寒亥,隨后觸發(fā)基于該手指的 touchstart 事件。如果一個(gè)或兩個(gè)手指在屏幕上滑動(dòng)荧关,將會(huì)觸發(fā) gesturechange 事件溉奕。但只要有一個(gè)手指移開,就會(huì)觸發(fā) gestureend 事件忍啤,緊接著又會(huì)觸發(fā)基于該手指的 touchend 事件加勤。
??與觸摸事件一樣,每個(gè)手勢(shì)事件的 event 對(duì)象都包含著標(biāo)準(zhǔn)的鼠標(biāo)事件屬性:bubbles同波、cancelable鳄梅、view、clientX未檩、clientY戴尸、screenX、screenY冤狡、detail孙蒙、altKey、shiftKey悲雳、ctrlKey 和 metaKey挎峦。此外,還包含兩個(gè)額外的屬性:rotation 和 scale合瓢。
??其中坦胶,rotation 屬性表示手指變化引起的旋轉(zhuǎn)角度,負(fù)值表示逆時(shí)針旋轉(zhuǎn)晴楔,正值表示順時(shí)針旋轉(zhuǎn)(該值從 0 開始)迁央。
??而 scale 屬性表示兩個(gè)手指間距離的變化情況(例如向內(nèi)收縮會(huì)縮短距離);這個(gè)值從 1 開始滥崩,并隨距離拉大而增長(zhǎng)岖圈,隨距離縮短而減小。
??下面是使用手勢(shì)事件的一個(gè)示例钙皮。
function handleGestureEvent(event){
var output = document.getElementById("output");
switch(event.type){
case "gesturestart":
output.innerHTML = "Gesture started (rotation=" + event.rotation + ",scale=" + event.scale + ")";
break;
case "gestureend":
output.innerHTML += "<br>Gesture ended (rotation=" + event.rotation + ",scale=" + event.scale + ")";
break;
case "gesturechange":
output.innerHTML += "<br>Gesture changed (rotation=" + event.rotation + ",scale=" + event.scale + ")";
break;
}
}
document.addEventListener("gesturestart", handleGestureEvent, false);
document.addEventListener("gestureend", handleGestureEvent, false);
document.addEventListener("gesturechange", handleGestureEvent, false);
??與前面演示觸摸事件的例子一樣蜂科,這里的代碼只是將每個(gè)事件都關(guān)聯(lián)到同一個(gè)函數(shù)中顽决,然后通過該函數(shù)輸出每個(gè)事件的相關(guān)信息。
??觸摸事件也會(huì)返回 rotation 和 scale 屬性导匣,但這兩個(gè)屬性只會(huì)在兩個(gè)手指與屏幕保持接觸時(shí)才會(huì)發(fā)生變化才菠。一般來(lái)說,使用基于兩個(gè)手指的手勢(shì)事件贡定,要比管理觸摸事件中的所有交互要容易得多赋访。
5、內(nèi)存和性能
??由于事件處理程序可以為現(xiàn)代 Web 應(yīng)用程序提供交互能力缓待,因此許多開發(fā)人員會(huì)不分青紅皂白地向頁(yè)面中添加大量的處理程序蚓耽。在創(chuàng)建 GUI 的語(yǔ)言(如 C#)中,為 GUI 中的每個(gè)按鈕添加一個(gè) onclick
事件處理程序是司空見慣的事旋炒,而且這樣做也不會(huì)導(dǎo)致什么問題步悠。
??可是在 JavaScript 中,添加到頁(yè)面上的事件處理程序數(shù)量將直接關(guān)系到頁(yè)面的整體運(yùn)行性能瘫镇。導(dǎo)致這一問題的原因是多方面的鼎兽。
??首先,每個(gè)函數(shù)都是對(duì)象铣除,都會(huì)占用內(nèi)存谚咬;內(nèi)存中的對(duì)象越多,性能就越差尚粘。
??其次择卦,必須事先指定所有事件處理程序而導(dǎo)致的 DOM 訪問次數(shù),會(huì)延遲整個(gè)頁(yè)面的交互就緒時(shí)間背苦。
??事實(shí)上互捌,從如何利用好事件處理程序的角度出發(fā)潘明,還是有一些方法能夠提升性能的行剂。
5.1、 事件委托
??對(duì)“事件處理程序過多”問題的解決方案就是事件委托钳降。事件委托利用了事件冒泡厚宰,只指定一個(gè)事件處理程序,就可以管理某一類型的所有事件遂填。
??例如铲觉,click 事件會(huì)一直冒泡到 document 層次。也就是說吓坚,我們可以為整個(gè)頁(yè)面指定一個(gè) onclick 事件處理程序撵幽,而不必給每個(gè)可單擊的元素分別添加事件處理程序。以下面的 HTML 代碼為例礁击。
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
??其中包含 3 個(gè)被單擊后會(huì)執(zhí)行操作的列表項(xiàng)盐杂。按照傳統(tǒng)的做法逗载,需要像下面這樣為它們添加 3 個(gè)事件處理程序。
var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");
EventUtil.addHandler(item1, "click", function(event){
location.;
});
EventUtil.addHandler(item2, "click", function(event){
document.title = "I changed the document's title";
});
EventUtil.addHandler(item3, "click", function(event){
alert("hi");
});
??如果在一個(gè)復(fù)雜的 Web 應(yīng)用程序中链烈,對(duì)所有可單擊的元素都采用這種方式厉斟,那么結(jié)果就會(huì)有數(shù)不清的代碼用于添加事件處理程序。
??此時(shí)强衡,可以利用事件委托技術(shù)解決這個(gè)問題擦秽。使用事件委托沃暗,只需在 DOM 樹中盡量最高的層次上添加一個(gè)事件處理程序谓松,如下面的例子所示。
var list = document.getElementById("myLinks");
EventUtil.addHandler(list, "click", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id){
case "doSomething":
document.title = "I changed the document's title";
break;
case "goSomewhere":
location.;
break;
case "sayHi":
alert("hi");
break;
}
});
??在上述代碼里冻辩,我們使用事件委托只為<ul>元素添加了一個(gè) onclick 事件處理程序锯七。由于所有列表項(xiàng)都是這個(gè)元素的子節(jié)點(diǎn)链快,而且它們的事件會(huì)冒泡,所以單擊事件最終會(huì)被這個(gè)函數(shù)處理眉尸。
??我們知道域蜗,事件目標(biāo)是被單擊的列表項(xiàng),故而可以通過檢測(cè) id 屬性來(lái)決定采取適當(dāng)?shù)牟僮鳌?br>
??與前面未使用事件委托的代碼比一比噪猾,會(huì)發(fā)現(xiàn)這段代碼的事前消耗更低霉祸,因?yàn)橹蝗〉昧艘粋€(gè) DOM 元素,只添加了一個(gè)事件處理程序袱蜡。
??雖然對(duì)用戶來(lái)說最終的結(jié)果相同丝蹭,但這種技術(shù)需要占用的內(nèi)存更少。所有用到按鈕的事件(多數(shù)鼠標(biāo)事件和鍵盤事件)都適合采用事件委托技術(shù)坪蚁。
??如果可行的話奔穿,也可以考慮為 document 對(duì)象添加一個(gè)事件處理程序,用以處理頁(yè)面上發(fā)生的某種特定類型的事件敏晤。這樣做與采取傳統(tǒng)的做法相比具有如下優(yōu)點(diǎn)贱田。
- document 對(duì)象很快就可以訪問,而且可以在頁(yè)面生命周期的任何時(shí)點(diǎn)上為它添加事件處理程序(無(wú)需等待 DOMContentLoaded 或 load 事件)嘴脾。換句話說男摧,只要可單擊的元素呈現(xiàn)在頁(yè)面上,就可以立即具備適當(dāng)?shù)墓δ堋?/li>
- 在頁(yè)面中設(shè)置事件處理程序所需的時(shí)間更少译打。只添加一個(gè)事件處理程序所需的 DOM 引用更少耗拓,所花的時(shí)間也更少。
- 整個(gè)頁(yè)面占用的內(nèi)存空間更少奏司,能夠提升整體性能乔询。
??最適合采用事件委托技術(shù)的事件包括 click、mousedown韵洋、mouseup竿刁、keydown岸夯、keyup 和 keypress。
??雖然 mouseover 和 mouseout 事件也冒泡们妥,但要適當(dāng)處理它們并不容易猜扮,而且經(jīng)常需要計(jì)算元素的位置。(因?yàn)楫?dāng)鼠標(biāo)從一個(gè)元素移到其子節(jié)點(diǎn)時(shí)监婶,或者當(dāng)鼠標(biāo)移出該元素時(shí)旅赢,都會(huì)觸發(fā) mouseout 事件。)
5.2惑惶、 移除事件處理程序
??每當(dāng)將事件處理程序指定給元素時(shí)煮盼,運(yùn)行中的瀏覽器代碼與支持頁(yè)面交互的 JavaScript 代碼之間就會(huì)建立一個(gè)連接。這種連接越多带污,頁(yè)面執(zhí)行起來(lái)就越慢僵控。如前所述,可以采用事件委托技術(shù)鱼冀,限制建立
的連接數(shù)量报破。另外,在不需要的時(shí)候移除事件處理程序千绪,也是解決這個(gè)問題的一種方案充易。
??內(nèi)存中留有那些過時(shí)不用的“空事件處理程序”(dangling event handler),也是造成 Web 應(yīng)用程序內(nèi)存與性能問題的主要原因荸型。在兩種情況下盹靴,可能會(huì)造成上述問題。
??第一種情況就是從文檔中移除帶有事件處理程序的元素時(shí)瑞妇。這可能是通過純粹的 DOM 操作稿静,例如使用 removeChild() 和 replaceChild() 方法,但更多地是發(fā)生在使用 innerHTML 替換頁(yè)面中某一部分的時(shí)候辕狰。
??如果帶有事件處理程序的元素被 innerHTML 刪除了改备,那么原來(lái)添加到元素中的事件處理程序極有可能無(wú)法被當(dāng)作垃圾回收。來(lái)看下面的例子柳琢。
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
// 先執(zhí)行某些操作
document.getElementById("myDiv").innerHTML = "Processing..."; // 麻煩了绍妨!
};
</script>
??這里润脸,有一個(gè)按鈕被包含在<div>元素中柬脸。為避免雙擊,單擊這個(gè)按鈕時(shí)就將按鈕移除并替換成一條消息毙驯;這是網(wǎng)站設(shè)計(jì)中非常流行的一種做法倒堕。
??但問題在于,當(dāng)按鈕被從頁(yè)面中移除時(shí)爆价,它還帶著一個(gè)事件處理程序呢垦巴。在<div>元素上設(shè)置 innerHTML 可以把按鈕移走媳搪,但事件處理程序仍然與按鈕保持著引用關(guān)系。
??有的瀏覽器(尤其是 IE)在這種情況下不會(huì)作出恰當(dāng)?shù)靥幚碇栊鼈兒苡锌赡軙?huì)將對(duì)元素和對(duì)事件處理程序的引用都保存在內(nèi)存中秦爆。
??如果你知道某個(gè)元素即將被移除,那么最好手工移除事件處理程序憔披,如下面的例子所示等限。
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
// 先執(zhí)行某些操作
btn.onclick = null; // 移除事件處理程序
document.getElementById("myDiv").innerHTML = "Processing...";
};
</script>
??在此,我們?cè)谠O(shè)置<div>的 innerHTML 屬性之前芬膝,先移除了按鈕的事件處理程序望门。這樣就確保了內(nèi)存可以被再次利用,而從 DOM 中移除按鈕也做到了干凈利索锰霜。
??注意筹误,在事件處理程序中刪除按鈕也能阻止事件冒泡。目標(biāo)元素在文檔中是事件冒泡的前提癣缅。
??采用事件委托也有助于解決這個(gè)問題厨剪。如果事先知道將來(lái)有可能使用innerHTML 替換掉頁(yè)面中的某一部分,那么就可以不直接把事件處理程序添加到該部分的元素中友存。而通過把事件處理程序指定給較高層次的元素丽惶,同樣能夠處理該區(qū)域中的事件。
??導(dǎo)致“空事件處理程序”的另一種情況爬立,就是卸載頁(yè)面的時(shí)候钾唬。毫不奇怪,IE8 及更早版本在這種情況下依然是問題最多的瀏覽器侠驯,盡管其他瀏覽器或多或少也有類似的問題抡秆。
??如果在頁(yè)面被卸載之前沒有清理干凈事件處理程序,那它們就會(huì)滯留在內(nèi)存中吟策。每次加載完頁(yè)面再卸載頁(yè)面時(shí)(可能是在兩個(gè)頁(yè)面間來(lái)回切換儒士,也可以是單擊了“刷新”按鈕),內(nèi)存中滯留的對(duì)象數(shù)目就會(huì)增加檩坚,因?yàn)槭录幚沓绦蛘加玫膬?nèi)存并沒有被釋放着撩。
??一般來(lái)說,最好的做法是在頁(yè)面卸載之前匾委,先通過 onunload 事件處理程序移除所有事件處理程序拖叙。在此,事件委托技術(shù)再次表現(xiàn)出它的優(yōu)勢(shì)——需要跟蹤的事件處理程序越少赂乐,移除它們就越容易薯鳍。對(duì)這種類似撤銷的操作,我們可以把它想象成:只要是通過 onload 事件處理程序添加的東西挨措,最后都要通過 onunload 事件處理程序?qū)⑺鼈円瞥?br> ??不要忘了挖滤,使用 onunload 事件處理程序意味著頁(yè)面不會(huì)被緩存在 bfcache 中崩溪。如果你在意這個(gè)問題,那么就只能在 IE 中通過 onunload 來(lái)移除事件處理程序了斩松。
6伶唯、 模擬事件
??事件,就是網(wǎng)頁(yè)中某個(gè)特別值得關(guān)注的瞬間惧盹。事件經(jīng)常由用戶操作或通過其他瀏覽器功能來(lái)觸發(fā)抵怎。
??但很少有人知道,也可以使用 JavaScript 在任意時(shí)刻來(lái)觸發(fā)特定的事件岭参,而此時(shí)的事件就如同瀏覽器創(chuàng)建的事件一樣反惕。也就是說噩凹,這些事件該冒泡還會(huì)冒泡犀暑,而且照樣能夠?qū)е聻g覽器執(zhí)行已經(jīng)指定的處理它們的事件處理程序篷角。
??在測(cè)試 Web 應(yīng)用程序记舆,模擬觸發(fā)事件是一種極其有用的技術(shù)涨薪。DOM2 級(jí)規(guī)范為此規(guī)定了模擬特定事件的方式芯肤,IE9潜支、Opera蒸矛、Firefox娄徊、Chrome 和 Safari 都支持這種方式闽颇。IE 有它自己模擬事件的方式。
6.1寄锐、 DOM 中的事件模擬
??可以在 document 對(duì)象上使用 createEvent() 方法創(chuàng)建 event 對(duì)象兵多。這個(gè)方法接收一個(gè)參數(shù),即表示要?jiǎng)?chuàng)建的事件類型的字符串橄仆。
??在 DOM2 級(jí)中剩膘,所有這些字符串都使用英文復(fù)數(shù)形式,而在 DOM3級(jí)中都變成了單數(shù)盆顾。這個(gè)字符串可以是下列幾字符串之一怠褐。
- UIEvents:一般化的 UI 事件。鼠標(biāo)事件和鍵盤事件都繼承自 UI 事件您宪。DOM3 級(jí)中是 UIEvent奈懒。
- MouseEvents:一般化的鼠標(biāo)事件。DOM3 級(jí)中是 MouseEvent宪巨。
- MutationEvents:一般化的 DOM 變動(dòng)事件磷杏。DOM3 級(jí)中是 MutationEvent。
- HTMLEvents:一般化的 HTML 事件揖铜。沒有對(duì)應(yīng)的 DOM3 級(jí)事件(HTML 事件被分散到其他類別中)茴丰。
??要注意的是达皿,“DOM2 級(jí)事件”并沒有專門規(guī)定鍵盤事件天吓,后來(lái)的“DOM3 級(jí)事件”中才正式將其作為一種事件給出規(guī)定贿肩。IE9 是目前唯一支持 DOM3 級(jí)鍵盤事件的瀏覽器。不過龄寞,在其他瀏覽器中汰规,在現(xiàn)有方法的基礎(chǔ)上,可以通過幾種方式來(lái)模擬鍵盤事件物邑。
??在創(chuàng)建了 event 對(duì)象之后溜哮,還需要使用與事件有關(guān)的信息對(duì)其進(jìn)行初始化。每種類型的 event 對(duì)象都有一個(gè)特殊的方法色解,為它傳入適當(dāng)?shù)臄?shù)據(jù)就可以初始化該 event 對(duì)象茂嗓。不同類型的這個(gè)方法的名字也不相同,具體要取決于 createEvent() 中使用的參數(shù)科阎。
??模擬事件的最后一步就是觸發(fā)事件述吸。這一步需要使用 dispatchEvent() 方法,所有支持事件的 DOM 節(jié)點(diǎn)都支持這個(gè)方法锣笨。調(diào)用 dispatchEvent() 方法時(shí)蝌矛,需要傳入一個(gè)參數(shù),即表示要觸發(fā)事件的 event 對(duì)象错英。觸發(fā)事件之后入撒,該事件就躋身“官方事件”之列了,因而能夠照樣冒泡并引發(fā)相應(yīng)事件處理程序的執(zhí)行椭岩。
1. 模擬鼠標(biāo)事件
??創(chuàng)建新的鼠標(biāo)事件對(duì)象并為其指定必要的信息茅逮,就可以模擬鼠標(biāo)事件。創(chuàng)建鼠標(biāo)事件對(duì)象的方法是為 createEvent() 傳入字符串"MouseEvents"判哥。返回的對(duì)象有一個(gè)名為 initMouseEvent() 方法氮唯,用于指定與該鼠標(biāo)事件有關(guān)的信息。這個(gè)方法接收 15 個(gè)參數(shù)姨伟,分別與鼠標(biāo)事件中每個(gè)典型的屬性一一對(duì)應(yīng)惩琉;這些參數(shù)的含義如下。
- type(字符串):表示要觸發(fā)的事件類型夺荒,例如"click"瞒渠。
- bubbles(布爾值):表示事件是否應(yīng)該冒泡。為精確地模擬鼠標(biāo)事件技扼,應(yīng)該把這個(gè)參數(shù)設(shè)置為 true伍玖。
- cancelable(布爾值):表示事件是否可以取消。為精確地模擬鼠標(biāo)事件剿吻,應(yīng)該把這個(gè)參數(shù)設(shè)置為 true窍箍。
- view(AbstractView):與事件關(guān)聯(lián)的視圖。這個(gè)參數(shù)幾乎總是要設(shè)置為 document.defaultView。
- detail(整數(shù)):與事件有關(guān)的詳細(xì)信息椰棘。這個(gè)值一般只有事件處理程序使用纺棺,但通常都設(shè)置為 0。
- screenX(整數(shù)):事件相對(duì)于屏幕的 X 坐標(biāo)邪狞。
- screenY(整數(shù)):事件相對(duì)于屏幕的 Y 坐標(biāo)祷蝌。
- clientX(整數(shù)):事件相對(duì)于視口的 X 坐標(biāo)。
- clientY(整數(shù)):事件想對(duì)于視口的 Y 坐標(biāo)帆卓。
- ctrlKey(布爾值):表示是否按下了 Ctrl 鍵巨朦。默認(rèn)值為 false。
- altKey(布爾值):表示是否按下了 Alt 鍵剑令。默認(rèn)值為 false糊啡。
- shiftKey(布爾值):表示是否按下了 Shift 鍵。默認(rèn)值為 false吁津。
- metaKey(布爾值):表示是否按下了 Meta 鍵悔橄。默認(rèn)值為 false。
- button(整數(shù)):表示按下了哪一個(gè)鼠標(biāo)鍵腺毫。默認(rèn)值為 0癣疟。
- relatedTarget(對(duì)象):表示與事件相關(guān)的對(duì)象。這個(gè)參數(shù)只在模擬mouseover 或 mouseout時(shí)使用潮酒。
??顯而易見睛挚,initMouseEvent() 方法的這些參數(shù)是與鼠標(biāo)事件的 event 對(duì)象所包含的屬性一一對(duì)應(yīng)的。其中急黎,前 4 個(gè)參數(shù)對(duì)正確地激發(fā)事件至關(guān)重要扎狱,因?yàn)闉g覽器要用到這些參數(shù);而剩下的所有參數(shù)只有在事件處理程序中才會(huì)用到勃教。
??當(dāng)把 event 對(duì)象傳給 dispatchEvent() 方法時(shí)淤击,這個(gè)對(duì)象的 target 屬性會(huì)自動(dòng)設(shè)置。下面故源,我們就通過一個(gè)例子來(lái)了解如何模擬對(duì)按鈕的單擊事件污抬。
var btn = document.getElementById("myBtn");
// 創(chuàng)建事件對(duì)象
var event = document.createEvent("MouseEvents");
// 初始化事件對(duì)象
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
// 觸發(fā)事件
btn.dispatchEvent(event);
??在兼容 DOM 的瀏覽器中,也可以通過相同的方式來(lái)模擬其他鼠標(biāo)事件(例如 dblclick)绳军。
2. 模擬鍵盤事件
??前面曾經(jīng)提到過印机,“DOM2 級(jí)事件”中沒有就鍵盤事件作出規(guī)定,因此模擬鍵盤事件并沒有現(xiàn)成的思路可循门驾。
??“DOM2 級(jí)事件”的草案中本來(lái)包含了鍵盤事件射赛,但在定稿之前又被刪除了;Firefox 根據(jù)其草案實(shí)現(xiàn)了鍵盤事件奶是。需要提請(qǐng)大家注意的是楣责,“DOM3 級(jí)事件”中的鍵盤事件與曾包含在“DOM2 級(jí)事件”草案中的鍵盤事件有很大區(qū)別竣灌。
??DOM3 級(jí)規(guī)定,調(diào)用 createEvent() 并入"KeyboardEvent"就可以創(chuàng)建一個(gè)鍵盤事件秆麸。返回的事件對(duì)象會(huì)包含一個(gè) initKeyEvent() 方法初嘹,這個(gè)方法接收下列參數(shù)。
- type(字符串):表示要觸發(fā)的事件類型蛔屹,如"keydown"削樊。
- bubbles(布爾值):表示事件是否應(yīng)該冒泡豁生。為精確模擬鍵盤事件兔毒,應(yīng)該設(shè)置為 true。
- cancelable(布爾值):表示事件是否可以取消甸箱。為精確模擬鍵盤事件育叁,應(yīng)該設(shè)置為 true。
- view(AbstractView):與事件關(guān)聯(lián)的視圖芍殖。這個(gè)參數(shù)幾乎總是要設(shè)置為 document.defaultView豪嗽。
- key(布爾值):表示按下的鍵的鍵碼。
- location(整數(shù)):表示按下了哪里的鍵豌骏。0 表示默認(rèn)的主鍵盤,1 表示左,2 表示右柑贞,3 表示數(shù)字鍵盤掖棉,4 表示移動(dòng)設(shè)備(即虛擬鍵盤),5 表示手柄蒂窒。
- modifiers(字符串):空格分隔的修改鍵列表躁倒,如"Shift"。
- repeat(整數(shù)):在一行中按了這個(gè)鍵多少次洒琢。
??由于 DOM3 級(jí)不提倡使用 keypress 事件秧秉,因此只能利用這種技術(shù)來(lái)模擬 keydown 和 keyup 事件。
var textbox = document.getElementById("myTextbox"),
event;
// 以 DOM3 級(jí)方式創(chuàng)建事件對(duì)象
if (document.implementation.hasFeature("KeyboardEvents", "3.0")){
event = document.createEvent("KeyboardEvent");
// 初始化事件對(duì)象
event.initKeyboardEvent("keydown", true, true, document.defaultView, "a", 0, "Shift", 0);
}
// 觸發(fā)事件
textbox.dispatchEvent(event);
??上述例子模擬的是按住 Shift 的同時(shí)又按下 A 鍵衰抑。在使用 document.createEvent("KeyboardEvent")之前象迎,應(yīng)該先檢測(cè)瀏覽器是否支持 DOM3 級(jí)事件;其他瀏覽器返回一個(gè)非標(biāo)準(zhǔn)的 KeyboardEvent 對(duì)象呛踊。
??在 Firefox 中挖帘,調(diào)用 createEvent() 并傳入"KeyEvents"就可以創(chuàng)建一個(gè)鍵盤事件。返回的事件對(duì)象會(huì)包含一個(gè) initKeyEvent()方法恋技,這個(gè)方法接受下列 10 個(gè)參數(shù)拇舀。
- type(字符串):表示要觸發(fā)的事件類型,如"keydown"蜻底。
- bubbles(布爾值):表示事件是否應(yīng)該冒泡骄崩。為精確模擬鼠標(biāo)事件聘鳞,應(yīng)該設(shè)置為 true。
- cancelable(布爾值):表示事件是否可以取消要拂。為精確模擬鼠標(biāo)事件抠璃,應(yīng)該設(shè)置為 true。
- view(AbstractView):與事件關(guān)聯(lián)的視圖脱惰。這個(gè)參數(shù)幾乎總是要設(shè)置為 document.defaultView搏嗡。
- ctrlKey(布爾值):表示是否按下了 Ctrl 鍵。默認(rèn)值為 false拉一。
- altKey(布爾值):表示是否按下了 Alt 鍵采盒。默認(rèn)值為 false。
- shiftKey(布爾值):表示是否按下了 Shift 鍵蔚润。默認(rèn)值為 false磅氨。
- metaKey(布爾值):表示是否按下了 Meta 鍵。默認(rèn)值為 false嫡纠。
- keyCode(整數(shù)):被按下或釋放的鍵的鍵碼烦租。這個(gè)參數(shù)對(duì) keydown 和 keyup 事件有用,默認(rèn)值為 0除盏。
- charCode(整數(shù)):通過按鍵生成的字符的 ASCII 編碼叉橱。這個(gè)參數(shù)對(duì) keypress 事件有用,默認(rèn)值為 0者蠕。
??將創(chuàng)建的 event 對(duì)象傳入到 dispatchEvent() 方法就可以觸發(fā)鍵盤事件窃祝,如下面的例子所示。
// 只適用于 Firefox
var textbox = document.getElementById("myTextbox");
// 創(chuàng)建事件對(duì)象
var event = document.createEvent("KeyEvents");
// 初始化事件對(duì)象
event.initKeyEvent("keypress", true, true, document.defaultView, false, false, false, false, 65, 65);
// 觸發(fā)事件
textbox.dispatchEvent(event);
??在 Firefox 中運(yùn)行上面的代碼蠢棱,會(huì)在指定的文本框中輸入字母 A锌杀。同樣,也可以依此模擬 keyup 和 keydown 事件泻仙。
??在其他瀏覽器中糕再,則需要?jiǎng)?chuàng)建一個(gè)通用的事件,然后再向事件對(duì)象中添加鍵盤事件特有的信息玉转。例如:
var textbox = document.getElementById("myTextbox");
// 創(chuàng)建事件對(duì)象
var event = document.createEvent("Events");
// 初始化事件對(duì)象
event.initEvent(type, bubbles, cancelable);
event.view = document.defaultView;
event.altKey = false;
event.ctrlKey = false;
event.shiftKey = false;
event.metaKey = false;
event.keyCode = 65;
event.charCode = 65;
// 觸發(fā)事件
textbox.dispatchEvent(event);
??以上代碼首先創(chuàng)建了一個(gè)通用事件突想,然后調(diào)用 initEvent() 對(duì)其進(jìn)行初始化,最后又為其添加了鍵盤事件的具體信息究抓。
??在此必須要使用通用事件猾担,而不能使用 UI 事件,因?yàn)?UI 事件不允許向 event 對(duì)象中再添加新屬性(Safari 除外)刺下。像這樣模擬事件雖然會(huì)觸發(fā)鍵盤事件绑嘹,但卻不會(huì)向文本框中寫入文本,這是由于無(wú)法精確模擬鍵盤事件所造成的橘茉。
3. 模擬其他事件
??雖然鼠標(biāo)事件和鍵盤事件是在瀏覽器中最經(jīng)常模擬的事件工腋,但有時(shí)候同樣需要模擬變動(dòng)事件和 HTML 事件姨丈。要模擬變動(dòng)事件,可以使用 createEvent("MutationEvents") 創(chuàng)建一個(gè)包含 initMutationEvent() 方法的變動(dòng)事件對(duì)象擅腰。這個(gè)方法接受的參數(shù)包括:type蟋恬、bubbles、cancelable趁冈、relatedNode歼争、preValue、newValue渗勘、attrName 和 attrChange沐绒。下面來(lái)看一個(gè)模擬變動(dòng)事件的例子。
var event = document.createEvent("MutationEvents");
event.initMutationEvent("DOMNodeInserted", true, false, someNode, "","","",0);
target.dispatchEvent(event);
??以上代碼模擬了 DOMNodeInserted 事件呀邢。其他變動(dòng)事件也都可以照這個(gè)樣子來(lái)模擬洒沦,只要改一改參數(shù)就可以了豹绪。
??要模擬 HTML 事件价淌,同樣需要先創(chuàng)建一個(gè) event 對(duì)象——通過 createEvent("HTMLEvents"),然后再使用這個(gè)對(duì)象的 initEvent() 方法來(lái)初始化它即可瞒津,如下面的例子所示蝉衣。
var event = document.createEvent("HTMLEvents");
event.initEvent("focus", true, false);
target.dispatchEvent(event);
??這個(gè)例子展示了如何在給定目標(biāo)上模擬 focus 事件。模擬其他 HTML 事件的方法也是這樣巷蚪。
??瀏覽器中很少使用變動(dòng)事件和 HTML 事件病毡,因?yàn)槭褂盟鼈儠?huì)受到一些限制。
4. 自定義 DOM 事件
??DOM3 級(jí)還定義了“自定義事件”屁柏。自定義事件不是由 DOM 原生觸發(fā)的啦膜,它的目的是讓開發(fā)人員創(chuàng)建自己的事件。
??要?jiǎng)?chuàng)建新的自定義事件淌喻,可以調(diào)用 createEvent("CustomEvent")僧家。返回的對(duì)象有一個(gè)名為 initCustomEvent() 的方法,接收如下 4 個(gè)參數(shù)裸删。
- type(字符串):觸發(fā)的事件類型八拱,例如"keydown"。
- bubbles(布爾值):表示事件是否應(yīng)該冒泡涯塔。
- cancelable(布爾值):表示事件是否可以取消肌稻。
- detail(對(duì)象):任意值,保存在 event 對(duì)象的 detail 屬性中匕荸。
??可以像分派其他事件一樣在 DOM 中分派創(chuàng)建的自定義事件對(duì)象爹谭。例如:
var div = document.getElementById("myDiv"),
event;
EventUtil.addHandler(div, "myevent", function(event){
alert("DIV: " + event.detail);
});
EventUtil.addHandler(document, "myevent", function(event){
alert("DOCUMENT: " + event.detail);
});
if (document.implementation.hasFeature("CustomEvents", "3.0")){
event = document.createEvent("CustomEvent");
event.initCustomEvent("myevent", true, false, "Hello world!");
div.dispatchEvent(event);
}
??上述例子創(chuàng)建了一個(gè)冒泡事件"myevent"。而 event.detail 的值被設(shè)置成了一個(gè)簡(jiǎn)單的字符串榛搔,然后在<div>元素和 document 上偵聽這個(gè)事件诺凡。
??因?yàn)?initCustomEvent() 方法已經(jīng)指定這個(gè)事件應(yīng)該冒泡齿风,所以瀏覽器會(huì)負(fù)責(zé)將事件向上冒泡到 document。
??支持自定義 DOM 事件的瀏覽器有 IE9+和 Firefox 6+绑洛。
6.2救斑、 IE 中的事件模擬
??在 IE8 及之前版本中模擬事件與在 DOM 中模擬事件的思路相似:先創(chuàng)建 event 對(duì)象,然后為其指定相應(yīng)的信息真屯,然后再使用該對(duì)象來(lái)觸發(fā)事件脸候。當(dāng)然,IE 在實(shí)現(xiàn)每個(gè)步驟時(shí)都采用了不一樣的方式绑蔫。
??調(diào)用 document.createEventObject() 方法可以在 IE 中創(chuàng)建 event 對(duì)象运沦。但與 DOM 方式不同的是,這個(gè)方法不接受參數(shù)配深,結(jié)果會(huì)返回一個(gè)通用的 event 對(duì)象携添。
??然后,你必須手工為這個(gè)對(duì)象添加所有必要的信息(沒有方法來(lái)輔助完成這一步驟)篓叶。
??最后一步就是在目標(biāo)上調(diào)用 fireEvent() 方法烈掠,這個(gè)方法接受兩個(gè)參數(shù):事件處理程序的名稱和 event 對(duì)象。
??在調(diào)用 fireEvent()方法時(shí)缸托,會(huì)自動(dòng)為 event 對(duì)象添加 srcElement 和 type 屬性左敌;其他屬性則都是必須通過手工添加的。換句話說俐镐,模擬任何 IE 支持的事件都采用相同的模式矫限。例如,下面的代碼模擬了在一個(gè)按鈕上觸發(fā) click 事件過程佩抹。
var btn = document.getElementById("myBtn");
// 創(chuàng)建事件對(duì)象
var event = document.createEventObject();
// 初始化事件對(duì)象
event.screenX = 100;
event.screenY = 0;
event.clientX = 0;
event.clientY = 0;
event.ctrlKey = false;
event.altKey = false;
event.shiftKey = false;
event.button = 0;
// 觸發(fā)事件
btn.fireEvent("onclick", event);
??上述例子先創(chuàng)建了一個(gè) event 對(duì)象叼风,然后又用一些信息對(duì)其進(jìn)行了初始化。注意棍苹,這里可以為對(duì)象隨意添加屬性无宿,不會(huì)有任何限制——即使添加的屬性 IE8 及更早版本并不支持也無(wú)所謂。在此添加的屬性對(duì)事件沒有什么影響,因?yàn)橹挥惺录幚沓绦虿艜?huì)用到它們。
??采用相同的模式也可以模擬觸發(fā) keypress 事件钞它,如下面的例子所示。
var textbox = document.getElementById("myTextbox");
// 創(chuàng)建事件對(duì)象
var event = document.createEventObject();
// 初始化事件對(duì)象
event.altKey = false;
event.ctrlKey = false;
event.shiftKey = false;
event.keyCode = 65;
// 觸發(fā)事件
textbox.fireEvent("onkeypress", event);
??由于鼠標(biāo)事件梭灿、鍵盤事件以及其他事件的 event 對(duì)象并沒有什么不同,所以可以使用通用對(duì)象來(lái)觸發(fā)任何類型的事件冰悠。
??不過堡妒,正如在DOM中模擬鍵盤事件一樣,運(yùn)行這個(gè)例子也不會(huì)因模擬了keypress 而在文本框中看到任何字符溉卓,即使觸發(fā)了事件處理程序也沒有用皮迟。
小結(jié)
??事件是將 JavaScript 與網(wǎng)頁(yè)聯(lián)系在一起的主要式搬泥。“DOM3 級(jí)事件”規(guī)范和 HTML5 定義了常見的大多數(shù)事件伏尼。即使有規(guī)范定義了基本事件忿檩,但很多瀏覽器仍然在規(guī)范之外實(shí)現(xiàn)了自己的專有事件,從而為開發(fā)人員提供更多掌握用戶交互的手段爆阶。
??有些專有事件與特定設(shè)備關(guān)聯(lián)燥透,例如移動(dòng) Safari 中的 orientationchange 事件就是特定關(guān)聯(lián) iOS 設(shè)備的。
??在使用事件時(shí)辨图,需要考慮如下一些內(nèi)存與性能方面的問題班套。
- 有必要限制一個(gè)頁(yè)面中事件處理程序的數(shù)量,數(shù)量太多會(huì)導(dǎo)致占用大量?jī)?nèi)存故河,而且也會(huì)讓用戶感覺頁(yè)面反應(yīng)不夠靈敏吱韭。
- 建立在事件冒泡機(jī)制之上的事件委托技術(shù),可以有效地減少事件處理程序的數(shù)量鱼的。
- 建議在瀏覽器卸載頁(yè)面之前移除頁(yè)面中的所有事件處理程序理盆。
??可以使用 JavaScript 在瀏覽器中模擬事件≡“DOM2 級(jí)事件”和“DOM3 級(jí)事件”規(guī)范規(guī)定了模擬事件的方法熏挎,為模擬各種有定義的事件提供了方便速勇。此外晌砾,通過組合使用一些技術(shù),還可以在某種程度上模擬鍵盤事件烦磁。IE8 及之前版本同樣支持事件模擬养匈,只不過模擬的過程有些差異。
??事件是 JavaScript 中最重要的主題之一都伪,深入理解事件的工作機(jī)制以及它們對(duì)性能的影響至關(guān)重要呕乎。