avaScript與HTML之間的交互是通過事件實現的。事件,就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。可以使用偵聽器(或處理程序)來預定義事件,以便事件發生時執行相應的代碼。這種在傳統軟件工程中被稱為觀察者模式的模型,支持頁面的行為與頁面的外觀之間的松散耦合。
事件最早是在IE3和NetscapeNavigator 2中出現的,當時是作為分擔服務器運算負載的一種手段。在IE4和Navigator4發布時,這兩種瀏覽器都提供了相似但不相同的API,這些API并存經過了好幾個主要版本。DOM2級規范開始嘗試以一種符合邏輯的方式來標準化DOM事件。IE9、Firefox、Opera、Safari和Chrome全都實現了"DOM2級事件"模塊的核心部分。IE8是最后一個仍然使用其專有事件系統的主要瀏覽器。
事件流
當瀏覽器發展到第四代時,瀏覽器開發團隊遇到了一個很有意思的問題:頁面的哪一部分會擁有某個特定的事件?要明白這個問題問的是什么,可以想象畫在一張紙上的一組同心圓。如果把手指放在圓心上,那么指向的不是一個圓,而是紙上的所有圓。兩家公司的瀏覽器開發團隊在看待瀏覽器事件方面還是一致的。如果單擊了某個按鈕,都認為單擊事件不僅僅發生在按鈕上。也就是說,單擊按鈕的同時,也單擊了按鈕的容器元素,甚至也單擊了整個頁面。
事件流描述的是從頁面中接收事件的順序。但有意思的是,IE和Netscape開發團隊居然提出了差不多是完全相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕獲流。
事件冒泡
IE的事件流叫做事件冒泡,即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,然后逐級向上傳播到較為不具體的節點(文檔)。所有現代瀏覽器都支持事件冒泡,但在具體實現上還是有一些差別。IE5.5及更早版本中的事件冒泡會跳過<html>元素(從<body>直接跳到document)。IE9、Firefox、Chrome和Safari則將事件一直冒泡到window對象。
事件捕獲
NetscapeCommunicator團隊提出的另一種事件流叫做事件捕獲。事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最后接收到事件。事件捕獲的用以在于在事件到達預定目標之前捕獲它。雖然事件捕獲是Netscape Communicator唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox目前也支持這種事件流模型。盡管"DOM2級事件"規范要求事件應該從document對象開始傳播,但這些瀏覽器都是從window對象開始捕獲事件的。
DOM事件流
"DOM2級事件"規定的事件流包括三個階段:事件捕獲階段、處于目標階段和事件冒泡階段。首先發生的是事件捕獲,為截獲事件提供了機會。然后是實際的目標接收到事件。最后一個階段是冒泡階段,可以在這個階段對事件做出響應。
事件處理程序
事件就是用戶或瀏覽器自身執行的某種動作。諸如click、load和mouseover,嗾使事件的名字。而響應某個事件的函數就叫做事件處理程序(或事件偵聽器)。事件處理程序的名字以"on"開頭,因此click事件的事件處理程序就是onclick、load事件的事件處理程序就是onload。為事件指定處理程序的方式有好幾種。
HTML事件處理程序
某個元素支持的每種事件,都可以使用一個與相應事件處理程序同名的HTML特性來指定。這個特性的值應該是能夠執行的JavaScript代碼。例如,要在按鈕被單擊時執行一些JavaScript,可以像下面這樣編寫代碼:
<input type="button"onclick="alert('Clicked')" value="Click!"/>
當單擊這個按鈕時,就會顯示一個警告框。這個操作是通過指定onclick特性并將一些JavaScript代碼作為它的值來定義的。由于這個值是JavaScript,因此不能在其中使用未經轉義的HTML語法字符,例如和號(&)、雙引號("")、小于號(<)或大于號(>)。為了避免使用HTML實體,這里使用了單引號。如果想要使用雙引號,那么既要將代碼改成如下所示:
<input type="button"onclick="alert("Clicked")" value="Click!"/>
在HTML中定義的事件處理程序可以包含要執行的具體動作,也可以調用在頁面其他地方定義的腳本。如下面的例子所示:
<script type="text/javascript">
function showMessage(){
alert("Hello World!");
}
</script>
<input type="button"onclick="showMessage()"value="Click!"/>
在這個例子中,單擊按鈕就會調用showMessage()函數。這個函數是在一個獨立的<script>元素中定義的,當然也可以被包含在一個外部文件中。事件處理程序中的代碼在執行時,有權訪問全局作用域中的任何代碼。
這樣指定事件處理程序具有一些獨到之處。首先,這樣會創建一個封裝著元素屬性值的函數。這個函數中有一個局部變量event,也就是事件對象。
<!--輸出"click" -->
<input type="button"onclick="alert(event.type)" value="Click!">
通過event變量,可以直接訪問事件對象,不用自己定義它,也不用從函數的參數列表中讀取。在這個函數內部,this值等于事件的目標元素。
<!--輸出"Click!" -->
<input type="button"onclick="alert(this.value)" value="Click!">
關于這個動態創建的函數,另一個有意思的地方是它擴展作用域的方式。在這個函數內部,可以像訪問局部變量一樣訪問document及該元素本身的成員。這個函數使用with像下面這樣擴展作用域:
function funcName(){
with(document){
with(this){
//元素屬性值
}
}
}
如此一來,事件處理程序要訪問自己的屬性就簡單多了。下面這行代碼與前面的例子效果相同:
<!--輸出"Click!" -->
<input type="button"onclick="alert(value)"value="Click!">
如果當前元素是一個表單輸入元素,則作用域中還會包含表單元素(父元素)的入口,這個函數就變成了如下所示:
function funcName(){
with(this.form){
with(this){
//元素屬性值
}
}
}
實際上,這樣擴展作用域的方式無非就是想讓事件處理程序無需引用表單元素就能訪問其他表單字段。例如:
<form method="post">
<input type="text"name="username_" value="">
<input type="button"value="Echo Username" onclick="alert(username_.value)">
</form>
在這個例子中,單擊按鈕會顯示文本框中的文本,值得注意的是,這里直接引用了username_元素。不過,在HTML中指定事件處理程序有兩個缺點。首先,存在一個時差問題。因為用戶可能會在HTML元素一出現在頁面上就觸發相應的事件,但當時的事件處理程序可能尚不舉杯執行條件。以前面的例子來說明,假設showMessage()函數是在按鈕下方、頁面的最底部定義的。如果用戶在頁面解析showMessage()函數之前就點擊了按鈕,就會引發錯誤。為此,很多HTML事件處理程序就會被封裝在try-catch塊中,以便錯誤不會浮出水面。
另一個缺點是,這樣擴展時間處理程序的作用域鏈在不同瀏覽器中會導致不同結果。不同JavaScript引擎遵循的標識符解析規則略有差異,很可能在訪問非限定對象成員時出錯。
通過HTML指定時間處理程序的最后一個缺點是HTML與JavaScript代碼緊密耦合。如果要更換事件處理程序,就要改動兩個地方:HTML代碼和JavaScript代碼。而這也是許多開發人員摒棄HTML事件處理程序,轉而使用JavaScript指定時間處理程序的原因所在。
DOM0 級事件處理程序
通過JavaScript指定事件處理程序的傳統方式,就是將一個函數賦值給一個時間處理程序屬性。這種為事件處理程序賦值的方法是在第四代Web瀏覽器中出現的,而且至今仍然為所有現代瀏覽器所支持。原因一是簡單,二是具有跨瀏覽器的優勢。要使用JavaScript指定事件處理程序,首先必須取得一個要操作的對象的引用。
每個元素(包括window和document)都有自己的事件處理程序屬性,這些屬性通常全部小寫,例如onclick。將這種屬性的值設置為一個函數,就可以指定事件處理程序,如:
varbtn=document.getElementById("myBtn");
btn.onclick=function(){
alert("Clicked");
}
在此,我們通過文檔對象取得了一個按鈕的引用,然后為它指定了onclick事件處理程序。但要注意,在這些代碼運行以前不會指定事件處理程序,因此如果這些代碼在頁面中位于按鈕后面,就有可能在一段時間內怎么單擊都沒有反應。
使用DOM 0 級方法指定的事件處理程序被認為是元素的方法。因此,這時候的事件處理程序是在元素的作用域中運行;換句話說,程序中的this引用當前元素。可以在事件處理程序中通過this訪問元素的任何屬性和方法。以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。
也可刪除通過DOM0級方法指定的事件處理程序,只要像下面這樣將時間處理程序屬性的值設置為null即可:
btn.onclick=null;//刪除事件處理程序
如果使用HTML指定事件處理程序,那么onclick屬性的值就是一個包含著在同名HTML特性中指定的代碼的函數。而將相應的屬性設置為null,也可以刪除以這種方式指定的事件處理程序。
DOM2級事件處理程序
"DOM2級事件"定義了兩個方法,用于處理指定和刪除事件處理程序的操作:addEventListener()和removeEventListener()。所有DOM節點中都包含這兩個方法,并且它們都接受3個參數:要處理的事件名、作為時間處理程序的函數和一個布爾值。最后這個布爾值參數如果是true,表示在捕獲階段調用事件處理程序;如果是false,表示在冒泡階段調用事件處理程序。
要在click事件添加事件處理程序,可以使用下列代碼:
varbtn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
上面的代碼為一個按鈕添加了onclick事件處理程序,而且該事件會在冒泡階段被觸發(因為最后一個參數是false)。與DOM0級方法一樣,這里添加的事件處理程序也是在其依附的元素的作用域中運行。使用DOM2級方法添加事件處理程序的主要好處是可以添加多個事件處理程序。如:
varbtn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
btn.addEventListener("click",function(){
alert("Hello World");
},false);
這里為按鈕添加了兩個事件處理程序。這兩個事件處理程序會按照添加它們的順序觸發,因此首先會顯示元素的ID,其次會顯示"Hello World"消息。
通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除,移除時傳入的參數與添加時使用的參數相同。這也意味著通過addEventListener()添加的匿名函數將無法移除,如:
varbtn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
btn.removeEventListener("click",function(){//沒有用!
alert(this.id);
},false);
在這個例子中,使用addEventListener()添加了一個事件處理程序。雖然調用removeEventListener()時看似使用了相同的參數,但實際上,第二個參數與傳入addEventListener()中的那一個是完全不同的函數。而傳入removeEventListener()中的事件處理程序函數必須與傳入addEventListener()中的相同,如:
var handler = function(){
alert(this.id);
}
varbtn=document.getElementById("myBtn");
btn.addEventListener("click",handler,false);
btn.removeEventListener("click",handler,false);//有效
重寫后的例子沒有問題,是因為在addEventListener()和removeEventListener()中使用了相同的函數。
大多數情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器。最好只在需要在事件達到目標之前截獲它的時候將事件處理程序添加到捕獲階段。如果不是特別需要,不建議在事件捕獲階段注冊事件處理程序。
IE事件處理程序
IE實現了與DOM中類似的兩個方法:attachEvent()和detachEvent()。這兩個方法接受相同的兩個參數:事件處理程序名稱與事件處理程序函數。由于IE8及更早版本只支持事件冒泡,所以通過attachEvent()添加的事件處理程序都會被添加到冒泡階段。
要使用attachEvent()為按鈕添加一個事件處理程序,可以使用以下代碼。
varbtn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("Clicked");
});
注意,attachEvent()的第一個參數是"onclicke",而非DOM的addEventListener()方法中的"click"。
在IE中使用attachEvent()與使用DOM0級方法的主要區別在于事件處理程序的作用域。在使用DOM0級方法的情況下,事件處理程序會在其所屬元素的作用域內運行;在使用attachEvent()方法的情況下,事件處理程序會在全局作用域中運行,因此this等于window。在編寫跨瀏覽器的代碼時,牢記這一區別非常重要。
與addEventListener()類似,attachEvent()方法也可以用來為一個元素添加多個事件處理程序。如:
varbtn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("Clicked");
});
btn.attachEvent("onclick",function(){
alert("Hello World");
});
這里,調用了兩次attachEvent(),為同一個按鈕添加了兩個不同的事件處理程序。不過,與DOM方法不同的是,這些事件處理程序不是以添加它們的順序執行,而是以相反的順序被觸發。單擊這個例子中的按鈕,首先看到的是"Hello World",然后才是"Clicked"。
使用attachEvent()添加的事件可以通過detachEvent()來移除,條件是必須提供相同的參數。
IE11中需要使用addEventListener()和removeEventListener()。
跨瀏覽器的事件處理程序
為了以跨瀏覽器的方式處理事件,不少開發人員會使用能夠隔離瀏覽器差異的JavaScript庫,還有一些開發人員會自己開發最合適的事件處理的方法。自己編寫代碼其實也不難,只要恰當地使用能力檢測即可。要保證處理事件的代碼能在大多數瀏覽器下一致地運行,只需關注冒泡階段。
第一個要創建的方法是addHandler(),它的職責是視情況分別使用DOM0級方法、DOM2級方法或IE方法來添加事件。這個方法屬于一個叫EventUtil的對象。addHandler()方法接受3個參數:要操作的元素、事件名稱和事件處理程序函數。
與addHandler()對應的方法是removeHandler(),它也接受相同的參數。這個方法的職責是移除之前添加的事件處理程序——無論事件程序是采用什么方式添加到元素中的,如果其他方法無效,默認采用DOM0級方法。
var EventUtil={
addHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.addEventListener){
element.addEventListener(type.substring(2),handler,false);
}else if(element.attachEvent){
element.attachEvent(type,handler);
}else{
element[type]=handler;
}
},
removeHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.removeEventListener){
element.removeEventListener(type.substring(2),handler,false);
}else if(element.detachEvent){
element.detachEvent(type,handler);
}else{
element[type]=null;
}
}
};
這兩個方法首先都會檢測傳入的元素是否存在DOM2級方法。如果存在DOM2級方法,則使用該方法,其次是IE方法,最后是DOM0級方法。無論傳入的事件名稱是DOM類型的還是IE中的,都會自動匹配成適合的而且會進行小寫轉換。使用上面方法時先獲取到element,定義好handler事件傳入對應的方法中即可。
事件對象
在觸發DOM上的某個事件時,會產生一個事件對象event,這個對象中包含著所有與事件有關的信息。包括導致事件的元素、事件的類型以及其他與特定事件相關的信息。例如,鼠標操作導致的事件對象中,會包含鼠標位置的信息,而鍵盤操作導致的事件對象中,會包含與按下的鍵有關的信息。所有瀏覽器都支持event對象,但支持方式不同。
DOM中的事件對象
兼容DOM的瀏覽器會將一個event對象傳入到事件處理程序中。無論指定事件處理程序時使用什么方法(DOM0級或DOM2級),都會傳入event對象。event對象包含與創建它的特定事件有關的屬性和方法。觸發的事件類型不一樣,可用的屬性和方法也不一樣。不過,所有事件都會有下表列出的成員。
屬性/方法類型讀/寫說明bubblesBoolean只讀表明事件是否冒泡cancelableBoolean只讀表名是否可以取消事件的默認行為currentTargetElement只讀其事件處理程序當前正在處理事件的那個元素defaultPreventedBoolean只讀為true表示已經調用了preventDefault()detailInteger只讀與事件相關的細節信息eventPhaseInteger只讀調用事件處理程序的階段:1事件捕獲,2處于目標,3冒泡階段preventDefault()Function只讀取消事件的默認行為。如果cancelable為true,可以使用這個方法stopImmediatePropagation()Function只讀取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程序被調用。stopPropagation()Function只讀取消事件的進一步捕獲或冒泡。如果bubbles為true,則可以使用這個方法targetElement只讀事件的目標trustedBoolean只讀為true表示事件是瀏覽器生成的,false表示事件是由開發人員通過JavaScript創建的typeString只讀被觸發的事件的類型viewAbstractView只讀與事件關聯的抽象視圖。等同于發生事件的window對象在事件處理程序內部,對象this始終等于currentTarget的值,而target則只包含事件的實際目標。如果直接將事件處理程序指定給了目標元素,則this、currentTarget和target包含相同的值。
在需要通過一個函數處理多個事件時,可以使用type屬性。下面使用事件對象做一個例子,模擬購物網站中鼠標在圖片上時顯示該圖片的局部放大圖,點擊該圖片時,在新的網頁中顯示該圖片,需要做一些準備:id為kitty的背景圖(beautykitty.jpg)的像素大小為該div的大小,將該圖片復制一份(beautykittybig.jpg),并調整大小為原圖片寬高x4。然后就可以實驗下面的例子了。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample Page</title>
</head>
<body>
<table>
<tr>
<td>
<div id='kitty' style=";">
<div id="SQ" style="position: relative;background:#2196F3;;"></div>
</div>
</td>
<td>
<div id='bigImg' style=";"></div>
</td>
</tr>
</table>
</body>
<script type="text/javascript">
var EventUtil={
addHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.addEventListener){
element.addEventListener(type.substring(2),handler,false);
}else if(element.attachEvent){
element.attachEvent(type,handler);
}else{
element[type]=handler;
}
},
removeHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.removeEventListener){
element.removeEventListener(type.substring(2),handler,false);
}else if(element.detachEvent){
element.detachEvent(type,handler);
}else{
element[type]=null;
}
}
};
varkitty=document.getElementById('kitty');
var clickHandler=function(){
window.open("http://localhost:8080/demo/fiels/beautykitty.jpg","HellKitty");
}
varmousemoveHandler=function(){
var offsetX=(event.offsetX)*4;
var offsetY=(event.offsetY)*4;
var SQStyle=document.getElementById("SQ").style;
SQStyle.visibility="visible";
SQStyle.top=event.offsetY + "px";
SQStyle.left=event.offsetX + "px";
document.getElementById("bigImg").style.background="url('../fiels/beautykittybig.jpg')-"+offsetX+"px -"+offsetY+"px";
}
varmouseoutHandler=function(){
document.getElementById("bigImg").style.background="gray";
}
EventUtil.addHandler(kitty,"onmousemove",mousemoveHandler);
EventUtil.addHandler(kitty,"onclick",clickHandler);
EventUtil.addHandler(kitty,"onmouseout",mouseoutHandler);
</script>
</html>
做出來的效果就是:
要阻止事件的默認行為,可以使用preventDefault()方法。例如,連接的默認行為就是在被單擊時會導航到其href特性指定的URL。如果項阻止連接導航這一默認行為,那么通過連接的onclick事件處理程序就可以取消它,如:
varlink=document.getElementById('myLink');
link.onclick=function(){
if(new Date().getHours() > 15){
event.preventDefault();
}
}
只有cancelable屬性設置為true的事件,才可以使用preventDefault()來取消其默認行為。
另外,stopPropagation()方法用于立即停止事件在DOM層次中的傳播,即取消進一步的事件捕獲或冒泡。例如,直接添加到一個按鈕的事件處理程序可以調用stopPropagation(),從而避免觸發注冊在document.body上面的事件處理程序,如:
varlink=document.getElementById('myLink');
link.onclick=function(){
alert('Link clicked!');
event.preventDefault();
event.stopPropagation();
}
document.body.onclick=function(){
alert("Body clicked!");
}
對于這個例子而言,如果步調用stopPropagation(),就會在單擊連接時出現兩個警告框。調用stopPropagation()后click事件就不會傳播到document.body,因此就不會觸發注冊在這個元素上的onclick事件處理程序。
事件對象的eventPhase屬性,可以用來確定事件當前位于事件流的哪個階段。如果是在捕獲階段調用的事件處理程序,那么eventPhase等于1;如果事件處理程序處于目標對象上,則eventPhase等于2;如果是在冒泡階段調用的事件處理程序,eventPhase等于3。這里要注意的是,盡管"處于目標"發生在冒泡階段,但eventPhase仍然一直等于2。
事件類型
Web瀏覽器中可能發生的事件有很多類型。如前所述,不同的事件類型具有不同的信息,而"DOM3級事件"規定了以下幾類事件。
UI(UserInterface,用戶界面)事件,當用戶與頁面上的元素交互時觸發;
焦點事件,當元素獲得或失去焦點時觸發;
鼠標事件,當用戶通過鼠標在頁面上執行操作時觸發;
滾輪事件,當使用鼠標滾輪(或類似設備)時觸發;
文本事件,當在文檔中輸入文本時觸發;
鍵盤事件,當用戶通過鍵盤在頁面上執行操作時觸發;
合成事件,當為IME(Input MethodEditor,輸入法編輯器)輸入字符時觸發;
變動事件,當底層DOM結構發生變化時觸發;
變動名稱事件,當元素或屬性名稱變動時觸發。此類事件已經廢棄,沒有任何瀏覽器實現它們。
除了這幾類事件之外,HTML5也定義了一組事件,而有些瀏覽器還會在DOM和BOM中實現其他專有事件。這些專有的事件一般都是根據開發人員需求定制的,沒有什么規范,因此不用瀏覽器的實現有可能也不一致。
DOM3級事件模塊在DOM2級事件模塊基礎上重新定義了這些事件,也添加了一些新事件。包括IE9+在內的所有主流瀏覽器都支持DOM2級事件。IE9+也支持了DOM3級事件。
UI事件
UI事件指的是那些不一定與用戶操作有關的事件。這些事件在DOM規范出現之前,都是以這種或那種形式存在的,而在DOM規范中保留是為了向后兼容。現有的UI事件如下。
DOMActivate:表示元素已經被用戶操作(通過鼠標或鍵盤)激活。這個事件在DOM3級事件中被廢棄,但Firefox2+和Chrome支持它。考慮到不同瀏覽器實現的差異,不建議使用這個事件。
load:當頁面完全加載后在window上面觸發,當所有框架都加載完畢時在框架集上面觸發,當圖像加載完畢時在<img>元素上觸發,或者當嵌入的內容加載完畢時在<object>元素上面觸發。
unload:當頁面完全卸載后在window上面觸發,當所有框架都卸載后在框架集上面觸發,或者當嵌入的內容卸載完畢后在<object>元素上面觸發。
abort:在用戶停止下載過程時,如果嵌入的內容沒有加載完,則在<object>元素上面觸發。
error:當發生JavaScript錯誤時window上面觸發,當無法加載如想時在<img>元素上面觸發,當無法加載嵌入內容時在<object>元素上面觸發,或者當有一或多geese框架無法加載時在框架集上面觸發。
select:當用戶選擇文本框(<input>或<textarea>)中的一或多個字符時觸發。
resize:當窗口或框架的大小變化時在window或框架上面觸發。
scroll:當用戶滾動帶滾動條的元素中的內容時,在該元素上面觸發。<body>元素中包含加載頁面的滾動條。
多數這些事件都與window對象或表單控件相關。
處理DOMActivate之外,其他事件在DOM2級事件中都歸為HTML事件(DOMActivate在DOM2級事件中仍然屬于UI事件)。要確定瀏覽器是否支持DOM2級事件規定的HTML事件,可以使用如下代碼:
var isSupported =document.implementation.hasFeature("UIEvent","2.0");
注意,只有根據"DOM2級事件"實現這些事件的瀏覽器才會返回true。而以非標準方式支持這些事件的瀏覽器則會返回false。要確定瀏覽器是否支持"DOM3級事件"定義的事件,可以使用如下代碼:
var isSupported = document.implementation.hasFeature("UIEvent","3.0");
load事件
JavaScript中最常用的一個事件就是load。當頁面完全加載后(包括所有圖像、JavaScript文件、CSS文件等外部資源),就會觸發window上面的load事件。有兩種定義onload事件處理程序的方式。第一種方式是使用如下的JavaScript代碼:
EventUtil.addHandler(window,"load", function(event){
alert("Loaded!");
});
這是通過JavaScript來指定事件處理程序的方式,使用的前面定義的跨瀏覽器的EventUtil對象。與添加其他事件一樣,這里也給事件處理程序傳入了一個event對象。這個event對象中不包含有關這個事件的任何附加信息,但在兼容DOM的瀏覽器中,event.target屬性的值會被設置為document。
第二種指定onload事件處理程序的方式是為<body>元素添加一個onload特性。一般來說,在window上面發生的任何事件都可以在<body/>元素中通過相應的特性來指定,因為在HTML中無法訪問window元素。實際上,這只是為了保證向后兼容的一種權宜之計,但所有瀏覽器都能很好地支持這種方式,建議盡可能使用JavaScript方式。
圖像上面也可以觸發load事件,無論是在DOM中的圖像元素還是HTML中的圖像元素。因此可以在HTML中為任何圖像指定onload事件處理程序,例如:
<img src="smile.gif" onload="alert('Imageloaded.')">
這樣,當例子中的圖像加載完畢后就會顯示一個警告框。同樣的功能還可以使用JavaScript來實現。
還有一些元素也以非標準的方式支持load事件。在IE9+、Firefox、Opera、Chrome和Safari3+及更高版本中,<script>元素也會觸發load事件,以便開發人員確定動態加載的JavaScript文件是否加載完畢。與圖像不同,只有在設置了<script>元素的src屬性并將該元素添加到文檔后,才會開始下載JavaScript文件。換句話說,對于<script>元素而言,指定src屬性和指定事件處理程序的先后順序就不重要了。
unload事件
與load事件對應的是unload事件,這個事件在文檔被完全卸載后觸發。只要用戶從一個頁面切換到另一個頁面,就會發生unload事件。而利用這個事件最多的情況是清除引用,以避免內存泄漏。
resize事件
當瀏覽器窗口被調整到一個新的高度或寬度時,就會觸發resize事件。這個事件在window(窗口)上面觸發,因此可以通過JavaScript或者<body>元素中的onresize特性來指定事件處理程序。
關于何時會觸發resize事件,不同的瀏覽器有不同的機制。IE、Safari、Chrome和Opera會在瀏覽器窗口變化了1像素時就觸發resize事件,然后隨著變化不斷重復觸發,應該注意不要在這個事件的處理程序中加入大計算量的代碼,因為這些代碼可能被頻繁執行,從而導致瀏覽器反應明顯變慢。瀏覽器窗口最小化或最大化時也會觸發resize事件。
scroll事件
雖然scroll事件是在window對象上發生的,但它實際表示的則是頁面中相應元素的變化。在混雜模式下,可以通過<body>元素的scrollLeft和scrollTop來監控到這一變化;而在標準模式下,除Safari之外的所有瀏覽器都會通過<html>元素來反映這一變化(Safari仍然基于<body>跟蹤滾動位置)。
與resize事件類似,scroll事件也會在文檔被滾動期間重復觸發,所以有必要盡量保持事件處理程序的代碼簡單。
焦點事件
焦點事件會在頁面獲得或失去焦點時觸發。利用這些事件并與document.hasFocus()方法及document.activeElement屬性配合,可以知曉用戶在頁面上的行蹤。有以下6個焦點事件。
blur:在元素失去焦點時觸發。這個事件不會冒泡;所有瀏覽器都支持它。
DOMFocusIn:在元素獲得焦點時觸發。這個事件與HTML事件focus等價,但它冒泡。只有Opera支持這個事件。DOM3級事件廢棄了DOMFocusIn,選擇了focusin。
DomFocusOut:在元素失去焦點時觸發。這個事件是HTML事件blur的通用版本。只有Opera支持這個事件。DOM3級事件廢棄了DomFocusOut,選擇了focusout。
focus:在元素獲得焦點時觸發。這個事件不會冒泡;所有瀏覽器都支持它。
focusin:在元素獲得焦點時觸發。這個事件與HTML事件focus等價,但它冒泡。
focusout:在元素失去焦點時觸發。這個事件是HTML事件blur的通用版本。
這一類事件中最主要的兩個是focus和blur,它們都是JavaScript早期就得到所有瀏覽器支持的事件。這些事件的最發問題是它們不冒泡。因此,IE的focusin和focusout與Opera的DOMFocusIn和DomFocusOut才會發生重疊。IE的方式最后被DOM3級事件采納為標準方式。
當從頁面中的一個元素移動到另一個元素,會依次觸發下列事件:
(1) focusout在失去焦點的元素上觸發;
(2) focusin在獲得焦點的元素上觸發;
(3) blur在失去焦點的元素上觸發;
(4) DomFocusOut在數去焦點的元素上觸發;
(5) focus在獲得焦點的元素上觸發;
(6) DOMFocusIn在獲得焦點的元素上觸發。
其中,blur、DomFocusOut和focusout的事件目標是失去焦點的元素;而focus、DOMFocusIn和focusin的事件目標是獲得焦點的元素。
要確定瀏覽器是否支持這些事件,可以使用如下代碼:
var isSupported =document.implementation.hasFeature("FocusEvent","3.0");
鼠標與滾輪事件
鼠標事件是Web開發中最常用的一類事件,畢竟鼠標還是最主要的定位設備。DOM3級事件中定義了9個鼠標事件:
click:在用戶單擊鼠標按鈕(一般是左邊的按鈕)或按下回車鍵時觸發。這一點對確保易訪問性很重要,意味著onclick事件處理程序既可以通過鍵盤也可以通過鼠標執行。
dbclick:在用戶雙擊主鼠標按鈕(一般是左邊的按鈕)時觸發。從技術上說,這個事件并不是DOM2級事件規范中規定的,但鑒于它得到了廣泛支持,所以DOM3級事件將其納入了標準。
mousedown:在用戶按下了任意鼠標按鈕時觸發。不能通過鍵盤觸發這個事件。
mouseenter:在鼠標從元素外部首次移動到元素范圍之內時觸發。這個事件不冒泡,而且在光標移動到后代元素上不會觸發。DOM2級事件并沒有定義這個事件,但DOM3級事件將它納入了規范。
mouseleave:在位于元素上方的鼠標光標移動到元素范圍之外時觸發。這個事件不冒泡,而且在光標移動到后代元素上不會觸發。DOM2級事件并沒有定義這個事件,但DOM3級事件將它納入了規范。
mousemove:當鼠標指針在元素內部移動時重復地觸發。不能通過鍵盤觸發這個事件。
mouseout:在鼠標指針位于一個元素上方,然后用戶將其一如另一個元素時觸發。又移入的另一個元素可能位于前一個元素的外部,也可能是這個元素的子元素。不能通過鍵盤觸發這個事件。
mouseover:在鼠標指針位于一個元素外部,然后用戶將其首次一如另一個元素邊界之內時觸發。不能通過鍵盤觸發這個事件。
mouseup:在用戶釋放鼠標按鈕時觸發。不能通過鍵盤觸發這個事件。
頁面上的所有元素都支持鼠標事件。除了mouseenter和mouseleave,所有鼠標事件都會冒泡,也可以被取消,而取消鼠標事件將會影響瀏覽器的默認行為。取消鼠標事件的默認行為還會影響其他事件,因為鼠標事件與其他事件是密不可分的關系。
只有在一個元素上相繼觸發mousedown和mouseup事件,才會觸發click事件;如果mousedown和mouseup中的一個被取消,就不會觸發click事件。類似地,只有觸發兩次click事件才會觸發依次dbclick事件。如果有代碼阻止了連續兩次觸發click事件(可能是直接取消click事件,也可能通過mousedown或mouseup間接實現),那么就不會觸發dbclick事件了。在onclick事件中出現了alert語句時也會阻止觸發dbclick事件,如下面的dbclick事件處理程序用于不會得到執行:
<button onclick="alert('Click')" ondblclick="alert('DBClick')">點擊</button>
這4個事件觸發的順序始終如下:
(1) mousedown
(2) mouseup
(3) click
(4) mousedown
(5) mouseup
(6) click
(7) dbclick
顯然,click和dbclick事件都會依賴與其他事件的觸發;然而mousedown和mouseup則不受其他事件的影響。
使用以下代碼可以檢測瀏覽器是否支持以上DOM2級事件(除dbclick、mouseenter和mouseleave之外):
var isSupported = document.implementation.hasFeature("MouseEvents","2.0");
要檢測瀏覽器是否支持上面的所有事件,可以使用以下代碼:
var isSupported =document.implementation.hasFeature("MouseEvent","3.0");
注意,DOM3級事件的feature名是"MouseEvent",而非"MouseEvents"
客戶區坐標位置
鼠標事件都是在瀏覽器視口的特定位置上發生的。這個位置信息保存在事件對象的clientX和clientY屬性中。所有瀏覽器都支持兩個屬性,它們的值表示事件發生時鼠標在視口中的水平和垂直坐標。
可以使用類似下列代碼取得鼠標事件的客戶坐標信息,可在頁面中看到隨著鼠標的移動,坐標的顯示跟隨變化:
<body style="background-color:antiqueWhite;height:1000px">
<label id="position"style="">(000,000)</label>
</body>
<script type="text/javascript">
var EventUtil={
addHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.addEventListener){
element.addEventListener(type.substring(2),handler,false);
}else if(element.attachEvent){
element.attachEvent(type,handler);
}else{
element[type]=handler;
}
},
removeHandler:function(element,type,handler){
type=type.toLowerCase();
type=type.substring(0,2)=="on"?type:"on"+type;
if(element.removeEventListener){
element.removeEventListener(type.substring(2),handler,false);
}else if(element.detachEvent){
element.detachEvent(type,handler);
}else{
element[type]=null;
}
}
};
var label =document.getElementById("position");
var showCoordinate= function(event){
var position="(" + event.clientX + " , " + event.clientY + ")";
label.innerText=position;
label.style.paddingTop=(event.clientY-20)+"px";
label.style.paddingLeft=(event.clientX-50)+"px";
}
EventUtil.addHandler(document,"onmousemove",showCoordinate);
</script>
頁面坐標位置
通過客戶區坐標能夠知道鼠標是在視口中什么位置,而頁面坐標通過事件對象的pageX和pageY屬性,能夠告訴你事件是在頁面中什么位置發生的。換句話說,這兩個屬性表示鼠標光標在頁面中的位置,因此坐標是從頁面本身而非視口的左邊和定邊計算的。
在頁面沒有滾動的情況下,pageX和pageY的值與clientX和clientY的值相等。
IE8及更早版本不支持事件對象上的頁面坐標,不過使用客戶區坐標和滾動信息可以計算出來。這個時候需要用到document.body(混雜模式)或document.documentElement(標準模式)中的scrollLeft和scrollTop屬性。
屏幕坐標位置
鼠標事件發生時,不僅會有相對于瀏覽器窗口的位置,還有一個相對于整個電腦屏幕的位置。而通過screenX和screenY屬性就可以確定鼠標事件發生時鼠標指針相對于整個屏幕的坐標信息。
修改鍵
雖然鼠標事件主要是使用鼠標來觸發的,但在按下鼠標時鍵盤上某些鍵的狀態也可以影響到索要采取的操作。這些修改鍵就是Shfit、Ctrl、Alt和Meat(在Windows鍵盤中是Windows鍵,在蘋果機中是Cmd鍵),它們經常被用來修改鼠標事件的行為。DOM為此規定了4個屬性,表示這些修改鍵的狀態:shfitKey、ctrlKey、altKey和metaKey。這些屬性中包含的都是布爾值,如果相應的鍵被按下了,則值為true,否則為false。當某個鼠標事件發生時,通過檢測這幾個屬性就可以確定用戶是否同時按下了其中的鍵。如:
EventUtil.addHandler(btn,"click",function(event){
var keys=new Array();
if(event.shiftKey){
keys.push("shfit");
}
if(event.ctrlKey){
keys.push("ctrl");
}
if(event.altKey){
keys.push("alt");
}
if(event.metaKey){
keys.push("meta");
}
console.log("keys:" + keys.join(","));
});
相關元素
在發生mouseover和mouseout事件時,還會涉及更多的元素。這兩個事件都會涉及把鼠標指針從一個元素的邊界之內移動到另一個元素的邊界之內。對mouseover事件而言,事件的主要目標是獲得光標的元素,而相關元素就是哪個失去光標的元素。類似地,對mouseout事件而言,事件的主要目標是失去光標的元素,而相關元素則是光標的元素。
鼠標按鈕
只有在主鼠標按鈕被單擊(或鍵盤回車被按下)時才會觸發click事件,因此檢測按鈕的信息并不是必要的。但對于mousedown和mouseup事件來說,則在其event對象存在一個button屬性,表示按下或釋放的按鈕。DOM的button屬性可能有如下3個值:0表示主鼠標按鈕,1表示中間的鼠標滾輪,2表示次鼠標按鈕。在常規的設置中,主鼠標按鈕就是鼠標左鍵,而次鼠標按鈕就是鼠標右鍵。
更多的事件信息
"DOM2級事件"規范在event對象中還提供了detail屬性,用于給出有關事件的更多信息。對于鼠標事件來說,detail中包含了一個數值,表示在給定位置上發生了多少次單擊。在同一個像素上相繼地發生一次mousedown和一次mouseup事件算作一次單擊。details屬性從1開始計數,每次單擊發生后都會遞增。如果鼠標在mousedown和mouseup之間移動了位置,則details會被重置為0。
鼠標滾輪事件
IE6.0首先實現了mousewheel事件。此后,Opera、Chrome和Safari也都實現了這個事件。當用戶通過鼠標滾輪與頁面交互、在垂直方向上滾動頁面時(無論是向上還是向下),就會觸發mousewheel事件。這個事件可以在任何元素上面觸發,最終會冒泡到document(IE8)或window(IE9、Opera、Chrome及Safari)對象。與mousewheel事件對應的event對象除包含鼠標事件的所有標準信息外,還包含一個特殊的wheelDelta屬性。當用戶向前滾動鼠標滾輪時,wheelDelta是120的倍數;當向后時,是-120的倍數。
鍵盤與文本事件
用戶在使用鍵盤時會觸發鍵盤事件。"DOM2"級事件最初規定了鍵盤事件,但在最終定稿之前又刪除了相應的內容。結果,對鍵盤事件的支持主要遵循的是DOM0級。
"DOM3級事件"為鍵盤事件制定了規范,有3個鍵盤事件:
keydown:當用戶按下鍵盤上的任意鍵時觸發,而且如果按住不放的話,會重復觸發此事件。
keypress:當用戶按下鍵盤上的字符鍵時觸發,而且按住不放的話,會重復觸發此事件。按下Esc鍵也會觸發這個事件。
keyup:當用戶釋放鍵盤上的鍵時觸發。
雖然所有元素都支持以上3個事件,但只有在用戶通過文本框輸入文本時才最常用到。
只有一個文本事件:textInput。這個事件是對keypress的補充,用以是在將文本顯示給用戶之前更容易攔截文本。在文本插入文本框之前會觸發textInput事件。
在用戶按了一下鍵盤上的字符鍵時,首先會觸發keydown事件,然后緊跟著是keypress事件,最后會觸發keyup事件。其中keydown和keypress都是在文本框發生變化之前被觸發的;而keyup事件則是在文本框已經發生變化之后被觸發的。如果用戶按下了一個字符鍵不放,就會重復觸發keydown和keypress事件,知道用戶松開該鍵為止。
如果用戶按下的是一個非字符鍵,那么首先會觸發keydown事件,然后就是keyup事件。如果按住這個非字符鍵不放,那么就會一直重復觸發keydown事件,直到用戶松開這個鍵,此時會觸發keyup事件。
鍵盤事件與鼠標事件一樣,都支持相同的修改鍵。而且,鍵盤事件的事件對象中也有shfitKey、ctrlKey、altKey和metaKey屬性。IE8及低版本不支持metaKey。
鍵碼
在發生keydown和keyup事件時,event對象的keyCode屬性中會包含一個代碼,與鍵盤上一個特定的鍵對應。對數字字母字符鍵,keyCode屬性的值與ASCII碼中對應小寫字母或數字的編碼相同。因此,數字鍵7的keyCode值為55,而字母A鍵的keyCode值為65——與Shfit的狀態無關。DOM和IE的event對象都支持keyCode屬性。無論keydown或keyup事件都會存在一些特殊情況。在Firefox和Opera中,按分號鍵時keyCode值為59,也就是ASCII分號中的編碼;但IE和Safari返回186,即鍵盤中按鍵的鍵碼。
字符編碼
發生keypress事件意味著按下的鍵會影響到屏幕中文本的顯示。在所有瀏覽器中,按下能夠插入或刪除的鍵都會觸發keypress事件;按下其他鍵能夠觸發此事件因瀏覽器而異。
IE9、Firefox、Chrome和Safari的event對象都支持一個charCode屬性,這個屬性只有在發生keypress事件時才包含值,而且這個值是按下的那個鍵所代表字符的ASCII編碼。此時的keyCode通常等于0或者也有可能所按鍵的鍵碼。IE8及之前版本和Opera則是在keyCode中保存字符的ASCII編碼。要想以跨瀏覽器的方式取得字符編碼,首先必須檢測charCode屬性是否可用,如果不可用則使用keyCode,如下所示:
var EventUtil={
//……
getCharCode:function(event){
if(typeof event.charCode == "number"){
return event.charCode;
}else{
return event.keyCode;
}
}
//……
};
這個方法首先檢測charCode屬性是否包含數值(在不支持這個屬性的瀏覽器中,值為undefined),如果是返回該值,否則返回keyCode屬性值。在取得了字符編碼之后,就可以使用String.fromCharCode()將其轉換成實際的字符。
DOM3級變化
盡管所有瀏覽器都實現了某種形式的鍵盤事件,DOM3級事件還是做出了一些改變。比如,DOM3級事件中的鍵盤事件,不再包含charCode屬性,而是包含兩個新屬性:key和char。
其中,key屬性是為了取代keyCode而新增的,它的值是一個字符串。在按下某個字符鍵時,key的值就是相應的文本字符(如"k"或"M");在按下非字符鍵時,key的值是相應鍵的名(如"Shift"或"Down")。而char屬性在按下字符鍵時的行為與key相同,但在按下非字符鍵時值為null。
IE9支持key屬性,但不支持char屬性。Safari5和Chrome支持名為keyIdentifier的屬性,在按下非字符鍵(例如Shfit)的情況下與key的值相同。對于字符鍵,keyIdentifier返回一個格式類似"U+0000"的字符串,表示Unicode值。
textInput事件
"DOM3級事件"規范中引入了一個新事件,名叫textInput。根據規范,當用戶在可編輯區域中輸入字符時,就會觸發這個事件。這個用于替代keypress的textInput事件的行為稍微有不同。區別之一就是任何可以獲得焦點的元素都可以觸發keypress時間愛你,但只有可編輯區才能觸發textInput事件。區別之二是textInput事件只會在用戶按下能夠輸入實際字符的鍵時才會被觸發,而keypress事件則在按下哪些能夠影響文本顯示的鍵時也會觸發(例如退格鍵)。
由于textInput事件主要考慮的是字符,因此它的event對象中還包含一個data屬性,這個屬性的值就是用戶輸入的字符(而非字符編碼)。換句話說,用戶在沒有按上檔鍵的情況下按下了S鍵,data的值就是"s",而如果在按住上檔鍵時按下該鍵,data的值就是"S"。
復合事件
復合事件是DOM3級事件中新添加的一類事件,用于處理IME的輸入序列。IME(Input Method Editor,輸入法編輯器)可以讓用戶輸入在物理鍵盤上找不到的字符。例如,使用拉丁文鍵盤的用戶通過IME照樣能輸入日文字符。IME通常需要同時按住多個見,但最終只輸入一個字符。復合事件就是針對檢測和處理這種輸入而設計的。有以下三種復合事件。
compositionstart:在IME的文本復合系統打開時觸發,表示要開始輸入了。
compositionupdate:在向輸入字段中插入新字符時觸發。
compositionend:在IME的文本復合系統關閉時觸發,表示返回正常鍵盤輸入狀態。
復合事件與文本事件在很多方面都很相似。在觸發復合事件時,目標是接收文本的輸入字段。但它比文本事件的事件對象多一個屬性data,其中包含以下幾個值中的一個:
如果在compositionstart事件發生時訪問,包含正在編輯的文本(例如,已經選中的需要馬上替換的文本);
如果在compositionupdate事件發生時訪問,包含正插入的新字符;
如果在compositionend事件發生時訪問,包含此次輸入會話中插入的所有字符。
到2016年,大多數的主流瀏覽器(Safari除外)都已支持復合事件,要確定瀏覽器是否支持復合事件,可以使用以下代碼:
var isSupported=document.implementation.hasFeature("CompositionEvent","3.0");
變動事件
DOM2級的變動事件能在DOM中的某一部分發生變化時給出提示。變動事件是為XML或HTML DOM設計的,并不特定于某種語言。DOM2級定義了如下變動事件。
DOMSubtreeModified:在DOM結構中發生任何變化時觸發。這個事件在其他任何事件觸發后都會觸發。
DOMNodeInserted:在一個節點作為子節點被插入到另一個節點中時觸發。
DOMNodeRemoved:在節點從其父節點中被移除時觸發。
DOMNodeInsertedIntoDocument:在一個節點被直接插入文檔或通過子樹直接插入文檔之后觸發。這個事件在DOMNodeInserted之后觸發。
DOMNodeRemovedFromDocument:在一個節點被直接從文檔中移除或通過子樹間接從文檔中移除之前觸發。這個事件在DOMNodeRemoved之后觸發。
DOMAttrModified:在特性被修改之后觸發。
DOMCharacterDataModified:在文本節點的值發生變化時觸發。
使用下列代碼可以檢測出瀏覽器是否支持變動事件:
var isSupported=document.implementation.hasFeature("MutationEvents","2.0");
刪除節點
在使用removeChild()或replaceChild()從DOM中刪除節點時,首先會觸發DOMNodeRemoved事件。這個事件的目標(event.target)是被刪除的節點,而event.relatedNode屬性中包含著對目標節點父節點的引用。在這個事件觸發時,節點尚未從其父節點刪除,因此其parentNode屬性仍然指向父節點(與event.relatedNode相同)。這個事件會冒泡,因而可以在DOM的任何層次上面處理它。
如果被移除的子節點包含子節點,那么在其所有子節點及這個被移除的節點上會相繼觸發DOMNodeRemovedFromDocument事件。這個事件不會冒泡,所以只有直接指定給其中一個子節點的事件處理程序才會被調用。這個事件的目標是相應的子節點或者那個被移除的節點,除此之外event對象中不包含其他信息。
緊隨其后觸發的是DOMSubtreeModified事件。這個事件的目標是移除節點的父節點;此時的event對象也不會提供與事件相關的其他信息。
插入節點
在使用appendChild()、replaceChild()或insertBefore()向DOM中插入節點時,相繼觸發的事件為:DOMNodeInserted(冒泡)、DOMNodeInsertedIntoDocument(不冒泡)、DomSubtreeModified。
HTML5事件
DOM規范中沒有涵蓋所有瀏覽器支持的所有事件。很多瀏覽器出于不同的目的——滿足用戶需求或解決特殊問題,還實現了一些自定義的事件。HTML5詳盡列出了瀏覽器應該支持的所有事件。
contextmenu事件
表示合適應該顯示上下文菜單,以便開發人員取消默認的上下文菜單而提供自定義的菜單。該事件冒泡。在所有瀏覽器中都可以取消這個事件:在兼容DOM的瀏覽器中,使用event.preventDefault();在IE中,將event.returnValue的值設置為false。
Beforeunload事件
之所以有發生在window對象上的beforeunload事件,是為了讓開發人員有可能在頁面卸載前阻止這一操作。這個事件會在瀏覽器卸載頁面之前觸發,可以通過它來取消卸載并繼續使用原有頁面。但是不能徹底取消這個事件,因為那就相當于讓用戶無法離開當前頁面了。為此,這個事件的意圖是將控制權交給用戶,顯示的消息會告知用戶頁面將被卸載,詢問用戶是否真的要關閉頁面,還是希望繼續留下來。類似事件的應用場景很多,比如有些電腦程序退出時會詢問是否退出,網頁游戲在關閉網頁時會詢問是否再多玩一會……
DOMContentLoaded事件
如前所述,window的load事件會在頁面中的一切都加載完畢時觸發,但這個過程可能會因為要加載的外部資源過多而頗費周折。而DOMContentLoaded事件則在形成完整的DOM樹之后就會觸發,不理會圖像、JavaScript文件、CSS文件或其他資源是否已經下載完畢。與load事件不同,DOMContentLoaded支持在頁面下載的早起添加事件處理程序,這也就意味著用戶能夠盡早地與頁面進行交互。
要處理DOMContentLoaded事件,可以為document或window添加相應的事件處理程序(盡管這個事件會冒泡到window,但它的實際目標實際上是document)。DOMContentLoaded事件對象不會提供任何額外的信息。IE9+、Firefox、Chrome、Safari3.0+和Opera9+都支持DOMContentLoaded事件,通常這個事件既可以添加事件處理程序,也可以執行其他DOM操作。這個事件始終都會在load事件之前觸發。
內存和性能
為頁面添加大量的處理程序將降低頁面的整體運行性能。導致這一問題的原因是多方面的。首先,每個函數都是對象,都會占用內存;內存中的對象越多,性能就越差。其次,必須事先指定所有事件處理程序而導致的DOM訪問次數,會延遲整個頁面的交互就緒時間。
事件委托
對"事件處理程序過多"問題的解決方案就是事件委托。事件委托利用了事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件。例如,click事件會一直冒泡到document層次。也就是說,可以為整個頁面指定一個onclick事件處理程序,而不必給每個可單擊的元素分別添加事件處理程序。
最適合采用事件委托技術的事件包括click、mousedown、mouseup、keydown、keyup和keypress。雖然mouseover和mouseout事件也冒泡,但要適當處理他們并不容易,而且經常需要計算元素的位置(因為當鼠標從一個元素移到其子節點時,或者當鼠標移出該元素時,都會觸發mouseout事件)。
移除事件處理程序
每當將事件處理程序指定給元素時,運行中的瀏覽器代碼與支持頁面交互的JavaScript代碼之間就會建立一個連接。當事件處理程序不再使用時,應該在適當的時機將其去除。
模擬事件
事件,就是網頁中某個特別值得關注的瞬間。事件經常由用戶操作或通過其他瀏覽器功能來觸發。但很少有人知道,可以使用JavaScript在任意時刻來觸發特定的事件,而此時的事件就如同瀏覽器創建的事件一樣。也就是說,這些事件該冒泡還會冒泡,而且按照能夠導致瀏覽器執行已經指定的處理他們的事件處理程序。在測試Web應用程序,模擬觸發事件是一種極其有用的技術。DOM2級規范為此規定了模擬特定事件的方式。IE9,Opera、Firefox、Chrome和Safari都支持這種方式。IE有它自己模擬事件的方式。
DOM中的事件模擬
可以在document對象上使用createEvent()方法創建event對象。這個方法接收一個參數,即表示要創建的事件類型的字符串。在DOM2級中,所有這些字符串都使用英文復數形式,而在DOM3級中都變成了單數。這個字符串可以是下列幾個字符串之一:UIEvents、MouseEvents、MutationEvents和HTMLEvents。
創建完成事件對象后,可以調用事件對象的initXXX()方法,為該事件對象指定具體的屬性,然后調用HTML對象的dispatchEvent()方法完成事件的模擬,如:
var btn = document.getElementById("myBtn");
var event = document.createEvent("MouseEvents");
event.initMouseEvent("click",true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null);
btn.dispatchEvent(event);
我們學習了 HTML 提供的原生拖放(drag & drop)后,是時候想一想這個東西可以用來作什么,可以在什么時候使用,使用的場景等等
場景分析
當我們在注冊成功一個賬戶時,一般網站會讓我們上傳我們的用戶頭像,或者在實名認證的時候會涉及到身份證圖片上傳到等,這時候我們可以使用input提供的file屬性進行選擇本地文件進行上傳。
我們再想一下,當在電腦端的情況下,當用戶打開文件選擇框時再尋找圖片對應的文件夾,再進行選取文件的時候是不是會有點麻煩呢?我們可不可以讓用戶找到圖片文件,直接引入實現上傳呢?答案是可以的。
怎么做
經過這些分析后,我們可以嘗試使用 HTML5 提供的拖拽,使得目標元素增加讀取文件功能,然后使用 ajax 實現圖片上傳。
談一談我們需要使用到的技術:
HTML5 拖拽事件
關于 Drag & Drop 拖拽事件,之前我寫過一篇專門介紹的文章,HTML5-拖拽,大家有興趣的話可以點擊鏈接查看,我在這里就不在多啰嗦了~下面直接出拖拽上傳的簡要代碼示例
var oDragWrap = document.body; //拖進 oDragWrap.addEventListener( "dragenter", function(e) { e.preventDefault(); }, false ); //拖離 oDragWrap.addEventListener( "dragleave", function(e) { dragleaveHandler(e); }, false ); //拖來拖去 , 一定要注意dragover事件一定要清除默認事件 //不然會無法觸發后面的drop事件 oDragWrap.addEventListener( "dragover", function(e) { e.preventDefault(); }, false ); //扔 oDragWrap.addEventListener( "drop", function(e) { dropHandler(e); }, false ); var dropHandler = function(e) { //將本地圖片拖拽到頁面中后要進行的處理都在這 };
獲取文件數據 HTML5 File API
File API 中的 FileReader 接口,作為 File API 的一部分,FileReader 專門用來讀取文件。我們在這里主要介紹一些 File API 中的 FileList 接口,它主要通過兩個途徑獲取本地文件列表,一是<input type="file"/>的表單形式,另一種則是e.dataTransfer.files拖拽事件傳遞的文件信息。
var fileList = e.dataTransfer.files;
使用 files 方法將會獲取到拖拽文件的數組形式的數據,每個文件占用一個數組的索引,如果索引不存在文件數據,將返回 Null。可以通過length屬性獲取文件的數量。
var fileNum = fileList.length;
拖拽上傳需要注意的是需要判斷兩個條件
// 檢測是否是拖拽文件到頁面的操作 if (fileList.length === 0) { return; } // 檢測文件是不是圖片 if (fileList[0].type.indexOf("image") === -1) { return; }
下面我們看看結合之前的拖拽事件,來實現拖拽圖片并在頁面中預覽
var dropHandler = function(e) { e.preventDefault(); //獲取文件列表 var fileList = e.dataTransfer.files; //檢測是否是拖拽文件到頁面的操作 if (fileList.length == 0) { return; } //檢測文件是不是圖片 if (fileList[0].type.indexOf("image") === -1) { return; } //實例化file reader對象 var reader = new FileReader(); var img = document.createElement("img"); reader.onload = function(e) { img.src = this.result; oDragWrap.appendChild(img); }; reader.readAsDataURL(fileList[0]); };
當完成以上操作后,相信你可以成功的完成了拖拽圖片預覽的操作。當你查看 img 標簽時會發現,img的src屬性是一個超長的文件二進制數據,當你需要很多這種的img元素時,建議將展示區域脫離文檔流,讓其絕對定位減少頁面的 reflow
AJAX 上傳圖片
既然已經獲取到拖拽到web頁面中的圖片數據了,下一步就是將其發送到服務器端。
總結
內容是《Web前端開發之Javascript視頻》的課件,請配合大師哥《Javascript》視頻課程學習。
MouseEvent鼠標事件:
DOM2級事件中定義了7個,DOM3級事件增加了2個鼠標事件:
鼠標事件中還有一類滾輪事件,只包括一個mousewheel事件,但此事件已歸WheelEvent類了;
document.addEventListener("click", function(event){
console.log(event); // MouseEvent
},false);
document.addEventListener("mousewheel", function(event){
console.log(event); // WheelEvent
},false);
可以檢測瀏覽器是否支持所有事件,如:
var isSupported = document.implementation.hasFeature("MouseEvent", "3.0");
頁面上的所有元素都支持鼠標事件;除了mouseenter和mouseleave,其他所有鼠標事件都會冒泡,也可以取消,而取消鼠標事件將會影響瀏覽器的默認行為,也會影響其他事件;
click事件:
在一個元素上被按下和放開時,click事件就會被觸發,包括鼠標單擊(通常是左按鈕)或鍵盤回車,或者在腳本中為一個對象執行了click()方法也會觸發該事件;
var btn = document.getElementById("btn");
btn.click();
在一個focusable元素上單擊時,該元素就獲得了焦點,就會觸發focus事件和click事件;
function handler(event){
console.log(event.type);
}
var txt = document.getElementById("txt");
txt.addEventListener("click", handler,false);
txt.addEventListener("focus", handler,false);
其觸發的順序為:focus、click;
只有在同一個元素上相繼觸發mousedown和mouseup事件,才會觸發click事件;如果mousedown或mouseup中一個被取消,就不會觸發click事件,類似只有觸發兩次click事件才會觸發一次dbclick事件;
這4個鼠標事件觸發順序:
mousedown –> mouseup –> click –> mousedown –> mouseup –> click –> dbclick;
mouseover和mouseout事件:
當鼠標進入或移出元素時觸發這兩個事件;
示例:鼠標懸停改變表格行的背景色,如:
<style>
#mytable{width: 400px; border-collapse: collapse;}
#mytable td{ height: 20px; border: 1px solid #000;}
</style>
<table id="mytable">
<tr>
<td></td> <td></td> <td></td>
</tr>
<!-- 多行 -->
</table>
<script>
// 表格名稱、奇數行背景、偶數行背景、鼠標經過背景、點擊后背景
function changeBg(table, oddColor, evenColor,overColor, clickColor){
var rows = document.getElementById(table).rows;
for(var i=0; i < rows.length; i++){
var tr = rows[i];
tr.style.backgroundColor = (tr.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
tr.original = true;
tr.addEventListener("click", function(event){
if(this.original){
this.original = false;
this.style.backgroundColor = clickColor;
}else{
this.original = true;
this.style.backgroundColor = (this.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
}
});
tr.addEventListener("mouseover", function(){
if(this.original)
this.style.backgroundColor = overColor;
});
tr.addEventListener("mouseout", function(){
if(this.original)
this.style.backgroundColor = (this.sectionRowIndex % 2 == 0) ? oddColor : evenColor;
});
}
}
changeBg("mytable", "#FFF", "#ccc", "#cfc", "#f00");
</script>
mouseover和mouseout事件會冒泡,因此,當觸發mouseout事件時,有可能鼠標真的離開了目標元素,但也有可能是從這個元素移動到它的子元素上,或者從一個子元素移動到另一個子元素,所以在這種情況下,需要判斷鼠標的確切位置;
<div id="mydiv">
<div id="div1">div1</div>
<div id="div2">div2</div>
</div>
<script>
var oDiv = document.getElementById("mydiv");
oDiv.addEventListener("mouseover", function(event){
console.log("mouseover:" + event.target.id);
},false);
oDiv.addEventListener("mouseout", function(event){
console.log("mouseout:" + event.target.id);
},false);
</script>
DOM3提供了兩個不冒泡的對應版本mouseenter和mouseleave,如:
oDiv.addEventListener("mouseenter", function(event){
console.log("mouseenter:" + event.target.id);
},false);
oDiv.addEventListener("mouseleave", function(event){
console.log("mouseleave:" + event.target.id);
},false);
示例:圖片遮罩,如:
<style>
*{margin:0; padding: 0;}
ul,li{list-style: none;}
ul{display:flex; flex-wrap: wrap;}
li{width: 200px;}
li>a{display: block; width: 100%; position: relative;}
li img{width:200px;}
</style>
<ul id="mylist">
<li><a href="#" title="天下第一山"><img src="images/1.jpg"></a></li>
<li><a href="#" title="zeronetwork"><img src="images/2.jpg"></a></li>
<li><a href="#" title="Javascript"><img src="images/3.jpg"></a></li>
<li><a href="#" title="Web前端開發"><img src="images/4.jpg"></a></li>
</ul>
<script>
window.onload = function(){
var mylist = document.getElementById("mylist");
var aList = mylist.getElementsByTagName("a");
for(var i=0,len=aList.length; i<len; i++){
var a = aList[i];
var mask = null;
a.addEventListener("mouseenter", function(event){
mask = this.getElementsByClassName("mask")[0];
if(!mask){
mask = document.createElement("div");
mask.className = "mask";
}
mask.style.backgroundColor = "rgba(0,0,0,0.8)";
var computedStyle = document.defaultView.getComputedStyle(this, null);
mask.style.width = computedStyle.width;
mask.style.height = computedStyle.height;
mask.style.position = "absolute";
mask.style.color = "#FFF";
mask.style.textAlign = "center";
mask.style.lineHeight = computedStyle.height;
mask.innerHTML = this.title;
this.insertBefore(mask, this.firstChild);
},false);
a.addEventListener("mouseleave", function(event){
var mask = this.getElementsByClassName("mask")[0];
console.log(this);
if(mask){
this.removeChild(mask);
}
},false);
}
}
</script>
mouseleave和mouseenter事件的行為與CSS的:hover 偽類非常相似;
mousemove事件,會頻繁觸發,所以,在其事件處理程序中不能放置計算密集的任務,或者使用事件節流的方式;
鼠標事件對象:
鼠標事件屬于MouseEvent類,該類指的是用戶與指針設備( 如鼠標 )交互時發生的事件,其繼承自UIEvent類;
MouseEvent類定義了一組專屬于鼠標事件的屬性,描述了當事件發生時鼠標的位置和按鍵的狀態,也包含了是否有輔助鍵被按下等所有信息;
客戶區坐標位置:
clientX與clientY屬性:取得鼠標相對于瀏覽器視口的坐標位置;
var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "click", function(event){
event = EventUtil.getEvent(event);
console.log(event);
console.log(event.clientX + "," + event.clientY);
});
注,這個位置不包括頁面滾動的距離,如果加上窗口的滾動偏移量,就會把鼠標位置轉換成文檔坐標;
所有瀏覽器也都實現了x和y屬性,其是clientX和clientY的別名;
示例:計算鼠標拖動的直線距離,如:
var obj = {};
function downHandler(event){
obj.x = event.x;
obj.y = event.y;
}
function upHandler(event){
obj.mx = event.x - obj.x;
obj.my = event.y - obj.y;
obj.d = Math.sqrt((Math.pow(obj.mx,2) + Math.pow(obj.my,2)));
console.log(obj.mx, obj.my, obj.d);
}
document.addEventListener("mousedown", downHandler, false);
document.addEventListener("mouseup", upHandler, false);
示例:自定義鼠標樣式,如:
document.documentElement.style.cursor = "none";
var cursor = document.createElement("span");
cursor.style.width = "20px";
cursor.style.height = "20px";
cursor.style.position = "absolute";
cursor.style.backgroundColor = "#000";
document.body.appendChild(cursor);
document.addEventListener("mousemove", function(event){
cursor.style.left = event.clientX - cursor.offsetWidth / 2 + "px";
cursor.style.top = event.clientY - cursor.offsetHeight / 2 + "px";
},false);
文檔坐標位置:
pageX和pageY屬性:取得鼠標光標在文檔中的位置;
console.log(event.pageX + "," + event.pageY);
這個位置是包括滾動距離的,在文檔沒有滾動的情況下,pageX、pageY與clientX、clientY值相等;
IE8及以下不支持文檔坐標,不過使用客戶區坐標和滾動信息可以計算出來,也就是需要用到scrollLeft和scrollTop屬性,如:
var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "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);
console.log(pageX + ":" + pageY);
});
屏幕坐標位置:screenX與screenY屬性:取得相對于屏幕的位置;
console.log(event.screenX + ":" + event.screenY);
元素坐標位置:
offsetX和offsetY屬性,返回與目標元素(target)的內填充邊(padding edge)在X和Y軸方向上的偏移量;坐標原點為padding區左上角,因此,如果目標元素有邊框,鼠標的位置位于邊框上,該屬性值為負值;
var oDiv = document.getElementById("mydiv");
oDiv.style.position = "relative";
oDiv.style.left = "100px";
oDiv.style.top = "50px";
document.addEventListener("click", function(event){
console.log("offsetX:" + event.offsetX + ",offsetY:" + event.offsetY);
},false);
如果元素滾動了,也包括offsetLeft和offsetTop值;
示例:繪圖:
<style>
canvas{border: 1px solid;}
</style>
<canvas id="mydraw" width="560" height="360"></canvas>
<script>
var isDrawing = false;
var x=0, y=0;
var mydraw = document.getElementById("mydraw");
var context = mydraw.getContext("2d");
mydraw.addEventListener("mousedown", function(event){
x = event.offsetX, y = event.offsetY;
isDrawing = true;
});
mydraw.addEventListener("mousemove", function(event){
if(isDrawing === true){
drawLine(context, x, y, event.offsetX, event.offsetY);
x = event.offsetX, y = event.offsetY;
}
});
window.addEventListener("mouseup", function(event){
if(isDrawing === true){
drawLine(context, x, y, event.offsetX, event.offsetY);
x = 0, y = 0;
isDrawing = false;
}
});
function drawLine(content, x1, y1, x2, y2){
context.beginPath();
context.strokeStyle = "black";
context.lineWidth = 1;
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
context.closePath();
}
</script>
movementX和movementY屬性:
返回當前事件和上一個mousemove事件之間鼠標在水平或垂直方向上的移動值;
即:currentEvent.movementX = currentEvent.screenX - previousEvent.screenX;
currentEvent.movementY = currentEvent.screenY - previousEvent.screenY;
document.addEventListener("mousemove", function(event){
console.log(event.movementX);
},false);
但IE不支持,并且此屬性只有在mousemove事件中才能返回正確的值;
輔助鍵:
DOM規定了4個屬性:shiftkey、ctrlKey、altkey和metaKey,表示當事件發生時shift、ctrl、alt和meta4個鍵的按下狀態,均為布爾值,如果按下為true,反之為false;
var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", handler);
function handler(event){
event = EventUtil.getEvent(event);
var keys = new Array();
if(event.shiftKey)
keys.push("shift");
if(event.altKey)
keys.push("alt");
if(event.ctrlKey)
keys.push("ctrl");
if(event.metaKey)
keys.push("meta");
console.log("keys:" + keys.join(","));
}
標準瀏覽器支持,但IE不支持metaKey屬性;
getModifierState(key)方法:返回指定修飾鍵的當前狀態;
參數key可以為Control、Alt、Shift和Meta,注意,大小寫是敏感的;
btn.addEventListener("click", function(event){
console.log(event.getModifierState("Control"));
},false);
relatedTarget相關元素:
在發生mouseover和mouseout事件時,會涉及到多個元素;對mouseover而言,事件的主目標是獲得光標的元素,而相關元素就是那個失去光標的元素(這個相關元素也可以把它叫做次目標元素);對mouseout事件而言,事件的主目標是失去光標的元素,而相關元素則是獲得光標的元素;
DOM通過event對象的relatedTarget屬性提供了相關元素的信息,該屬性只針于mouseover、mouseout、mouseenter、mouseleave、focusin、focusout、dragenter(拖動元素進入)事件時才有值;對于其他事件,該屬性為null;
var oDiv = document.getElementById("mydiv");
oDiv.addEventListener("mouseover", function(event){
console.log(event);
},false);
oDiv.addEventListener("mouseout", function(event){
console.log(event);
},false);
IE8及以下不支持relatedTarget屬性,但提供了保存著同樣信息不同的屬性,在mouseover事件觸發時,fromElement屬性中保存了相關元素,toElement屬性為事件目標;在mouseout事件觸發時,toElement屬性保存了相關元素,fromElement屬性為事件目標;
document.addEventListener("mouseover", function(event){
console.log(event.fromElement);
console.log(event.toElement);
},false);
跨瀏覽器取得相關元素,添加到eventutil文件中;
getRelatedTarget: function(event){
if(event.relatedTaret)
return event.relatedTaret;
else if(event.toElement)
return event.toElement;
else if(event.fromElement)
return event.fromElement;
else
return null;
}
應用:
var oDiv = document.getElementById("mydiv");
EventUtil.addHandler(oDiv, "mouseout", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
var relatedTarget = EventUtil.getRelatedTarget(event);
console.log(relatedTarget);
console.log(relatedTarget.tagName);
})
鼠標按鈕:
對于mousedown和mouseup事件來說,在其event對象存在一個button屬性,表示按下或釋放的哪個鼠標按鈕;可能有3個值:0主按鈕;1中間按鈕鼠標滾輪);2次按鈕;
btn.addEventListener("mouseup", function(event){
console.log(event.button);
},false);
IE8及以下也提供了button,但其值與DOM的button屬性有很大差異:0 沒有按下按鈕;1按下主按鈕;2次按鈕;3同時按下主次按鈕;4中間按鈕;5同時按下主和中間按鈕;6次和中間按鈕;7同時按下三個;
跨瀏覽器取得button屬性,在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;
}
}
}
buttons屬性:
當鼠標事件觸發的時,如果多個鼠標按鈕被按下,將會返回一個或者多個代表鼠標按鈕的位掩碼:
buttons的值為各鍵對應值做按位與(+)計算后的值,例如,如果右鍵(2)和滾輪鍵(4)被同時按下,buttons的值為 2 + 4 = 6,如:
btn.addEventListener("mousedown", function(event){
console.log(event.button);
console.log(event.buttons);
},false);
屬性button和buttons 是不同的,buttons可指示任意鼠標事件中鼠標的按鍵情況,而 button只能保證在由按下和釋放一個或多個按鍵時觸發的事件中獲得正確的值;
which屬性:
當鼠標事件觸發時,表示被按下的按鈕,其返回特定按鍵的數字,0為無、1為左鍵、2為中間滾輪、3為右鍵;其是非標準屬性,但所有瀏覽器都支持;
btn.addEventListener("mousedown", function(event){
console.log(event.which);
console.log(event.button);
},false);
注意,此時應該注冊mousedown或mouseup事件而不是click事件,因為右擊或按下中間滾動不會觸發click事件;
detail屬性:
DOM2在event對象中提供了detail屬性,用于給出有關事件的更多信息;對于鼠標click、mousedown和mouseup事件來說,detail中包含了一個數值,表示目標元素被單擊了多少次,其它事件返回0;detail從1開始計數,每次單擊都會遞增;
console.log(event.detail);
用此屬性就可以判斷用戶是單擊、雙擊還是三擊;如果鼠標在mousedown和mouseup之間移動了位置,則detail被重置為0;
IE也通過下列屬性為鼠標事件提供了更多信息:
這些屬性只有IE支持;
示例:拖動文檔元素,當鼠標按下或釋放時,會觸發mousedown和mouseup事件,因此,通過這兩個事件,可以探測和響應鼠標的拖動;如:
function drag(elementToDrag, event){
var scroll = {x:0, y:0};
var startX = event.clientX + scroll.x;
var startY = event.clientY + scroll.y;
var origX = elementToDrag.offsetLeft;
var origY = elementToDrag.offsetTop;
var deltaX = startX - origX;
var deltaY = startY - origY;
if(document.addEventListener){
document.addEventListener("mousemove", moveHandler, true);
document.addEventListener("mouseup", upHandler, true);
}else if(document.attachEvent){
elementToDrag.setCapture();
elementToDrag.attachEvent("onmousemove", moveHandler);
elementToDrag.attachEvent("onmouseup", upHandler);
// 作為mouseup事件看待鼠標捕獲的丟失
elementToDrag.attachEvent("onlosecapture", upHandler);
}
// 處理了這個事件,不讓任何其他元素看到它
if(event.stopPropagation)
event.stopPropagation();
else
event.cancelBubble = true;
// 現在阻止任何默認操作
if(event.preventDefault)
event.preventDefault();
else
event.returnValue = false;
// 當元素正在被拖動時,這就是捕獲mousemove事件的處理程序
// 它用于移動這個元素
function moveHandler(e){
if(!e) e = window.event;
// 移動這個元素到當前鼠標位置
// 通過滾動條的位置和初始單擊的偏移量來調整
// var scroll = getScrollOffsets();
var scroll = {x:0,y:0};
elementToDrag.style.left = (e.clientX + scroll.x - deltaX) + "px";
elementToDrag.style.top = (e.clientY + scroll.y - deltaY) + "px";
// 同時不讓任何其他元素看到這個事件
if(e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
// 這是捕獲在拖動結束時發生的最終mouseup事件的處理程序
function upHandler(e){
if(!e) e = window.event;
// 注銷捕獲事件處理程序
if(document.removeEventListener){
document.removeEventListener("mouseup", upHandler, true);
document.removeEventListener("mousemove", moveHandler, true);
}else if(document.detachEvent){
elementToDrag.detachEvent("onlosecapture", upHandler);
elementToDrag.detachEvent("onmouseup", upHandler);
elementToDrag.detachEvent("onmousemove", moveHandler);
elementToDrag.releaseCapture();
}
// 并且不讓事件進一步傳播
if(e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
}
}
應用:
<div style="position: absolute;left:100px;top:100px; width:150px;background-color: purple;">
<div style="background-color: gray;" onmousedown="drag(this.parentNode, event);">標題欄-拖動我</div>
<p>Lorem ...</p>
</div>
CSS的pointer-events屬性:
指定在什么情況下 (如果有) 某個特定的元素可以成為鼠標事件的target;主要用于 SVG元素;
可能的值為:
該屬性可以:
<style>
/* 鏈接不會跳轉 */
a[href="https://www.zeronetwork.cn/"]{
pointer-events: none;
}
</style>
<a href="https://www.zeronetwork.cn/">零點網絡</a>
<script>
var link = document.querySelector("a");
function handler(event){
console.log(event);
}
// 以下均無效
link.addEventListener("click",handler,false);
link.addEventListener("mouseover",handler,false);
link.addEventListener("drag",handler,false);
</script>
此屬性可以通過控制臺改變,如在控制臺輸入:document.querySelector("a").style.pointerEvents = "auto";此時,超鏈接就可以觸發了;
<style>
/* 使所有img對任何鼠標事件(如拖動、懸停、單擊等)無反應 */
img{
pointer-events: none;
}
</style>
<img src="images/1.jpg" />
<script>
var img = document.querySelector("img");
function handler(event){
console.log(event);
}
// 以下均無效
img.addEventListener("click",handler,false);
img.addEventListener("mouseover",handler,false);
img.addEventListener("drag",handler,false);
</script>
除了指示該元素不是鼠標事件的目標之外,值none表示鼠標事件“穿透”該元素并且指定該元素“下面”的任何元素;如:
<style>
.container{position: relative; width: 200px; height: 150px;}
.mask{width: 100%; height: 100%; background-color: rgba(0, 0, 0, .5);
position: absolute; pointer-events: none; color:#FFF}
.container img{width: 100%; height: 100%;}
</style>
<div class="container">
<div class="mask"></div>
<a href="https://www.zeronetwork.cn"><img src="images/1.jpg" /></a>
</div>
<script>
var link = document.querySelector(".container>a");
link.addEventListener("mouseover", function(event){
var mask = event.currentTarget.parentNode.querySelector(".mask");
mask.innerHTML = event.currentTarget.title;
},false);
link.addEventListener("mouseout", function(event){
var mask = event.currentTarget.parentNode.querySelector(".mask");
mask.innerHTML = "";
},false);
</script>
示例:取得一個元素的相對鼠標坐標,如:
<style>
.parent{ width:400px; height:400px; padding: 50px; margin:100px; background:#f20; }
.child{ width:200px; height:200px; padding:50px; background:#ff0;}
.child-child{ width:50px; height:50px; background:#00d;}
</style>
<div class="parent" id="parent">
<div class="child">
<div class="child-child"></div>
</div>
</div>
<script>
var parent = document.getElementById("parent");
parent.addEventListener("click",function(event){
console.info(event.offsetX);
});
</script>
使用pointer-events屬性后再獲取,如為child和child-child類分別添加pointer-events屬性,此時打印的值就是相對于parent元素的坐標了;
使用pointer-events來阻止元素成為鼠標事件目標不一定意味著元素上的事件偵聽器永遠不會觸發,如果元素后代明確指定了pointer-events屬性并允許其成為鼠標事件的目標,那么指向該元素的任何事件在事件傳播過CSS添加pointer-events:none,再為其子元素添加pointer-events:all,此時在子元素上單擊就可以觸發注冊在父元素上的事件處理程序;
當然,位于父元素但不在后代元素上的鼠標活動都不會被父元素和后代元素捕獲(鼠標活動將會穿過父元素而指向位于其下面的元素);
var subchild = document.querySelector(".child-child");
subchild.addEventListener("click",function(event){
console.log("child-child");
parent.style.pointerEvents = "auto";
});
該屬性也可用來提高滾動時的幀頻;例如,當頁面滾動時,如果恰巧鼠標懸停在某些元素上,則會觸發其上的hover效果或觸發onmouseover事件,有可能會造成滾動出現問題,此時,如果對body元素應用pointer-events:none,則會禁用了包括hover在內的鼠標事件,從而提高滾動性能;
<style>
#mydiv:hover{
background-color: lightgreen !important;
}
</style>
<div id="mydiv" style="height: 300px;background-color: purple;"></div>
<div style="height: 1000px;"></div>
<script>
var mydiv = document.getElementById("mydiv");
mydiv.addEventListener("mouseover", function(event){
console.log(event);
},false);
</script>
滾動頁面時觸發了mouseover事件及hover效果,可以在scroll事件中進行相應的處理,如:
var timeoutId = null;
window.addEventListener("scroll", function(event){
document.body.style.pointerEvents = "none";
if(timeoutId){
clearTimeout(timeoutId);
timeoutId = null;
}else{
timeoutId = setTimeout(function(){
console.log("解禁了");
document.body.style.pointerEvents = "auto";
},500);
}
},false);
部分瀏覽器不支持該屬性,可以判斷其支持情況,如:
var supportsPointerEvents = (function(){
var dummy = document.createElement("_");
if(!('pointerEvents' in dummy.style))
return false;
dummy.style.pointerEvents = 'auto';
// 如果是真的屬性,則賦值不成功
dummy.style.pointerEvents = 'x';
document.body.appendChild(dummy);
var result = getComputedStyle(dummy).pointerEvents === 'auto';
document.body.removeChild(dummy);
return result;
})();
console.log(supportsPointerEvents);
WheelEvent滾輪事件:
當用戶通過鼠標滾輪與頁面交互,在垂直方向上滾動頁面時(無論向上還是向下),就會觸發mousewheel事件;該事件可以在任何元素上觸發,最終會冒泡到document或window對象,也可以阻止其默認行為;
document.addEventListener("mousewheel", function(event){
console.log(event); // WheelEvent
},false);
WheelEvent類:
表示用戶滾動鼠標滾輪或類似輸入設備時觸發的事件,用以替代MouseWheelEvent和MouseScrollEvent,mousewheel實際上是屬于MouseWheelEvent類,而后面要講的Firefox中的DOMMouseScroll屬于MouseScrollEvent類,它們兩者都不屬于標準,兼容性也不好,為了統一兩者,就出現了標準的WheelEvent類;
WheelEvent類繼承自MouseEvent類(MouseEvent類繼承自UIEvent類),所有也可以把它看作是鼠標事件,因此,對于WheelEvent事件對象來說,其中也保存著大量與MouseEvent事件同樣的屬性,例如,四對有關獲取坐標的屬性、which(值為0)、relatedTarget(為null)等等;還包括輔助鍵的屬性;
mousewheel事件中的event對象,除了保存鼠標事件的所有標準信息外,還包含一個特殊的wheelDelta屬性,其指定用戶滾動滾輪有多遠,當用戶向前滾動鼠標滾輪時,該屬性值是120的倍數,當向后滾動時,該值是-120的倍數;
EventUtil.addHandler(document, "mousewheel", function(event){
event = EventUtil.getEvent(event);
console.log(event);
console.log(event.wheelDelta);
})
如果要判斷用戶滾動的方向,只要檢測wheelDelta屬性的正負號即可;在Opera9.5之前的版本中,wheelDelta的值的正負號是顛倒的;
除了wheelDelta屬性外,事件對象還有wheelDeltaX和wheelDeltaY屬性,并且wheelDelta和wheelDeltaY的值一直相同;
console.log(event.wheelDelta);
console.log(event.wheelDeltaY);
console.log(event.wheelDeltaX);
IE不支持這兩個屬性;
Firefox不支持mousewheel事件,但支持一個名為DOMMouseScroll的類似事件,也是在鼠標滾輪滾動時觸發,它也被視為鼠標事件,也包含與鼠標事件有關的所有鼠標;而有關鼠標滾輪的信息則保存在detail屬性中,該屬性與wheelDelta作用相同;當向前滾動鼠標滾輪時,該屬性返回-3的位數,反之返回3的位數;
可以把該事件添加到頁面中的任何元素,而且該事件會冒泡到window對象;
EventUtil.addHandler(document, "DOMMouseScroll", function(event){
event = EventUtil.getEvent(event);
console.log(event);
console.log(event.detail); // 向前為-3,向后是3
})
detail屬性值與wheelDelta的值的關系是:wheelDelta等于detail乘以-40;
可以跨瀏覽器取得鼠標滾輪增值(delta),并添加到eventutil.js中,如:
getWheelDelta: function(event){
if(event.wheelDelta){
return event.wheelDelta;
}else
return -event.detail * 40;
}
應用時,需要同時注冊mousewheel和DOMMouseScroll事件,如:
function handlerMouseWheel(event){
event = EventUtil.getEvent(event);
var delta = EventUtil.getWheelDelta(event);
console.log(delta);
}
EventUtil.addHandler(document, "mousewheel", handlerMouseWheel);
EventUtil.addHandler(document, "DOMMouseScroll", handlerMouseWheel);
另外,DOMMouseEvent事件對象中還有一個axis屬性,其返回一個long型常量值,表明鼠標滾輪滾動的方向,當返回常量HORIZONTAL_AXIS,值為1時,表示由鼠標滾輪的橫向滾動觸發的,當返回VERTICAL_AXIS,值為2時,表示是由鼠標滾輪的縱向滾動觸發的;
wheel事件:
DOM3事件定義了一個名為wheel事件,用于替代mousewheel和DOMMouseScroll事件;事件對象中保存著deltaX、deltaY及deltaZ屬性:用來獲取三個不同的鼠標滾軸;大多數鼠標滾輪是一維或二維的,所以并不能使用deltaZ屬性;
EventUtil.addHandler(document, "wheel", function(event){
event = EventUtil.getEvent(event);
console.log(event); // WheelEvent
console.log(event.wheelDelta); // -120
console.log(event.wheelDeltaY); // -120
console.log(event.deltaX); // -0
console.log(event.deltaY); // chrome返回100,Firefox返回63
console.log(event.deltaZ); // 0
});
這些值必須乘以-1.2,才和mousewheel事件的wheelDelta值和正負號相匹配;
wheel事件對象還有一個deltaMode屬性,只讀,其返回long常量值,表示各delta*的值的單位,其值及所表示的單位如下:
常量值描述
console.log(event.deltaMode);
示例:在Enclose.js文件中定義enclose()函數,可以把一個圖片裝載到一個容器中,并且能移動這個容器,也能改變容器的大小,如:
function enclose(content, framewidth, frameheight, contentX, contentY){
// 這些參數不僅僅是初始值,它們保存當前狀態,能被mousewheel處理程序使用和修改
framewidth = Math.max(framewidth, 50);
frameheight = Math.max(frameheight, 50);
contentX = Math.min(contentX, 0) || 0;
contentY = Math.min(contentY, 0) || 0;
// 創建frame元素,且設置CSS類和樣式
var frame = document.createElement("div");
frame.className = "enclose";
frame.style.width = framewidth + "px";
frame.style.height = frameheight + "px";
frame.style.overflow = "hidden"; // 沒有滾動條,不能溢出
frame.style.boxSizing = "border-box"; // 能簡化調整frame大小的計算
content.parentNode.insertBefore(frame, content);
frame.appendChild(content);
// 確定元素相對于frame的位置
content.style.position = "relative";
content.style.left = contentX + "px";
content.style.top = contentY + "px";
var isFirefox = (navigator.userAgent.indexOf('Gecko') != -1);
// 注冊mousewheel事件處理程序
frame.onwheel = wheelHandler;
frame.onmousewheel = wheelHandler;
if(isFirefox)
frame.addEventListener("DOMMouseScroll", wheelHandler, false);
function wheelHandler(event){
var e = event || window.event;
var deltaX = e.deltaX / 3.33 || // wheel事件
e.wheelDeltaX / 4 || // mousewheel事件
0 // 屬性未定義
var deltaY = e.deltaY / 3.33 ||
e.wheelDeltaY / 4 ||
(e.wheelDeltaY === undefined && // 如果沒有2D屬性
e.wheelDelta / 4) || // 就使用1D的滾輪屬性
e.detail * -1.2 || // DOMMouseScroll事件
0; // 屬性未定義
if(isFirefox && e.type !== "DOMMouseScroll")
frame.removeEventListener("DOMMouseScroll", wheelHandler, false);
// 獲取內容元素的當前尺寸
var contentbox = content.getBoundingClientRect();
var contentwidth = contentbox.right - contentbox.left;
var contentheight = contentbox.bottom - contentbox.top;
// 如果按下Alt鍵,就可以調整frame大小
if(e.altKey){
if(deltaX){
framewidth -= deltaX; // 新寬度,但不能比內容大
framewidth = Math.min(framewidth, contentwidth);
framewidth = Math.max(framewidth, 50); // 不能小于50
frame.style.width = framewidth + "px";
}
if(deltaY){
frameheight -= deltaY;
frameheight = Math.min(frameheight, contentheight);
frameheight = Math.max(frameheight - deltaY, 50);
frame.style.height = frameheight + "px";
}
}else{ // 沒有按Alt鍵,就可以平移frame中的內容
if(deltaX){
var minoffset = Math.min(framewidth - contentwidth, 0);
// 把deltaX添加到contentX中,但不能小于minoffset
contentX = Math.max(contentX + deltaX, minoffset);
contentX = Math.min(contentX, 0);
content.style.left = contentX + "px";
}
if(deltaY){
var minoffset = Math.min(frameheight - contentheight, 0);
contentY = Math.max(contentY + deltaY, minoffset);
contentY = Math.min(contentY, 0);
content.style.top = contentY + "px";
}
}
if(e.preventDefault)
e.preventDefault();
if(e.stopPropagation)
e.stopPropagation();
e.cancelBubble = true;
e.returnValue = false;
return false;
}
}
應用:
<style>
div.enclose{border: 10px solid; margin:10px}
</style>
<img id="content" src="images/1.jpg" />
<script>
window.onload = function(){
enclose(document.getElementById("content"),400,200,-200,-300);
}
</script>
不要混淆wheel事件和scroll事件:wheel事件的默認動作取決于瀏覽器實現,因此wheel事件不一定會觸發scroll事件;即便滾輪事件引發了文檔內容的滾動行為,也不表示wheel事件中的delta*值恰好反映文檔內容的滾動方向;因此,不要依賴delta*屬性獲知文檔內容的滾動方向,可在文檔內容滾動事件中監視target的scrollLeft和scrollTop的變化以推斷滾動方向;
*請認真填寫需求信息,我們會在24小時內與您取得聯系。