解決因mouseover,mouseout冒泡產生的閃爍問題+相容性問題
啥是冒泡:
JavaSciprt事件中有兩個很重要的特性:事件冒泡以及目標元素。
當一個元素上的事件被觸發的時候,比如說滑鼠點選了一個按鈕,同樣的事件將會在那個元素的所有祖先元素中被觸發。這一過程被稱為事件冒泡;這個事件從原始元素開始一直冒泡到DOM樹的最上層。
任何一個事件的目標元素都是最開始的那個元素,在我們的這個例子中也就是按鈕,並且它在我們的元素物件中以屬性的形式出現。使用事件代理的話我們可以把事件處理器新增到一個元素上,等待一個事件從它的子級元素裡冒泡上來,並且可以很方便地得知這個事件是從哪個元素開始的。
由於瀏覽器的冒泡行為。造成如果在一個DIV元素上同時定義了mouseover,mouseout的時候,當滑鼠移動到DIV中的child子元素的時候,就會同時執行了兩個操作mouseover和mouseout。這個網頁可以讓你更好的理解冒泡:http://varnow.org/pages/mouseover-mouseout-problem.html
這個是解決冒泡後的顯示情況(採用onmouseenter等事件):http://varnow.org/pages/mouseover-mouseout-solution-two.html
事件的冒泡有什麼好處:
想象一下現在我們有一個10列、100行的HTML表格,你希望在使用者點選表格中的某一單元格的時候做點什麼。比如說我有一次就需要讓表格中的每一 個單元格在被點選的時候變成可編輯狀態。如果把事件處理器加到這1000個單元格會產生一個很大的效能問題,並且有可能導致記憶體洩露甚至是瀏覽器的崩潰。 相反地,使用事件代理的話,你只需要把一個事件處理器新增到table元素上就可以了,這個函式可以把點選事件給截下來,並且判斷出是哪個單元格被點選 了。
function showMemberInfoCard(memberId, id) { if($("#card").hasClass("true")) { clearTimeout(time); } if(memberId != null && memberId != 0) { getMemberInfoCard(memberId); $("#card").addClass("true"); var offset=$(id).offset(); $("#card").css({left:offset.left-60, top:offset.top+25}).show(); } }
事件冒泡的優點和缺點:
那些需要建立的以及駐留在記憶體中的事件處理器少了。這是很重要的一點,這樣我們就提高了效能,並降低了崩潰的風險。
在DOM更新後無須重新繫結事件處理器了。如果你的頁面是動態生成的,比如說通過Ajax,你不再需要在元素被載入或者解除安裝的時候來新增或者刪除事件處理器了。
潛在的問題也許並不那麼明顯,但是一旦你注意到這些問題,你就可以輕鬆地避免它們:你的事件管理程式碼有成為效能瓶頸的風險,所以儘量使它能夠短小精悍。
不是所有的事件都能冒泡:
blur、focus、load和unload不能像其它事件一樣冒泡。事實上blur和focus可以用事件捕獲而非事件冒泡的方法獲得(在IE之外的其它瀏覽器中)。
需要注意的是:如果你的程式碼處理mousemove事件的話你遇上效能瓶頸的風險可就大了,因為mousemove事件觸發非常頻繁。而mouseout則因為其怪異的表現而變得很難用事件代理來管理。
解決方案:
阻止冒泡行為,當執行mouseover的時候不觸發mouseout的操作。
方法1:(比較通用)
延遲執行(setTimeout)、取消延遲(clearTimeout),就是當mouseout的時候延遲執行,而在mouseover的時候取消延遲執行。當滑鼠在DIV上邊移動的時候因為延遲的執行所以mouseout一直都不會被觸發。
function showMemberInfoCard(memberId, id) { //這個id是當前<div>的ID
if($("#card").hasClass("true")) { //設定Class是為了判斷顯示層是否正在顯示
clearTimeout(time);
}
if(memberId != null && memberId != 0) {
//做你想要的操作
$("#card").addClass("true");
var offset=$(id).offset(); //獲取座標資訊
$("#card").css({left:offset.left-60, top:offset.top+25}).show();
}
}
function hideMemberInfoCard() {
if($("#card").hasClass("true")) {
time=setTimeout(function() {
$("#card").removeClass("true");
$("#card").hide();
}, 500);
}
}
function showCard() { //顯示層
if($("#card").hasClass("true")) {
clearTimeout(time);
}
}
function hideCard() {
if($("#card").hasClass("true")) {
time=setTimeout(function() {
$("#card").removeClass("true");
$("#card").hide();
}, 500);
}
}
方法2:jquery(需要版本號大於1.2.2)
mouseenter和mouseleave事件IE特有的函式,使用jquery就很好的解決了相容問題。函式的原理當第一次滑鼠進入節點區域時觸發,以後在節點區域內(子節點間)移動時不觸發。
就是把onmouseover->onmouseenter、onmouseout->onmouseleave
但是實際中我發現在Chrome瀏覽器下不響應這個事件,相容性問題,所以最終採用第一種方法,相對來說程式碼難理解一些,請求也多。