問題:當鼠標移動到元素上,多次觸發mouseover,mouseout事件。
(注,該問題是在實現鼠標移動到一起菜單,滑動彈出二級時碰到的;因為鼠標移動到二級菜單時,動畫再次觸發,才意識到該問題;之前因為使用的是:hover偽類實現的顯示二級菜單,並且沒有加入動畫,所以並沒有發現該問題。)
問題原因分析:事件的冒泡機制,當子元素上發生相應事件時,會觸發父級元素的該事件。如A元素包含B元素,在A,B元素上分別添加mouseover,mouseout監聽事件,當鼠標移到A上,但不在B上時,觸發A的mouseover,同時對應的event.eventPhase為2即目標階段;當鼠標繼續移入B元素中時,這時觸發A事件的mouseout,event.eventPhase為3,即冒泡階段,同時觸發B事件的mouseover,event.eventPhase為2,即目標階段。
解決方法:檢測事件的相關元素(relatedTarget,對於mouseover來說:relatedTarget是鼠標進入元素前,所離開的元素;對於mouseout,relatedTarget是鼠標離開元素后,所進入的元素)是被綁定元素的子元素與否。(或者利用jQuery的mouseenter,mouseleave事件,因為jquery已經將該事件封裝)
function contains(parentNode, childNode) { if (parentNode.contains) { return parentNode != childNode && parentNode.contains(childNode); } else { return !!(parentNode.compareDocumentPosition(childNode) & 16); } }
該函數是判斷兩個節點的關系,它考慮到IE與其他瀏覽器的兼容性,[dom].contains([dom])方法是IE瀏覽器的方法([dom]表示文檔流中的節點),[A].compareDocumentPosition([B])是DOM3中的方法,下面是不同位置關系對應的返回結果。
Bits Number Meaning
000000 0 元素一致
000001 1 節點在不同的文檔(或者一個在文檔之外)
000010 2 節點 B 在節點 A 之前
000100 4 節點 A 在節點 B 之前
001000 8 節點 B 包含節點 A
010000 16 節點 A 包含節點 B
100000 32 瀏覽器的私有使用
接下來是判斷事件相關元素與目標元素之間的關系,只有當觸發事件的相關元素不是目標元素的后繼節點,checkHover()函數才返回true.
function checkHover(e,target){ if (getEvent(e).type=="mouseover") { return !contains(target,getEvent(e).relatedTarget||getEvent(e).fromElement) && !((getEvent(e).relatedTarget||getEvent(e).fromElement)===target); } else { return !contains(target,getEvent(e).relatedTarget||getEvent(e).toElement) && !((getEvent(e).relatedTarget||getEvent(e).toElement)===target); } }
getEvent是為了兼容IE瀏覽器;checkHover函數中之所以添加一個if判斷是因為IE下mouseover和mouseout的相關元素分別對應的是fromElement,toElement,因此分別處理,當是其他事件時,這兩個屬性在IE下為null。而FF和chrome瀏覽器中的相關元素都是relatedTarget,mouseover中relatedTarget是鼠標移到目標元素時所離開的那個元素,mouseout中relatedTarget是鼠標離開目標元素時要進入的元素,對於其他事件該屬性無用。
最后是函數的調用。
myElement.onmouseover=function(e){ if(checkHover(e,this)){ do someting... } }
附加:
1)event還有兩個對象currentTarget,target;currentTarget是當前響應事件的對象,target是最初觸發該事件的對象;
2)阻止事件的冒泡為event.stopPropagation();
3)阻止默認行為為event.perventDefault();
4)在處理函數中最后寫上return false;即阻止冒泡又阻止默認操作。
5)事件分為3個階段:捕捉階段,eventPhase為1;目標階段,eventPhase為2;冒泡階段,eventPhase為3。
下面為測試時,監控程序的代碼
var count=0; $(this).bind('mouseover',function(e){ console.log(++count+' mouseover: '+e.target.className+" "+e.eventPhase); ... }).bind('mouseout',function(e){ console.log(++count+' mouseout: '+e.target.className+" "+e.eventPhase); ... })
