2025-04-23

問題描述

我希望當鼠標移動到id1上的時候,id2顯示,當鼠標離開id1的時候,id2顯示。問題如下:
1.當鼠標從id1上移動到id2上的時候,id由有顯示變為不顯示,然後變為顯示
2.當鼠標從id2上移動到id1上的時候, id2有顯示變為不顯示,然後變為顯示
我希望的是當鼠標在id1或者id2上移動的時候,id2一直顯示,不發生變化。

<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>

 

<script type="text/javascript">
$("#id1").mouseover(function(){
$(this).children().fadeIn(1000);
}).mouseout(function(){
$(this).children().fadeOut(1000);
});
</script>

這裡寫圖片描述

問題解決辦法

最開始的問題分析,當鼠標從id1上移動到id2上的時候,由於鼠標由id2離開進入id1,針對id1觸發瞭一個mouseout事件,於是id2有顯示變為不顯示,接著在鼠標移動到id2上,在id2上觸發瞭一個mouseover事件,由於冒泡機制,id2上的mouseover冒泡到id1之前,觸發瞭id1上的mouseover事件,然後id2由不顯示變為顯示。同理,當鼠標從id2上移動到id1上的時候,針對id2,觸發瞭一個mouseout事件,還是因為冒泡機制,mouseout事件傳到id1上,id2由顯示變為不顯示,接著鼠標移動到id1之前,觸發瞭一個mouseover事件,然後id2有不顯示變為顯示。

看來,上面的問題歸根要解決的是,當鼠標由id1上移動到id2上的時候,阻止id1的mouseout事件;當鼠標從id2上移動到id1上的時候,阻止id2的mouseout事件冒泡到id1之上。那麼僅僅通過阻止冒泡是解決不瞭問題。

為瞭解決這樣的問題,Jquery提供瞭mouseenter和mouseleave方法。於是將JS代碼改為如下,很好解決瞭問題。

$("#id1").mouseenter(function(){
                $(this).children().fadeIn(1000);
            }).mouseleave(function(){
                $(this).children().fadeOut(1000);
            });

很多地方都有介紹mouseenter、mouseleave與mouseover、mouseout,於是復制粘貼瞭一個。
/*********************************************************/
1.mouseover與mouseenter
不論鼠標指針穿過被選元素或其子元素,都會觸發 mouseover 事件。
隻有在鼠標指針穿過被選元素時,才會觸發 mouseenter 事件。

2.mouseout與mouseleave
不論鼠標指針離開被選元素還是任何子元素,都會觸發 mouseout 事件。
隻有在鼠標指針離開被選元素時,才會觸發 mouseleave 事件。
/*********************************************************/
現象確實是這個現象,但是過程說的有點模糊,我的理解如下:
當鼠標指針移動到被選元素,會觸發 mouseover 事件,這個大傢都知道,當鼠標指針由被選元素移動到其子元素,先是觸發被選元素的mouseout事件,然後子元素的mouseover事件冒泡到被選元素,此時相當於被選元素先執行瞭一個mouseout事件,然後執行瞭一個mouseover事件。
為瞭驗證將代碼改為如下

<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>

 

<script type="text/javascript">
$("#id1").mouseover(function(){
//$(this).children().fadeIn(1000);
console.log('a');
}).mouseout(function(){
//$(this).children().fadeOut(1000);
console.log('b');
});
</script>

鼠標從頁面移動到id1,然後由id1移動到id2上,控制臺輸出如下圖
這裡寫圖片描述
可以看出id1先後調用瞭mouseover、mouseout、mouseover事件,正好和上面分析的相同。

mouseenter與mouseleave實現分析

原理分析

從上面分析,我們可以看出,要實現mouseenter與mouseleave的效果,就是當鼠標從被選元素移動到其子元素上的時候,被選元素不執行mouseout事件,也不執行子類冒泡過來的mouseover事件,當鼠標從被選元素子元素移動到被選元素上的時候,被選元素不執行mouseover事件,也不執行子類冒泡過來的mouseout事件。
要實現上面的效果,我們需要event對象的一個屬性relatedTarget,這個屬性就是用來判斷 mouseover和mouseout事件目標節點的相關節點的屬性。簡單的來說就是當觸發mouseover事件時,relatedTarget屬性代表的就是鼠標剛剛離開的那個節點,當觸發mouseout事件時它代表的是鼠標移向的那個對象。由於MSIE不支持這個屬性,不過它有代替的屬性,分別是 fromElement和toElement。除此,我們還需要contains方法,來判斷一個對象是否包含在另外一個對象中。
這樣當鼠標移動,需要判斷以下兩條即可
1.調用mouseover,隻需要判斷relatedTarget是否被選元素的子元素,如果是,則不執行(當於從被選元素子元素移動到被選元素,不執行mouseover;當於從被選元素移動到被選元素子元素,不執行冒泡過來的mouseover);
2.調用mouseout,隻需要判斷relatedTarget是否被選元素的子元素,如果是,則不執行(當於從被選元素子元素移動到被選元素,不執行子元素冒泡過來的mouseout;當於從被選元素移動到被選元素子元素,不執行mouseover);

實現過程

判斷兩個元素是否存在包含關系

