一直以來(lái)我們要監(jiān)控2個(gè)元素的相對(duì)位置,總是比較麻煩的,而且之前也只能通過(guò)js以及每個(gè)元素的top值來(lái)控制,這也極易拖慢整個(gè)網(wǎng)站的性能。然而,隨著網(wǎng)頁(yè)的發(fā)展,對(duì)上述檢測(cè)的需求也隨之增加,多種情況下都需要用到元素交集變化的信息。如:
- 當(dāng)頁(yè)面滾動(dòng)時(shí),懶加載圖片或其他內(nèi)容。
- 實(shí)現(xiàn)”無(wú)限滾動(dòng)“功能頁(yè)面
- 可以統(tǒng)計(jì)一些廣告元素的曝光情況
- 根據(jù)用戶滾動(dòng)位置來(lái)控制執(zhí)行任務(wù)或者動(dòng)畫(huà)
相對(duì)于過(guò)去,我們?cè)跈z測(cè)交集時(shí),需要涉及到事件監(jiān)聽(tīng),以及對(duì)每個(gè)目標(biāo)元素執(zhí)行Element.getBoundingClientRect()方法來(lái)獲取所需信息。然后通過(guò)每個(gè)元素的信息來(lái)檢測(cè)元素的交叉。
而且在類似無(wú)限滾動(dòng)的場(chǎng)景下,我們不僅僅需要獲取一些元素的位置信息,更需要監(jiān)控例如scroll等事件,并且很多情況下,也需要依賴一些第三方庫(kù)來(lái)進(jìn)行監(jiān)控,并且在第三方庫(kù)中具體執(zhí)行了什么,我們并不知曉,這樣很是影響性能,并且得到的體驗(yàn)也不是特別友好。
那在這種背景下,我們有更好的方法嗎?
IntersectionObserver概念
Intersection Observer的出現(xiàn),解決了這個(gè)問(wèn)題,Intersection Observer API 會(huì)在瀏覽器注冊(cè)一個(gè)觀察者,并且可以設(shè)定據(jù)地要觀察的目標(biāo)(target),當(dāng)目標(biāo)元素(target)以及根元素或者指定的外層元素(root元素)相互交叉的時(shí)候觸發(fā)事件。
用法介紹
//首先我們要先創(chuàng)建觀察者
var observer = new IntersectionObserver(callback, options);
//接下來(lái)我們要設(shè)置具體觀察哪個(gè)目標(biāo)
let target = document.querySelector('#id');
observer.observe(target);
我們?cè)趤?lái)看看創(chuàng)建監(jiān)控函數(shù)時(shí)要傳遞的options中的參數(shù):
| 參數(shù)名 | 描述 | 類型 | 默認(rèn)值 |
|---|---|---|---|
| root | 指定根目錄,也就是當(dāng)目標(biāo)元素顯示在這個(gè)元素中時(shí)會(huì)觸發(fā)監(jiān)控回調(diào) | Dom元素 | null,即瀏覽器窗口 |
| rootMargin | 類似于css的margin,設(shè)定root元素的邊框區(qū)域。值與css的margin一樣“10px 10px 10px 10px” 對(duì)應(yīng)“top right bottom left" | String | 0 |
| threshold | 可以是單一的number也可以是一個(gè)number數(shù)組,這個(gè)值可以控制target元素進(jìn)入root元素中可見(jiàn)性超過(guò)的闕值,當(dāng)達(dá)到這個(gè)值則會(huì)出發(fā)函數(shù),并且我們也可以使用數(shù)據(jù)來(lái)讓元素在進(jìn)入時(shí)在不同的可見(jiàn)度返回多次值 | number或array | 0 |
實(shí)戰(zhàn)
我們先來(lái)看一個(gè)最簡(jiǎn)單的操作:
這是我們的html:
//我們創(chuàng)建三個(gè)容器,并且把第三個(gè)容器中的p定位目標(biāo)
<div class="con">
<div class="page red">
<p>我是第一頁(yè)</p>
</div>
<div id="scrollArea" class="page green">
<p>我是第二頁(yè)</p>
</div>
<div class="page blue">
<p class="listItem">我是第三頁(yè)</p>
</div>
</div>
接下來(lái)我們創(chuàng)建觀察者,以及設(shè)定要觀察的目標(biāo):
var observer = new IntersectionObserver((entries, observer) => {
alert("進(jìn)入");
}, {});
let target = document.querySelector('.listItem');
observer.observe(target);

