IntersectionObserver對象
IntersectionObserver
對象,從屬于Intersection Observer API
,提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗viewport
交叉狀態的方法,祖先元素與視窗viewport
被稱為根root
,也就是說IntersectionObserver API
,可以自動觀察元素是否可見,由于可見visible
的本質是,目標元素與視口產生一個交叉區,所以這個API
叫做交叉觀察器,兼容性https://caniuse.com/?search=IntersectionObserver
。
描述
IntersectionObserver
解決了一個長期以來Web
的問題,觀察元素是否可見,這個可見visible
的本質是,目標元素與視口產生一個交叉區,所以這個API
叫做交叉觀察器。
要檢測一個元素是否可見或者兩個元素是否相交并不容易,很多解決辦法不可靠或性能很差。現在很多需求下都需要用到相交檢測,例如圖片懶加載、內容無限滾動、檢測元素的曝光情況、可視區域播放動畫等等,相交檢測通常要用到onscroll
事件監聽,并且可能需要頻繁調用Element.getBoundingClientRect()
等方法以獲取相關元素的邊界信息,事件監聽和調用Element.getBoundingClientRect
都是在主線程上運行,因此頻繁觸發、調用可能會造成性能問題,這種檢測方法極其怪異且不優雅。
Intersection Observer API
會注冊一個回調函數,每當被監視的元素進入或者退出另外一個元素時或viewport
,或者兩個元素的相交部分大小發生變化時,該回調方法會被觸發執行,這樣網站的主線程不需要再為了監聽元素相交而辛苦勞作,瀏覽器會自行優化元素相交管理,注意Intersection Observer API
無法提供重疊的像素個數或者具體哪個像素重疊,他的更常見的使用方式是當兩個元素相交比例在N%
左右時,觸發回調,以執行某些邏輯。
const io = new IntersectionObserver(callback, option);// 開始觀察
io.observe(document.getElementById("example"));
// 停止觀察
io.unobserve(element);
// 關閉觀察器
io.disconnect();
- 參數
callback
,創建一個新的IntersectionObserver
對象后,當其監聽到目標元素的可見部分穿過了一個或多個閾thresholds
時,會執行指定的回調函數。 - 參數
option
,IntersectionObserver
構造函數的第二個參數是一個配置對象,其可以設置以下屬性:threshold
屬性決定了什么時候觸發回調函數,它是一個數組,每個成員都是一個門檻值,默認為[0]
,即交叉比例intersectionRatio
達到0
時觸發回調函數,用戶可以自定義這個數組,比如[0, 0.25, 0.5, 0.75, 1]
就表示當目標元素0%
、25%
、50%
、75%
、100%
可見時,會觸發回調函數。root
屬性指定了目標元素所在的容器節點即根元素,目標元素不僅會隨著窗口滾動,還會在容器里面滾動,比如在iframe
窗口里滾動,這樣就需要設置root
屬性,注意,容器元素必須是目標元素的祖先節點。rootMargin
屬性定義根元素的margin
,用來擴展或縮小rootBounds
這個矩形的大小,從而影響intersectionRect
交叉區域的大小,它使用CSS
的定義方法,比如10px 20px 30px 40px
,表示top
、right
、bottom
和left
四個方向的值。
- 屬性
IntersectionObserver.root
只讀,所監聽對象的具體祖先元素element
,如果未傳入值或值為null
,則默認使用頂級文檔的視窗。 - 屬性
IntersectionObserver.rootMargin
只讀,計算交叉時添加到根root
邊界盒bounding box
的矩形偏移量,可以有效的縮小或擴大根的判定范圍從而滿足計算需要,此屬性返回的值可能與調用構造函數時指定的值不同,因此可能需要更改該值,以匹配內部要求,所有的偏移量均可用像素pixel
、px
或百分比percentage
、%
來表達,默認值為0px 0px 0px 0px
。 - 屬性
IntersectionObserver.thresholds
只讀,一個包含閾值的列表,按升序排列,列表中的每個閾值都是監聽對象的交叉區域與邊界區域的比率,當監聽對象的任何閾值被越過時,都會生成一個通知Notification
,如果構造器未傳入值,則默認值為0
。 - 方法
IntersectionObserver.disconnect()
,使IntersectionObserver
對象停止監聽工作。 - 方法
IntersectionObserver.observe()
,使IntersectionObserver
開始監聽一個目標元素。 - 方法
IntersectionObserver.takeRecords()
,返回所有觀察目標的IntersectionObserverEntry
對象數組。 - 方法
IntersectionObserver.unobserve()
,使IntersectionObserver
停止監聽特定目標元素。
此外當執行callback
函數時,會傳遞一個IntersectionObserverEntry
對象參數,其提供的信息如下。
time:
可見性發生變化的時間,是一個高精度時間戳,單位為毫秒。target:
被觀察的目標元素,是一個DOM
節點對象。rootBounds:
根元素的矩形區域的信息,是getBoundingClientRect
方法的返回值,如果沒有根元素即直接相對于視口滾動,則返回null
。boundingClientRect:
目標元素的矩形區域的信息。intersectionRect:
目標元素與視口或根元素的交叉區域的信息。intersectionRatio:
目標元素的可見比例,即intersectionRect
占boundingClientRect
的比例,完全可見時為1
,完全不可見時小于等于0
。
應用
實現一個使用IntersectionObserver
的簡單示例,兩個方塊分別可以演示方塊1
是否在屏幕可見區域內以及方塊2
是否在方塊1
的相對可見交叉區域內,另外可以使用IntersectionObserver
可以進行首屏渲染的優化,可以參考https://github.com/WindrunnerMax/EveryDay/blob/master/Vue/Vue%E9%A6%96%E5%B1%8F%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E7%BB%84%E4%BB%B6.md
。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style> body{margin: 0;padding: 0;height: 100vh;width: 100vw;overflow-x: hidden;}.flex{display: flex;}.top-fixed{top: 0;position: fixed;}.placeholder1{width: 100%;}#box1{height: 200px; overflow-y: auto; border: 1px solid #aaa; width: 60%;}.box1-placeholder{height: 105vh;}#box2{height: 100px; background-color: blue; margin-top: 300px; width: 60%;}.box2-placeholder{height: 205px;}</style>
</head>
<body><section class="flex top-fixed"><div class="flex">BOX1:</div><div class="flex" id="box1-status">invisible</div><div class="flex"> BOX2:</div><div class="flex" id="box2-status">invisible</div></section><div class="box1-placeholder"></div><div id="box1"><div class="box2-placeholder"></div><div id="box2"></div> <div class="box2-placeholder"></div></div><div class="box1-placeholder"></div></body>
<script>(function(){const box1 = document.querySelector("#box1");const box2 = document.querySelector("#box2");const box1Status = document.querySelector("#box1-status");const box2Status = document.querySelector("#box2-status");const box1Observer = new IntersectionObserver(entries => {entries.forEach(item => {// `intersectionRatio`為目標元素的可見比例,大于`0`代表可見if (item.intersectionRatio > 0) {box1Status.innerText = "visible";}else{box1Status.innerText = "invisible";}});}, {root: document});const box2Observer = new IntersectionObserver(entries => {entries.forEach(item => {// `intersectionRatio`為目標元素的可見比例,大于`0`代表可見if (item.intersectionRatio > 0) {box2Status.innerText = "visible";}else{box2Status.innerText = "invisible";}});}, {root: box1});box1Observer.observe(box1);box2Observer.observe(box2);})();
</script>
</html>
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
https://www.jianshu.com/p/eadd83d794c8
https://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html
https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver