February 11, 2024Programming

JavaScript Scroll Event的坑:從冒泡說起

近期在工作上遇上一個關於Scroll Event奇怪的問題。

具體是這樣,客戶要求網站新建一個頁面,頁面內一些特定元素要進入視區才以漸變的方式顯示出來。

本來這個要求還是挺簡單的,馬上就完成了,只要一開始把特定元素隱藏起來,然後監察scroll event 直到檢查到該元素進入視區就可以了。

用jquery寫的版本如下:

$.fn.isInViewport = function () { var elementTop = $(this).offset().top; var elementBottom = elementTop + $(this).outerHeight(); var viewportTop = $(window).scrollTop(); var viewportBottom = viewportTop + $(window).height(); return elementBottom > viewportTop && elementTop < viewportBottom; }; $(document).ready(function () { onScroll(); $(window).scroll(onScroll); // callback function onScroll() { $(".fadein:not(.active)").each(function (i) { if ($(this).isInViewport()) { $(this).addClass("active"); } }); } });

然而,實際上相關元素在進入視區後卻毫無反應。 經過一番調試發現在window上根本監察不到scroll event!

一般情況下,絕大多數DOM事件都可以在位於DOM結構最上層的window/document節點(Node)上監察到,首先是因為很多事件本身就在window節點上發生,其次是因為DOM具有的冒泡機制(bubbling)使發生在下層節點的事件同時也會在window節點上發生。

其緣由可參考《JavaScript高級程序設計》對冒泡的一段介紹:

在第四代 Web 瀏覽器(IE4 和 Netscape Communicator 4)開始開發時,開發團隊碰到了一個有意思的問題:頁面哪個部分擁有特定的事件呢?要理解這個問題,可以在一張紙上畫幾個同心圓。把手指放到圓心上,則手指不僅是在一個圓圈里,而且是在所有的圓圈里。兩家瀏覽器的開發團隊都是以同樣的 方式看待瀏覽器事件的。當你點擊一個按鈕時,實際上不光點擊了這個按鈕,還點擊了它的容器以及整個頁面。 事件流描述了頁面接收事件的順序。結果非常有意思,IE 和 Netscape 開發團隊提出了幾乎完全相反的事件流方案。IE 將支持事件冒泡流,而 Netscape Communicator 將支持事件捕獲流。 IE 事件流被稱為事件冒泡,這是因為事件被定義為從最具體的元素(文檔樹中最深的節點)開始觸發,然後向上傳播至沒有那麽具體的元素(文檔)。 事件流描述了頁面接收事件的順序。結果非常有意思,IE 和 Netscape 開發團隊提出了幾乎完全相反的事件流方案。IE 將支持事件冒泡流,而 Netscape Communicator 將支持事件捕獲流。 IE 事件流被稱為事件冒泡,這是因為事件被定義為從最具體的元素(文檔樹中最深的節點)開始觸發,然後向上傳播至沒有那麽具體的元素(文檔)。 當一個事件(如點擊事件)發生在一個元素上,接著也會在它的父元素上發生,一直按著子到父的順序發生,直到最頂層的window節點也可以監察到這個事件的發生。

接著説回Scroll事件,scroll當然可以在很多元素上發生,但整個頁面的scroll一般都在window/document層次上發生,然而這次的網站似乎是用一個名為vux的組件庫開發,整個頁面的scroll事件並非在window上發生,而是在裡面的id 為vux_view_box_body的div元素。

如果scroll事件能夠冒泡,在window上監察事件當然也沒有問題,然而scroll事件卻是少有的不會冒泡的事件!(大概是因為scroll事件能夠被極高頻觸發)

因此最簡單的解決方法就是把scroll 事件的listener掛在直接觸發事件的元素上了。如果沒有相關文檔可供查看的話,大概要自己慢慢調試了。