問題描述
我希望當鼠標移動到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。