1. 程式人生 > >javascript中mouseover和mouseout事件詳解

javascript中mouseover和mouseout事件詳解

  與 mouseenter 事件不同,不論滑鼠指標穿過被選元素或其子元素,都會觸發 mouseover 事件。只有在滑鼠指標穿過被選元素時,才會觸發 mouseenter 事件。
  與 mouseout 事件不同,只有在滑鼠指標離開被選元素時,才會觸發 mouseleave 事件。如果滑鼠指標離開任何子元素,同樣會觸發 mouseout 事件。

  解決兩者的區別,看下面引用的例子:

  當為某個容器綁定了 onmouseover 或者onmouseout 事件時,如果這個容器中有其它元素節點,那麼滑鼠在內部移動時會頻繁觸發 onmouseover和onmouseout 事件。

  而我想要的效果是:事件僅在滑鼠進入/離開元素區域觸發一次,當滑鼠在元素區域內部移動的時候不會觸發。

  為什麼會出現這個原因呢?其實是因為事件冒泡導致的。當滑鼠移上或者移出容器中的子節點時,會分別觸發mouseover和mouseout事件,緊隨著dom樹向上冒泡傳遞,直到被事件處理程式(監聽器)捕獲捕獲或者冒泡到根節點(document或者window),也就是說事件會向它的父級物件派發。

  知道了問題產生原因,那麼解決起來是不是也很簡單呢?最初我想的是取消事件冒泡,使用event.cancelBubble = true(IE)和e.stopPropagation()(其它瀏覽器),但是簡單測試後發現貌似沒有什麼效果,問題依舊,貌似冒泡停止不了,原因不明。(補充:我是測試將容器中的a連結節點取消冒泡,但是發現滑鼠移上移下還會觸發事件。a節點下還有span節點。難道要將容器中所有節點都取消冒泡才行?有心人可以測試,如果真的這樣,那也太噁心了,要是N多的節點,難道都要停止冒泡下?)

  其實在IE下滑鼠事件有個 mouseEnter 和 mouseLeave,這個就是移進和移處容器時觸發一次,在內部移動則不會觸發,遺憾的是隻有IE支援。我們現在要做的就是“為非IE瀏覽器新增mouseEnter和mouseLeave支援”。

  我翻閱了百度最新開源的JS庫tangram,看了裡面的處理,發現貌似是單獨處理了非IE瀏覽器下的事件,使用一個叫“baidu.event._eventFilter._crossElementBoundary(listener, e)”的方法修正mouseover和mouseout,然後封裝了個mouseEnter和mouseLeave事件。

baidu.event._eventFilter._crossElementBoundary = function
(listener, e){
var related = e.relatedTarget, current = e.currentTarget; if(typeof related == 'undefined'){ return listener.call(current, e); } // 如果current和related都是body,contains函式會返回false // Firefox有時會把XUL元素作為relatedTarget // 這些元素不能訪問parentNode屬性 // thanks jquery & mootools //如果current包含related,說明沒有經過current的邊界 //注:baidu.dom.contains是個定義的檢測節點是否包含的函式,下面我會講到 if(related === false || current == related || related.prefix == 'xul' || baidu.dom.contains(current, related)){ return ; } //呼叫執行 return listener.call(current, e); };

  百度的方法我並不喜歡,首先它只對非IE瀏覽器進行了處理,當然,它又進行了封裝,可以直接使用mouseEnter和mouseLeave;但是,我們做普通開發,沒必要這麼封裝,我只是想要簡單的去掉mouseover和mouseout的這個惱人特性。

  而jQuery則不是這麼做的,它是直接對IE和其它所有瀏覽器下的mouseover和mouseout事件進行了修正。參考jQuery,我得到了我目前所有的程式碼。

  首先,介紹個判斷節點物件是否包含的函式contains.

function contains(p,c){
    return p.contains ?
    p != c && p.contains(c) : !!(p.compareDocumentPosition(c) & 16);
}

  然後就是重點的了,這裡我們在IE下用到了fromElement和toElement,這兩個是IE下的滑鼠移上去時和移出時的節點物件。

function fixedMouse(e,target){
    var related,
    type=e.type.toLowerCase();//這裡獲取事件名字
    if(type=='mouseover'){
        related=e.relatedTarget||e.fromElement
      }else if(type='mouseout'){
        related=e.relatedTarget||e.toElement
      }else return true;
      return related && related.prefix!='xul' && !contains(target,related) && related!==target;
}

  然後我們怎麼用呢?比如在繫結事件時,

//addListener為封裝的事件繫結函式
addListener(target,'mouseover',function(e){
    e=e||window.event;
    if(fixedMouse(e, target)){
    //do something
    }
},false);

  這樣就會只在移入移出target節點時觸發mouseover和mouseout了。

  當然,你也可以將上面的程式碼單獨封裝成mouseEnter和mouseLeave,這樣可以以後呼叫時更好區別mouseover和mouseout。