我們來(lái)設(shè)定一下對(duì)應(yīng)的root,讓target的目標(biāo)換成一個(gè)指定的元素。
//我們?cè)O(shè)定一個(gè)外層容器,并且把這個(gè)容器先搞小一點(diǎn),方便查看:
var options = {
root: document.querySelector('.con')
}
var observer = new IntersectionObserver((entries, observer) => {
console.log("進(jìn)入");
}, options);
let target = document.querySelector('.listItem');
observer.observe(target);
我們可以指定對(duì)應(yīng)的被觀察者要進(jìn)入哪個(gè)root根容器中才會(huì)觸發(fā)回調(diào)。

接下來(lái)我們?cè)趤?lái)嘗試更復(fù)雜的去控制:
需要注意的是,當(dāng)我們這樣來(lái)設(shè)置rootMargin時(shí),會(huì)出現(xiàn)錯(cuò)誤:
var options = {
rootMargin: "100px 0 0 0"
}

雖然其規(guī)范與css得我margin一樣,但是當(dāng)值是0的時(shí)候,我們是無(wú)法直接使用0的,要添加上單位,如px、rem、em等,正確寫(xiě)法如下:
var options = {
rootMargin: "1
00px 0px 0px 0px"
}
我們來(lái)看一下這個(gè)圖,當(dāng)設(shè)置rootMargin時(shí),就相當(dāng)于把對(duì)應(yīng)的元素放大,如下圖,root為黃色區(qū)域,但是我們?cè)O(shè)定了rootMargin,實(shí)際上對(duì)于target來(lái)說(shuō),整個(gè)藍(lán)色區(qū)域都會(huì)認(rèn)定為root的監(jiān)控區(qū)域,當(dāng)target進(jìn)入藍(lán)色區(qū)域機(jī)會(huì)觸發(fā)回調(diào)。

讓我們來(lái)看看如何設(shè)定吧:
var options = {
root: document.querySelector('.con'),
rootMargin: "0px 0px 100px 0px"
}
var observer = new IntersectionObserver((entries, observer) => {
console.log("進(jìn)入");
}, options);
let target = document.querySelector('.listItem');
observer.observe(target);
注意:我們?cè)O(shè)置的margin是root的而不是target的,所以在一下的這個(gè)demo中,當(dāng)target往上滑動(dòng)時(shí),target具體root還差100px時(shí)就觸發(fā)了回調(diào),這個(gè)要設(shè)定為bottom而非top。

我們來(lái)看看threshold屬性:
threshold 此參數(shù)的范圍為0.0-1.0,并且我們可以設(shè)置一個(gè)number值,也可以設(shè)置一個(gè)number的Array數(shù)組,來(lái)觸發(fā)多次回調(diào)。
首先我們?cè)O(shè)定一個(gè)觸發(fā)值:
var options = {
root: document.querySelector('.con'),
threshold: 1.0
}
var observer = new IntersectionObserver((entries, observer) => {
console.log("進(jìn)入");
}, options);
let target = document.querySelector('.listItem');
observer.observe(target);
我們?cè)O(shè)定,當(dāng)target全部顯示到root中時(shí)才會(huì)觸發(fā)回調(diào):

這次我們來(lái)嘗試設(shè)置多次觸發(fā)點(diǎn):
var options = {
root: document.querySelector('.con'),
threshold: [0, 0.5, 1.0]
}
var observer = new IntersectionObserver((entries, observer) => {
console.log("進(jìn)入");
}, options);
let target = document.querySelector('.listItem');
observer.observe(target);
我們?cè)O(shè)定了在target開(kāi)始出現(xiàn),出現(xiàn)一半,以及全部漏出的時(shí)候都會(huì)觸發(fā),所以應(yīng)該會(huì)觸發(fā)三次回調(diào),看看效果呢。

截止到此,我們應(yīng)該已經(jīng)知道IntersectionObserver的使用方法。
IntersectionObserver回調(diào)中的回參
既然我們知道此API的使用方式,那么我們也一定很好奇此函數(shù)具體返回了什么。
函數(shù)的callback會(huì)返回2個(gè)值: [IntersectionObserverEntry] 和 IntersectionObserver。
-
IntersectionObserverEntry: 提供了target和root交叉之后的一些信息,此值無(wú)法主動(dòng)創(chuàng)建,但是可以通過(guò)
IntersectionObserver.takeRecords()來(lái)獲取。
參數(shù)內(nèi)容如下:
IntersectionObserverEntry -
IntersectionObserver: 提供了當(dāng)前創(chuàng)建的觀察者IntersectionObserver的所有信息。
IntersectionObserver
尾聲
這個(gè)API,可能大家遇到或者聽(tīng)說(shuō)過(guò)的都不多,不過(guò)如果有幸你們看到了它,那它可能會(huì)給你的代碼帶來(lái)一些新的優(yōu)化靈感,如果能達(dá)到這樣的效果,那么這篇文章就有了其存在的價(jià)值。

