寫了這麼久 js應用 我居然不知道這兩個事件 於是 去google搜索了一番. 才發現這兩個事件 是如此的優秀 且好用... 但搜索過程中 發現 好多人 似乎不太明白這兩個事件 和mouseover mouseout 真正的區別 和用途.. 並且看到google中搜索得到的 一些朋友 實現的 跨浏覽器 解決方案. 覺得似乎有些繁瑣...所以產生了寫一篇blog 把這玩意 說透徹的沖動... 好啦.我們進入正題.
對於 mouseover 和mouseenter 兩個事件 最大的區別就是 mouseenter 是 不冒泡的事件 ..這話怎麼理解呢?
<div id=="parent">
<div id="child"></div>
</div>
對於mouseover 時間來說 當鼠標從其他元素 移動到 child節點時發生 但此事件會冒泡 所以會導致 parent 也出發mouseover
如果我們對 parent注冊了 mouseover監聽. 則可能會產生一個什麼問題呢? 從 parent移動到child 同樣出發parent的mouseover 有時候我們不希望這樣的事情發生. 這時候 如果注冊的監聽 是mouseenter的話 無論鼠標從任何元素 移動到child時 只有child元素 發生mouseenter事件 而其祖宗節點 都不會因為冒泡 而觸發此事件...這就 使我們可以徹底放棄 我們以往為了 實現同樣的邏輯 又要對子節點禁止mouseover冒泡 或者又去判斷事件源對象 或判斷srcElement/relatedTarget 那樣麻煩的方案.
對於 mouseout 和mouseleave 也是如此 當鼠標從child 移出時 mouseout同樣會冒泡到 parent 從而觸發parent的 mouseout 二mouseleave 同樣無此問題.
知道了區別 剩下的事情就好辦多了. 遇到此類需求 我們一律mouseenter mouseleave就好..問題是 這玩意只有ie支持 怎麼辦呢?
我們只能 用mouseover 和mouseout來模擬 但是如果我們的模擬方案 太過復雜 那是在就意義不大了... 這時候我們就可以 借助 xml 方法compareDocumentPosition 來徹底解決這個問題
我在我的類庫中 封裝了一個方法 專門用來判斷 某個節點的位置 是否在另一個節點的子節點中...
ie可以用 parentNode.contains(childNode) 來判斷 這沒什麼好說的 childNode在parentNode DOM樹中存在 那麼就是true
而contains方法 ie專屬 那麼 我們就是借助 !!(node.compareDocumentPosition(node2) &16) 來實現同樣的效果.
那麼接下來 我們就來談談 compareDocumentPosition 方法 否則 你看到上面的 &16 一定會困惑無比...
compareDocumentPosition 方法在非ie浏覽器 都被實現到 節點對象的 中了 所以
node.compareDocumentPosition(node2) 的作用就是 比較 node節點與node2節點之間的位置關系..
他的返回值是一個number值...
一般來說 對我們有用的 是以下幾個值
1. 20 (2進制: 010100)
2. 10 (2進制: 001010)
3. 4 (2進制: 000100)
4. 2 (2進制: 000010)
5. 0 (2進制: 000000)
6. 2進100***的數...
那麼這些 20 10 4 2 0 是怎麼來的呢? 我們接著往下 看...
試試上 這個2進制算法 是專門用來解釋 兩個節點之間的關系的
這個 6位2進制數 才是根本所在
第6位 代表 兩個節點是否一個在DOM樹上一個不在 這個是針對整個DOM樹而言的.也就是說 如果兩個都不在 或兩個都在 則為0 否則為1
第5位 代表node是否是node2的父節點 如果是 則為1 否則為0
第4位 代表node是否是node2的子節點 如果是則為1 否則為0
第3位 代表node是否在node2的前面 如果是 則為1 否則為0 (注:如果node是node2的父節則node同時也看做在node2的前面)
第2位 代表node是否在node2的後面 如果是 則為1 否則為0 (注如果node是node2的子節點 則node同時也看做在node2的後面)
最後 如果 2 3 4 5 6 位 都為0 即 000000 說明 兩個節點 要麼同時在DOM樹上 要麼同時不在. 且 兩個節點 沒有任何關系 那麼只有一種可能 即兩個節點是同一個節點...也就是 node==node2
所以 node.compareDocumentPosition(node2) &16 位運算 的結果是什麼呢? 以上幾種可能的組合中只有 010100 &010000 以及 110*** &16 的結果是 010000 即返回16 其他情況 均返回 0 然後 用!! 吧number隱式轉型成boolean類型 我們就可以判斷出 node是否是 node2的父節點了...
所以 我們也可以使用 node2.compareDocumentPosition(node) && 8 來判斷node2 是不是 node的子節點 道理是同樣的
或者我們也可以直接 node.compareDocumentPosition(node2) ==20 來做判斷 這樣還可以省略 !! 做轉型..也是可以的.
到了這裡 聰明你的 一定發現 這玩意是什麼? 分明就是c#中 flag 標識枚舉 的用法...
c# 中 File.Attributes 枚舉 可能同時具備 n多屬性 比如一個文件 可以是 只讀的同時 還可以是 隱藏的 或者同時還可以是 共享的. 等等
那麼 用一個枚舉 值 如何確定 一個文件同時具備哪些屬性 又不產生沖突呢? 答案 於 compareDocumentPosition是一樣的...
我用js 實現了一個 類似邏輯 來管理 flag標識的類 來說明這個問題 代碼如下
// flag 類
function flag(sFlags) {
this._flags = {};
this._status = 0;
sFlags && this.initFlags(sFlags);
}
flag.prototype = {
constructor: flag,
initFlags: function(sFlags) {//sFlags "狀態1,狀態2,狀態3...... 初始化原始標識集合...
sFlags = sFlags.split(',');
for (var i = 0, len = sFlags.length; i < len; i++) this._flags[sFlags[i]] = 1 << i;
//這裡初始化標識的值 如果同時具備n種狀態 則 每種狀態的值一定是 000001 000010 000100 001000 010000 100000
},
setStatus: function(sFlags) { //sFlags "狀態1,狀態3......設置當前狀態
sFlags = sFlags.split(',');
this._status=0;
for (var i = 0, len = sFlags.length; i < len; i++) {
this._check[sFlags[i]];
this._status |= this._flags[sFlags[i]];
}
},
addStatus: function(sFlags) {//sFlags "狀態1,狀態3......檢查當前狀態標示 是否有 狀態1和狀態3 如果沒有則添加
sFlags = sFlags.split(',');
for (var i = 0, len = sFlags.length; i < len; i++) {
this._check(sFlags[i]);
if (this.hasStatus(sFlags[i])) continue; //判斷是否已經有這個狀態如果有 跳過.
this._status |= this._flags[sFlags[i]];
//當前的狀態值 與 允許的標識值 做 | 運算 即添加狀態
}
},
removeStatus: function(sFlags) {
sFlags = sFlags.split(',');
for (var i = 0, len = sFlags.length; i < len; i++) {
this._check(sFlags[i]);
if (!this.hasStatus(sFlags[i])) continue;
this._status ^= this._flags[sFlags[i]];
// 當前狀態值 與 要去掉的狀態值做 ^運算 即刪除狀態
}
},
clear: function() {
this._status = 0;
},
hasStatus: function(sFlags) {//sFlags "狀態1,狀態3.....狀態 n.檢查當前狀態標識 是否同時 具備狀態1和狀態3 以及狀態n
sFlags = sFlags.split(',');
for (var i = 0, len = sFlags.length; i < len; i++) {
this._check(sFlags[i]);
if ((this._status & this._flags[sFlags[i]]) != this._flags[sFlags[i]]) return false;
//當前狀態值 與輸入的狀態做 & 運算 如果返回值 不等於 改狀態 的標識值 則 return false .
//比如 010101 & 010000 返回010000則 說明當前標識值具備 010000這個狀態.
}
return true;
},
_check: function(sFlag) {
if (!sFlag in this._flags) throw new Error(" 當前 flag 中不存在" + sFlag + "標識");
//檢查當前輸入狀態字符串 是否是合法值.
}
}
用法:
var fileStatus=new flag('readOnly,hidden,otherStatus');
fileStatus.setStatus('readOnly,hidden');
alert(fileStatus.hasStatus('readOnly'))//true;
alert(fileStatus.hasStatus('hidden'))//true;
alert(fileStatus.hasStatus('otherStatus'))//false;
最後 我們回到正題 我們借助 compareDocumentPosition 來模擬 mouseenter mouseleave
DOM結構:
<div id="dd" style="background-color:#369;width:50%;height:50%;position:absolute;left:25%;top:25%;" >
<div style="background-color:#ff0;width:50%;height:50%;position:relative;left:25%;top:25%" >
<div style="background-color:#789;width:50%;height:50%;position:relative;left:25%;top:25%" >
<div style="background-color:#123;width:50%;height:50%;position:relative;left:25%;top:25%" >
<div style="background-color:#456;width:50%;height:50%;position:relative;left:25%;top:25%" >
</div>
</div>
</div>
</div>
</div>
js腳本:
var dd = document.getElementById('dd')
if (! +'\v1') {//ie
dd.onmouseenter = function() { alert(1); };
dd.onmouseleave = function() { alert(2); };
}
else {//others
dd.onmouseover = function(e) {
var t = e.relatedTarget;
var t2 = e.target;
this == t2 && t && !(t.compareDocumentPosition(this) & 8) && alert(1);
};
dd.onmouseout = function(e) {
var t = e.relatedTarget;
var t2 = e.target;
this == t2 && t && !(t.compareDocumentPosition(this) & 8) && alert(2);
};
}
大功告成!!!!!