IntersectionObserver帶來X%的性能提升

原文,https://zhuanlan.zhihu.com/p/88767748

前言

廣告打點和圖片懶加載是兩個非常常見的需求,最簡單的實現方式通過監聽scroll事件,但是大家都知道scroll事件的監聽回調是同步執行的,這樣就會影響JS主線程的UI渲染。而我們的主角IntersectionObserver即將登場。文章里用observer代替IntersectionObserver。

首先來一段官方宣言(MDN),

Intersection Observer API提供了一種異步觀察目標元素與祖先元素或者頂級文檔viewport的交集中的變化的方法。

重點:異步交集變化

用途

再來一段官方宣言(MDN)介紹它的用途。如果大家對我引用官方描述不滿意的話,這里有地址可以直接查看,Intersection Observer API

  • 當頁面滾動時,懶加載圖片或其他內容。
  • 實現“可無限滾動”網站,也就是當用戶滾動網頁時直接加載更多內容,無需翻頁。
  • 為計算廣告收益,檢測其廣告元素的曝光情況。
  • 根據用戶是否已滾動到相應區域來靈活開始執行任務或動畫。

用法

它的用法也很簡單。

const callback = entries => {  
// ...  
}; 
 
const options = {  
  root: document.querySelector('#scroll'),
  rootMargin: '0px',  
  threshold: [0]  
};  

const observer = new IntersectionObserver(callback, options);  

const ele = document.querySelector('.img');  

observer.observe(ele); 

root 目標元素所在的容器節點,如果不指定根節點,默認文檔為根節點。

rootMargin 圍繞根元素的邊距,類似于css的margin屬性。注意這個單位為px

threshold 相交的比例,既可以是一個數字也可以是一個數組。取值在0-1之間。

const callback = (entries, observer) => {   
  entries.forEach(entry => {  
    //   entry.boundingClientRect  
    //   entry.intersectionRatio  
    //   entry.intersectionRect  
    //   entry.isIntersecting  
    //   entry.rootBounds  
    //   entry.target  
    //   entry.time  
  });  

};

entry.boundingClientRect 目標元素的區域信息,getBoundingClientRect()的返回值

entry.intersectionRatio 目標元素的可見比率

entry.intersectionRect 目標元素與根元素交叉的區域信息

entry.isIntersecting 是否進入可視區域

entry.rootBounds 根元素的矩形區域信息

entry.target 被觀察的目標,是一個DOM節點

entry.time 可見性發生變化的時間,相交發生時距離頁面打開時的毫秒數.精度為微秒。

上面一段基本也是基本來自于MDN的解釋,你問我這篇文章做了什么,對,我就是copy&paste搬運工。

可執行方法

observe,創建一個觀察對象。

const target = document.querySelector('#listItem');  
observer.observe(target);  

unobserver,取消觀察對象。

observer.unobserve(target);

takeRecords,返回一個IntersectionObserverEntry對象數組。

const records = observer.takeRecords(); 

每個對象的目標元素都包含每次相交的信息。takeRecords是同步的,IntersectionObserver的回調是異步的,且IntersectionObserver的回調時間最大是100ms,所以回調會在1-100ms內執行。如果執行了異步回調,takeRecords()就會返回空數據組,如果同步先執行,則回調不執行。使用場景較少。

disconnect,終止對所有目標元素可見性變化的觀察。

observer.disconnect(); 

從scroll切換observer

https://github.com/carrollcai/scroll-demo

上面是我寫的一個Demo,scroll和observer實現相同的元素出現在試視圖,執行回調的效果。

視頻里demo的效果。

下面我簡單做一個實驗,實現圖片預加載。如果B距離根文檔還有300px,那就與根文檔形成交集。代碼如下

<body>
  <A>
    <div style="height: 100vh; width: 100%"></div>
    <B></B>
  </A>
</body>
const callback = entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('B相交了');
    }
  })
}

const options = {
  rootMargin: '0px 0px 300px 0px'
}

const ob = new IntersectionObserver(callback, options);
ob.observe(B);

當observer沒有設置root,默認根文檔就是觀察區域。如果這時你設置A為滾動區間(overflow: auto; height: 100vh),它就會形成一個獨立的層,這會直接導致rootMargin失效,因為rootMargin為0px 0px 300px 0px時,只是將root的下邊界增加300px,而A已經形成自己的BFC(這里是我的理解,有錯誤請指出),所以rootMargin的設置會失效。最好的辦法是將A的滾動區間去掉,因為這樣觀察區域是根元素,js事件機制是先捕獲再冒泡,在根元素上捕獲和冒泡是同時發生的,等于沒有這個過程,性能最優。最小的改動,是將觀察區域root設置為A,這樣rootMargin也能生效。我為什么要舉這個例子,因為我之前想實現預加載圖片,即離窗口還有一段距離時,加載圖片。我設置觀察區域為根元素,但是觀察目標的父層又設置了滾動區域,直接導致rootMargin失效,一度讓我懷疑是API有問題,經過知乎一位大佬(id:紫云飛)幫助。它也寫過一篇介紹observer的文章,2016年時寫的。IntersectionObserver API

性能測試

有一位大師說過,我懶得查名字了,you can't optimize what you can't measure。當然我也懶得做性能測試了,所以文章的標題,由IntersectionObserver帶來100%的性能提升變成IntersectionObserver帶來X%的性能提升,X可能為負。

總結

observer api安卓手機2016年就支持了,ios2018年底才支持,好在有polyfill。我已經在生產上使用這個api,目前道路暢通,暫無阻礙。


寫作時間:20191027

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容