javascript中mouseenter與mouseover的異同
不知道大家在面試或者工作過程中有沒有被 mouseover 和 mouseenter (對應(yīng)的是 mouseout 和 mouseleave )事件所困擾。自己之前在面試的時候就有被問到諸如mouseover和mouseenter事件的異同之類的問題?當(dāng)時沒有答出來,一直也對這兩個事件有點模糊不清,趁著最近正在讀 zepto源碼 ,準(zhǔn)備寫一篇這方面的文章,如果有錯誤,請大家指正。
mouseenter與mouseover的異同?
要說清楚mouseenter與mouseover有什么不同,也許可以從兩方面去講。
是否支持冒泡 事件的觸發(fā)時機
先來看一張圖,對這兩個事件有一個簡單直觀的感受。
再看看官網(wǎng)對mouseenter的解釋
mouseenter | onmouseenter event
The event fires only if the mouse pointer is outside the boundaries of the object and the user moves the mouse pointer inside the boundaries of the object. If the mouse pointer is currently inside the boundaries of the object, for the event to fire, the user must move the mouse pointer outside the boundaries of the object and then back inside the boundaries of the object.
大概意思是說:當(dāng)鼠標(biāo)從元素的邊界之外移入元素的邊界之內(nèi)時,事件被觸發(fā)。而當(dāng)鼠標(biāo)本身在元素邊界內(nèi)時,要觸發(fā)該事件,必須先將鼠標(biāo)移出元素邊界外,再次移入才能觸發(fā)。(英語比較渣:no_mouth:,湊合看哈)
Unlike the onmouseover event, the onmouseenter event does not bubble.
大概意思是:和mouseover不同的是,mouseenter不支持事件冒泡 (英語比較渣:no_mouth:,湊合看哈)
由于mouseenter不支持事件冒泡,導(dǎo)致在一個元素的子元素上進入或離開的時候會觸發(fā)其mouseover和mouseout事件,但是卻不會觸發(fā)mouseenter和mouseleave事件
我們用一張動圖來看看他們的區(qū)別(或者點擊該鏈接體驗)。

