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