jquery中封裝瞭contains函數如下
這裡寫圖片描述
可以簡化為如下

//判斷兩個a中是否包含b
    function contains(a,b){
            return a.contains ? a != b && a.contains(b) :!!(a.compareDocumentPosition(b) & 16);
    }

compareDocumentPosition介紹

這個方法是 DOM Level 3 specification 的一部分,允許你確定 2 個 DOM Node 之間的相互位置。這個方法比 .contains() 強大。這個方法的一個可能應用是排序 DOM Node 成一個詳細精確的順序。NodeA.compareDocumentPosition(NodeB)返回的信息描述如下:

比特 序號 意義
000000 0 元素一致
000001 1 節點在不同的文檔(或者一個在 文檔之外)
000010 2 節點 B 在節點 A 之前
000100 4 節點 A 在節點 B 之前
001000 8 節點 B 包含節點 A
010000 16 節點 A 包含節點 B
100000 32 瀏覽器的私有使用

通過上面我們就可以理解為什麼要寫成a.compareDocumentPosition(b) & 16因為如果節點 A 包含節點 B,就會返回16,16&16=1,其他的情況結果都會0。

獲取兼容性性的relatedTarget

為瞭兼容各種瀏覽器,參考jquery源碼,寫出如下代碼,來獲取mouseover和mouseout事件目標節點的相關節點的屬性relatedTarget。

function getRelated(e){
        var related;
        var type=e.type.toLowerCase();//這裡獲取事件名字
        if(type=='mouseover'){
            related=e.relatedTarget||e.fromElement
        }else if(type='mouseout'){
            related=e.relatedTarget||e.toElement
        }
        return related; 
    }

改進mouseover和mouseout

改進mouseover和mouseout以實現改進mouseenter與mouseleave效果,所有代碼如下。


 

<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script><script type="text/javascript">
//判斷兩個a中是否包含b
function contains(a,b){
return a.contains ? a != b && a.contains(b) :!!(a.compareDocumentPosition(b) & 16);
}
function getRelated(e){
var related;
var type=e.type.toLowerCase();//這裡獲取事件名字
if(type=='mouseover'){
related=e.relatedTarget||e.fromElement
}else if(type='mouseout'){
related=e.relatedTarget||e.toElement
}
return related;
}

$(function(){
$("#id1").mouseover(function(e){
//判斷鼠標從哪移到id1上面
var related=getRelated(e);
//如果related是id1的子元素id2,即從子元素id2移動到id1,或是related為id1,即從id1移動到其子元素id2上面,則不進行任何操作,否則進行相應的操作
if(this!=related && !contains(this,related)){
console.log('mouseover');
}

}).mouseout(function(e){
//判斷鼠標要從id1上面移動到哪去?
var related=getRelated(e);
//如果related是id1,即當id1從其子元素移動到id1上,或是related是id2,即從id1上移動到其子元素,不進行任何操作,否則進行相應的操作
if(this !=related && !contains(this,related)){
console.log('mouseout');
}
});
});

</script>

測試,鼠標移動路線如下圖路線
這裡寫圖片描述
由控制臺可以很看出,此刻的mouseover和mouseout已經完全具備mouseenter與mouseleave效果效果。
代碼的封裝
如果每次進行這樣的操作,都需要加載Jquery或是寫很多代碼,將是件繁瑣的事,為瞭便於以後操作,進行瞭適當的封裝,模擬Jquery,生成自己的mouseenter與mouseleave。代碼封裝到dqMouse.js文件中,如下:

(function(w){
    var dqMouse = function(obj) {
        // 函數體
        return new dqMouse.fn.init(obj);
    }
    dqMouse.fn = dqMouse.prototype = {
        // 擴展原型對象
        obj:null,
        dqMouse: "1.0.0",
        init: function(obj) {
            this.obj=obj;
            return this;
        },
        contains:function(a,b) {
            return a.contains ? a != b && a.contains(b) :!!(a.compareDocumentPosition(b) & 16);
        },
        getRelated:function(e) {
            var related;
            var type=e.type.toLowerCase();//這裡獲取事件名字
            if(type=='mouseover'){
                related=e.relatedTarget||e.fromElement
            }else if(type='mouseout'){
                related=e.relatedTarget||e.toElement
            }
            return related; 
        },
        over:function(fn){
            var obj=this.obj;
            var _self=this;
            obj.onmouseover=function(e){                
                var related=_self.getRelated(e); 
                if(this!=related && !_self.contains(this,related)){
                    fn();
                }
            }
            return _self;
        },
        out:function(fn){
            var obj=this.obj;
            var _self=this;
            obj.onmouseout=function(e){
                var related=_self.getRelated(e); 
                if(obj!=related && !_self.contains(obj,related)){
                    fn();
                }
            }
            return _self;
        }

    }   
    dqMouse.fn.init.prototype = dqMouse.fn;
    window.dqMouse = window.$$= dqMouse;
})(window);

調用的源文件如下:
 

<script type="text/javascript">
    var id1=document.getElementById('id1');

    $$(id1).over(function(){
        console.log('mouseover');
    }).out(function(){
        console.log('mouseout');
    }); 
    </script>

經過測試okey。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *