下拉選單「點選外面關閉」的終極解決方案

Va007發表於2018-08-02

場景:react專案。

一般遇到這種問題網上的說法都是:

  1. 給點選開啟下拉選單的Dom元素方法中新增e.stopPropagation()阻止事件冒泡
  2. 再給document新增一個監聽點選的事件:

    document.addEventListener('click', this.offDropMenu, false)複製程式碼

可是這種一般只能適用於普通的下拉導航選單,點選選單就切換頁面然後選單就消失的這種情景,比如下面這種:

下拉選單「點選外面關閉」的終極解決方案

但是遇到這種複雜的選單,當使用者點選到選單內部時不想讓選單消失該怎麼辦?

下拉選單「點選外面關閉」的終極解決方案

網上千篇一律的帖子又會說了,在下拉選單的最外層Div再新增一個addEventListener監聽點選事件,事件內只寫一個e.stopPropagation()。

下拉選單「點選外面關閉」的終極解決方案

確實,點選選單內部是不跳轉了,但是裡面的事件也全被阻止掉了呀!

下拉選單「點選外面關閉」的終極解決方案點選都沒反應了……

下拉選單「點選外面關閉」的終極解決方案難道要我給每個子元素都新增阻止冒泡?

我思索片刻?,想到了一個很原始很暴力的方法,就是根據滑鼠點選的位置來避開選單?

下拉選單「點選外面關閉」的終極解決方案(⚠️此程式碼可能會造成大神的不適~)

然後看著功能還湊合,也實現了業務需求,就那麼放著了。

直到後來某一天,我發現了Dom元素有一個原生的方法contains()就是為了驗證知道某個節點是不是另一個節點的後代,返回一個布林值:

document.documentElement.contains(document.body)  // true
複製程式碼

這簡直就是神器呀!剛好適用於這個場景,管你冒不冒泡:

下拉選單「點選外面關閉」的終極解決方案

但是還遇到一個問題,那就是e.stopPropagation()無法阻止冒泡。

下拉選單「點選外面關閉」的終極解決方案

參考React的事件系統,如果出於某些原因想使用瀏覽器原生事件,可以使用 nativeEvent 屬性獲取。

e.nativeEvent.stopImmediatePropagation() 替換掉e.stopPropagation() 可以達到效果。

下面進入探索環節,經查閱資料,得出以下結論:
1. React為了提高效率,把事件都委託給了document,所以 e.stopPropagation() 並非是不能阻止冒泡,而是等他阻止冒泡的時侯,事件已經傳遞給document了,沒東西可阻止了。可以通過在document.body上繫結 alert(3),直觀的瞭解這一點,3 是優先於 1 彈出的。
2. e.stopPropagation()不行,瀏覽器支援一個好東西,e.stopImmediatePropagation() 他不光阻止冒泡,還能阻止在當前事件觸發元素上,觸發其它事件。這樣即使你都繫結到document上也阻止不了我了吧。
3. 這樣做還不行,React對原生事件封裝,提供了很多好東西,但也省略了某些特性。e.stopImmediatePropagation() 就是被省略的部分,然而,他給了開口:e.nativeEvent ,從原生的事件物件裡找到stopImmediatePropagation(),完活。

以上來自CNblog

stopImmediatePropagation和stopPropagation的區別

  • stopImmediatePropagation :
阻止事件流中當前節點的和所有後續節點的事件監聽器的執行。即影響當前結點的事件監聽器。
  • stopPropagation:
阻止事件流中當前節點的所有後續節點的事件監聽器的執行。即不會影響當前節點(currentTarget)的任何事件監聽。

舉個?:給一個元素新增三個點選事件(註冊順序,就是程式碼的執行順序)

下拉選單「點選外面關閉」的終極解決方案

  1. 如果是e.stopPropagation(),執行結果為依次彈出 1,2,2.1,3。因為不會影響到當前節點的事件
  2. 若是e.stopImmediatePropagation(),結果則為 1,2,2.1。


總結:只是一個很好用的contains()方法,記得最開始學習的時候是看過這個屬性,歸根到底還是自己基礎沒打牢!


相關文章