原文,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