我們給左右兩邊的ul分別添加了 mouseover 和 mouseenter 事件,當(dāng)鼠標(biāo)進入左右兩邊的ul時, mouseover 和 mouseenter 事件都觸發(fā)了,但是當(dāng)移入各自的子元素li的時候,觸發(fā)了左邊ul上的mouseover事件,然而右邊ul的mouseenter事件沒有被觸發(fā)。
造成以上現(xiàn)象本質(zhì)上是 mouseenter 事件不支持冒泡所致。
如何模擬mouseenter事件。
可見mouseover事件因其具有冒泡的性質(zhì),在子元素內(nèi)移動的時候,頻繁被觸發(fā),如果我們不希望如此,可以使用mouseenter事件代替之,但是早期只有ie瀏覽器支持該事件,雖然現(xiàn)在大多數(shù)高級瀏覽器都支持了mouseenter事件,但是難免會有些兼容問題,所以如果可以自己手動模擬,那就太好了。
關(guān)鍵因素: relatedTarget 要想手動模擬mouseenter事件,需要對mouseover事件觸發(fā)時的事件對象event屬性relatedTarget了解。
relatedTarget事件屬性返回與事件的目標(biāo)節(jié)點相關(guān)的節(jié)點。 對于mouseover事件來說,該屬性是鼠標(biāo)指針移到目標(biāo)節(jié)點上時所離開的那個節(jié)點。 對于mouseout事件來說,該屬性是離開目標(biāo)時,鼠標(biāo)指針進入的節(jié)點。 對于其他類型的事件來說,這個屬性沒有用。
重新回顧一下文章最初的那張圖,根據(jù)上面的解釋,對于ul上添加的mouseover事件來說,relatedTarget只可能是
ul的父元素wrap(移入ul時,此時也是觸發(fā)mouseenter事件的時候, 其實不一定,后面會說明 ), 或者ul元素本身(在其子元素上移出時), 又或者是子元素本身(直接從子元素A移動到子元素B)。
根據(jù)上面的描述,我們可以對relatedTarget的值進行判斷:如果值不是目標(biāo)元素,也不是目標(biāo)元素的子元素,就說明鼠標(biāo)已移入目標(biāo)元素而不是在元素內(nèi)部移動。
條件1: 不是目標(biāo)元素 很好判斷 e.relatedTarget !== target(目標(biāo)元素)
條件2:不是目標(biāo)元素的子元素,這個應(yīng)該怎么判斷呢?
ele.contains
這里需要介紹一個新的api node.contains(otherNode) , 表示傳入的節(jié)點是否為該節(jié)點的后代節(jié)點, 如果 otherNode 是 node 的后代節(jié)點或是 node 節(jié)點本身.則返回true , 否則返回 false
用法案例
<ul class="list"> <li class="item">1</li> <li>2</li> </ul> <div class="test"></div>
let $list = document.querySelector('.list')
let $item = document.querySelector('.item')
let $test = document.querySelector('.test')
$list.contains($item) // true
$list.contains($test) // false
$list.contains($list) // true
那么利用contains這個api我們便可以很方便的驗證條件2,接下來我們封裝一個 contains(parent, node) 函數(shù),專門用來判斷 node 是不是 parent 的子節(jié)點
let contains = function (parent, node) {
return parent !== node && parent.contains(node)
}
用我們封裝過后的 contains 函數(shù)再去試試上面的例子
contains($list, $item) // true contains($list, $test) // false contains($list, $list) // false (主要區(qū)別在這里)
這個方法很方便地幫助我們解決了模擬mouseenter事件中的條件2,但是悲催的 ode.contains(otherNode) ,具有瀏覽器兼容性,在一些低級瀏覽器中是不支持的,為了做到兼容我們再來改寫一下contains方法
let contains = docEle.contains ? function (parent, node) {
return parent !== node && parent.contains(node)
} : function (parent, node) {
let result = parent !== node
if (!result) { // 排除parent與node傳入相同的節(jié)點
return result
}
if (result) {
while (node && (node = node.parentNode)) {
if (parent === node) {
return true
}
}
}
return false
}
說了這么多,我們來看看用 mouseover 事件模擬 mouseenter 的最終代碼
// callback表示如果執(zhí)行mouseenter事件時傳入的回調(diào)函數(shù)
let emulateEnterOrLeave = function (callback) {
return function (e) {
let relatedTarget = e.relatedTarget
if (relatedTarget !== this && !contains(this, relatedTarget)) {
callback.apply(this, arguments)
}
}
}
好了,我們已經(jīng)通過mouseove事件完整的模擬了mouseenter事件,但是反過頭來看看
對于ul上添加的mouseover事件來說,relatedTarget只可能是
ul的父元素wrap(移入ul時,此時也是觸發(fā)mouseenter事件的時候, 其實不一定,后面會說明 ), 或者ul元素本身(在其子元素上移出時), 又或者是子元素本身(直接從子元素A移動到子元素B)。
我們通過排查2和3,最后只留下1,也就是mouseenter與mouseover事件一起觸發(fā)的時機。既然這樣我們?yōu)槭裁床幌襁@樣判斷呢?
target.addEventListener('mouseover', function (e) {
if (e.relatedTarget === this.parentNode) {
// 執(zhí)行mouseenter的回調(diào)要做的事情
}
}, false)
這樣不是更加簡單嗎?,何必要折騰通過排查2和3來做?
原因是,target的父元素有一定的占位空間的時后,我們這樣寫是沒有太大問題的,但是反之,這個時候 e.relatedTarget 就可能是target元素的父元素,又祖先元素中的某一個。我們無法準(zhǔn)確判斷e.relatedTarget到底是哪個元素。所以通過排除2和3應(yīng)該是個更好的選擇。
用mouseout模擬mouseleave事件
當(dāng)mouseout被激活時,relatedTarget表示鼠標(biāo)離開目標(biāo)元素時,進入了哪個元素,我們同樣可以對relatedTarget的值進行判斷:如果值不是目標(biāo)元素,也不是目標(biāo)元素的子元素,就說明鼠標(biāo)已移出目標(biāo)元素
我們同樣可以用上面封裝的函數(shù)完成
// callback表示如果執(zhí)行mouseenter事件時傳入的回調(diào)函數(shù)
let emulateEnterOrLeave = function (callback) {
return function (e) {
let relatedTarget = e.relatedTarget
if (relatedTarget !== this && !contains(this, relatedTarget)) {
callback.apply(this, arguments)
}
}
}
結(jié)尾
文中也許有些觀點不夠嚴謹,歡迎大家拍磚。
- 淺談JQ中mouseover和mouseenter的區(qū)別
- 關(guān)于事件mouseover ,mouseout ,mouseenter,mouseleave的區(qū)別
- 快速移動鼠標(biāo)觸發(fā)問題及解決方法(ECharts外部調(diào)用保存為圖片操作及工作流接線mouseenter和mouseleave)
- Jquery利用mouseenter和mouseleave實現(xiàn)鼠標(biāo)經(jīng)過彈出層且可以點擊
- 為非IE瀏覽器添加mouseenter,mouseleave事件的實現(xiàn)代碼
- 跨瀏覽器的 mouseenter mouseleave 以及 compareDocumentPosition的使用說明
- javascript 兼容FF的onmouseenter和onmouseleave的代碼
相關(guān)文章
JS實現(xiàn)選中當(dāng)前菜單后高亮顯示的導(dǎo)航條效果
這篇文章主要介紹了JS實現(xiàn)選中當(dāng)前菜單后高亮顯示的導(dǎo)航條效果,涉及JavaScript針對頁面元素的遍歷及樣式動態(tài)操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
理解JAVASCRIPT中hasOwnProperty()的作用
JavaScript中hasOwnProperty函數(shù)方法是返回一個布爾值,指出一個對象是否具有指定名稱的屬性2013-06-06
PHP abstract與interface之間的區(qū)別
本文是對PHP中abstract與interface之間的區(qū)別進行了詳細的分析介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-11-11
JavaScript將頁面表格導(dǎo)出為Excel的具體實現(xiàn)
如何將頁面表格導(dǎo)出為Excel,這在日常工作中很常見,下面為大家詳細的介紹下使用JavaScript是如何實現(xiàn)的2013-12-12
分享JavaScript獲取網(wǎng)頁關(guān)閉與取消關(guān)閉的事件
這篇文章主要介紹了JavaScript獲取網(wǎng)頁關(guān)閉與取消關(guān)閉的事件,有需要的朋友可以參考一下2013-12-12

