【JS】1067- 一個神奇的交叉觀察 API Intersection Observer
前言
前端開發(fā)肯定離不開判斷一個元素是否能被用戶看見,然后再基于此進行一些交互。
過去,要檢測一個元素是否可見或者兩個元素是否相交并不容易,很多解決辦法不可靠或性能很差。然而,隨著互聯(lián)網(wǎng)的發(fā)展,這種需求卻與日俱增,比如,下面這些情況都需要用到相交檢測:
圖片懶加載——當圖片滾動到可見時才進行加載 內(nèi)容無限滾動——也就是用戶滾動到接近內(nèi)容底部時直接加載更多,而無需用戶操作翻頁,給用戶一種網(wǎng)頁可以無限滾動的錯覺 檢測廣告的曝光情況——為了計算廣告收益,需要知道廣告元素的曝光情況 在用戶看見某個區(qū)域時執(zhí)行任務(wù)或播放動畫 過去,相交檢測通常要用到事件監(jiān)聽,并且需要頻繁調(diào)用
Element.getBoundingClientRect()方法以獲取相關(guān)元素的邊界信息。事件監(jiān)聽和調(diào)用Element.getBoundingClientRect()都是在主線程上運行,因此頻繁觸發(fā)、調(diào)用可能會造成性能問題。這種檢測方法極其怪異且不優(yōu)雅。
上面這一段話來自 MDN ,中心思想就是現(xiàn)在判斷一個元素是否能被用戶看見的使用場景越來越多,監(jiān)聽 scroll 事件以及通過 Element.getBoundingClientRect() 獲取節(jié)點位置的方式,又麻煩又不好用,那么怎么辦呢。于是就有了今天的內(nèi)容 Intersection Observer API。
Intersection Observer API 是什么
我們需要觀察的元素被稱為 目標元素(target),設(shè)備視窗或者其他指定的元素視口的邊界框我們稱它為 根元素(root),或者簡稱為 根 。
Intersection Observer API 翻譯過來叫做 “交叉觀察器”,因為判斷元素是否可見(通常情況下)的本質(zhì)就是判斷目標元素和根元素是不是產(chǎn)生了 交叉區(qū)域 。
為什么是通常情況下,因為當我們 css 設(shè)置了 opacity: 0,visibility: hidden 或者 用其他的元素覆蓋目標元素 的時候,對于視圖來說是不可見的,但對于交叉觀察器來說是可見的。這里可能有點抽象,大家只需記住,交叉觀察器只關(guān)心 目標元素 和 根元素 是否有 交叉區(qū)域, 而不管視覺上能不能看見這個元素。當然如果設(shè)置了 display:none,那么交叉觀察器就不會生效了,其實也很好理解,因為元素已經(jīng)不存在了,那么也就監(jiān)測不到了。
一句話總結(jié):Intersection Observer API 提供了一種異步檢測目標元素與祖先元素或 viewport 相交情況變化的方法。 -- MDN
現(xiàn)在不懂沒關(guān)系,咱們接著往下看,看完自然就明白了。
Intersection Observer API 怎么玩
生成觀察器
// 調(diào)用構(gòu)造函數(shù) IntersectionObserver 生成觀察器
const myObserver = new IntersectionObserver(callback, options);
復(fù)制代碼
首先調(diào)用瀏覽器原生構(gòu)造函數(shù) IntersectionObserver ,構(gòu)造函數(shù)的返回值是一個 觀察器實例 。
構(gòu)造函數(shù) IntersectionObserver 接收兩個參數(shù)
callback: 可見性發(fā)生變化時觸發(fā)的回調(diào)函數(shù) options: 配置對象(可選,不傳時會使用默認配置)
構(gòu)造函數(shù)接收的參數(shù) options
為了方便理解,我們先看第二個參數(shù) options 。一個可以用來配置觀察器實例的對象,那么這個配置對象都包含哪些屬性呢?
root: 設(shè)置目標元素的根元素,也就是我們用來判斷元素是否可見的區(qū)域,必須是目標元素的父級元素,如果不指定的話,則使用瀏覽器視窗,也就是
document。rootMargin: 一個在計算交叉值時添加至根的邊界中的一組偏移量,類型為字符串
(string),可以有效的縮小或擴大根的判定范圍從而滿足計算需要。語法大致和CSS 中margin屬性等同,默認值“0px 0px 0px 0px”,如果有指定root參數(shù),則rootMargin也可以使用百分比來取值。threshold: 介于
0和1之間的數(shù)字,指示觸發(fā)前應(yīng)可見的百分比。也可以是一個數(shù)字數(shù)組,以創(chuàng)建多個觸發(fā)點,也被稱之為 閾值。如果構(gòu)造器未傳入值, 則默認值為0。trackVisibility: 一個布爾值,指示當前觀察器是否將跟蹤目標可見性的更改,默認為
false,注意,此處的可見性并非指目標元素和根元素是否相交,而是指視圖上是否可見,這個我們之前就已經(jīng)分析過了,如果此值設(shè)置為false或不設(shè)置,那么回調(diào)函數(shù)參數(shù)中IntersectionObserverEntry的isVisible屬性將永遠返回false。delay: 一個數(shù)字,也就是回調(diào)函數(shù)執(zhí)行的延遲時間(毫秒)。如果
trackVisibility設(shè)置為true,則此值必須至少設(shè)置為100,否則會報錯(但是這里我也只是親測出來的,并不知道為什么會設(shè)計成這樣,如果有大佬了解請指教一下)。
構(gòu)造函數(shù)接收的參數(shù) callback
當元素可見比例超過指定閾值后,會調(diào)用一個回調(diào)函數(shù),此回調(diào)函數(shù)接受兩個參數(shù):存放 IntersectionObserverEntry 對象的數(shù)組和觀察器實例(可選)。
((doc) => {
//回調(diào)函數(shù)
const callback = (entries, observer) => {
console.log('????~ 執(zhí)行了一次callback');
console.log('????~ entries:', entries);
console.log('????~ observer:', observer);
};
//配置對象
const options = {};
//創(chuàng)建觀察器
const myObserver = new IntersectionObserver(callback, options);
//獲取目標元素
const target = doc.querySelector(".target")
//開始監(jiān)聽目標元素
myObserver.observe(target);
})(document)
我們把這兩個參數(shù)打印出來看一下,可以看到,第一個參數(shù)是一個數(shù)組,每一項都是一個目標元素對應(yīng)的 IntersectionObserverEntry對象,第二個參數(shù)是觀察器實例對象 IntersectionObserver 。
什么是 IntersectionObserverEntry 對象
展開 IntersectionObserverEntry 看一下都有什么。
boundingClientRect: 一個對象,包含目標元素的
getBoundingClientRect()方法的返回值。intersectionRatio: 一個對象,包含目標元素與根元素交叉區(qū)域
getBoundingClientRect()的返回值。intersectionRect: 目標元素的可見比例,即
intersectionRect占boundingClientRect的比例,完全可見時為1,完全不可見時小于等于0。isIntersecting: 返回一個布爾值,如果目標元素與根元素相交,則返回
true,如果isIntersecting是true,則target元素至少已經(jīng)達到thresholds屬性值當中規(guī)定的其中一個閾值,如果是false,target元素不在給定的閾值范圍內(nèi)可見。isVisible: 這個看字面意思應(yīng)該是 “是否可見” ,如果要讓這個屬性生效,那么在使用構(gòu)造函數(shù)生成觀察器實例的時候,傳入的
options參數(shù)必須配置trackVisibility為true,并且delay設(shè)置為大于100,否則該屬性將永遠返回false。rootBounds: 一個對象,包含根元素的
getBoundingClientRect()方法的返回值。target:: 被觀察的目標元素,是一個
DOM節(jié)點。在觀察者包含多個目標的情況下,這是確定哪個目標元素觸發(fā)了此相交更改的簡便方法。time: 該屬性提供從 首次創(chuàng)建觀察者 到 觸發(fā)此交集改變 的時間(以毫秒為單位)。通過這種方式,你可以跟蹤觀察器達到特定閾值所花費的時間。即使稍后將目標再次滾動到視圖中,此屬性也會提供新的時間。這可用于跟蹤目標元素進入和離開根元素的時間,以及兩個閾值觸發(fā)的間隔時間。
這里再看一下 boundingClientRect ,intersectionRatio , rootBounds 三個屬性展開的內(nèi)容都有什么。
bottom: 元素下邊距離頁面上邊的距離 left: 元素左邊距離頁面左邊的距離 right: 元素右邊距離頁面左邊的距離 top: 元素上邊距離頁面上邊的距離 width: 元素的寬 height: 元素的高 x: 等同于 left,元素左邊距離頁面左邊的距離y: 等同于 top,元素上邊距離頁面上邊的距離
用一張圖來展示一下這幾個屬性,特別需要注意的是 right 和 bottom ,跟我們平時寫 css 的 position 那個不一樣 。
那么第二個參數(shù) IntersectionObserver 觀察器實例對象都有什么呢
別著急,接著往下看,實例屬性部分。
觀察器實例屬性
上面留了一個坑,回調(diào)函數(shù)的第二個參數(shù) IntersectionObserver 觀察器實例對象都有什么呢?我們把實例對象打印出來看一下
((doc) => {
//回調(diào)函數(shù)
const callback = () => {};
//配置對象
const options = {};
//創(chuàng)建觀察器
const myObserver = new IntersectionObserver(callback, options);
//獲取目標元素
const target = doc.querySelector(".target")
//開始監(jiān)聽目標元素
myObserver.observe(target);
console.log('????~ myObserver:', myObserver);
})(document)
可以看到,我們的觀察器實例上面包含如下屬性
root rootMargin thresholds trackVisibility delay
是不是特別眼熟,沒錯,就是我們創(chuàng)建觀察者實例的時候,傳入的 options 對象,只不過 options 對象是可選的,觀察器實例的屬性就使用我們傳入的 options 對象,如果沒傳就使用默認值,唯一不同的是,options 中 的屬性 threshold 是單數(shù),而我們實例獲取到的 thresholds 是復(fù)數(shù)。
值得注意的是,這里的所有屬性都是 只讀 的,也就是說一旦觀察器被創(chuàng)建,則 無法 更改其配置,所以一個給定的觀察者對象只能用來監(jiān)聽可見區(qū)域的特定變化值。
接下來我們就通過代碼結(jié)合動圖演示一下這些屬性
((doc) => {
let n = 0
//獲取目標元素
const target = doc.querySelector(".target")
//獲取根元素
const root = doc.querySelector(".out-container")
//回調(diào)函數(shù)
const callback = (entries, observer) => {
n++
console.log(`????~ 執(zhí)行了 ${n} 次callback`);
console.log('????~ entries:', entries);
console.log('????~ observer:', observer);
};
//配置對象
const options = {
root: root,
rootMargin: '0px 0px 0px 0px',
threshold: [0.5],
trackVisibility: true,
delay: 100
};
//創(chuàng)建觀察器
const myObserver = new IntersectionObserver(callback, options);
//開始監(jiān)聽目標元素
myObserver.observe(target);
console.log('????~ myObserver:', myObserver);
})(document)
root這個沒什么說的,就是設(shè)置指定節(jié)點為根元素
rootMargin我們把 rootMargin 修改為 '50px 50px 50px 50px',可以看到,我們的目標元素還沒有露出來的時候回調(diào)函數(shù)就已經(jīng)執(zhí)行了,也就是說目標元素距離根元素還有 50px 的 margin 時,觀察器就認為是發(fā)生了交叉。
thresholds我們把 threshold 修改為 [0.1, 0.3, 0.5, 0.8, 1],可以看到,回調(diào)函數(shù)觸發(fā)了多次,也就是說當交叉區(qū)域的百分比,每達到指定的閾值時都會觸發(fā)一次回調(diào)函數(shù)。
注意
Intersection Observer API無法提供重疊的像素個數(shù)或者具體哪個像素重疊,他的更常見的使用方式是——當兩個元素相交比例在N%左右時,觸發(fā)回調(diào),以執(zhí)行某些邏輯。 -- MDN
trackVisibility修改 trackVisibility 為 true ,可以看到, isVisible 屬性值為 true 。
修改 css 屬性 為 opacity: 0,可以看到,雖然我們藍色小方塊并沒有出現(xiàn)在視圖中,但是回調(diào)函數(shù)已經(jīng)執(zhí)行了,并且 isVisible 屬性值為 false 而 isIntersecting 值為 true 。
delay回調(diào)函數(shù)延遲觸發(fā),我們修改 delay 為 3000,可以看到 log 是 3000ms 以后才輸出的。
觀察器實例方法
通過此段代碼來演示觀察器實例方法,為了方便演示,我添加了幾個對應(yīng)的按鈕。
((doc) => {
let n = 0
//獲取目標元素
const target1 = doc.querySelector(".target1")
const target2 = doc.querySelector(".target2")
//添加幾個按鈕方便操作
const observe = doc.querySelector(".observe")
const unobserve = doc.querySelector(".unobserve")
const disconnect = doc.querySelector(".disconnect")
observe.addEventListener('click', () => myObserver.observe(target1))
unobserve.addEventListener('click', () => myObserver.unobserve(target1))
disconnect.addEventListener('click', () => myObserver.disconnect())
//獲取根元素
const root = doc.querySelector(".out-container")
//回調(diào)函數(shù)
const callback = (entries, observer) => {
n++
console.log(`????~ 執(zhí)行了 ${n} 次callback`);
console.log('????~ entries:', entries);
console.log('????~ observer:', observer);
};
//配置對象
const options = {
root: root,
rootMargin: '0px 0px 0px 0px',
threshold: [0.1, 0.2, 0.3, 0.5],
trackVisibility: true,
delay: 100
};
//創(chuàng)建觀察器
const myObserver = new IntersectionObserver(callback, options);
//開始監(jiān)聽目標元素
myObserver.observe(target2);
console.log('????~ myObserver:', myObserver);
})(document)
observe
const myObserver = new IntersectionObserver(callback, options);
myObserver.observe(target);
接受一個目標元素作為參數(shù)。很好理解,當我們創(chuàng)建完觀察器實例后,要手動的調(diào)用 observe 方法來通知它開始監(jiān)測目標元素。
可以在同一個觀察者對象中配置監(jiān)聽多個目標元素
target2 元素是通過代碼自動監(jiān)測的,而 target1 則是我們在點擊了 observe 按鈕之后開始監(jiān)測的。通過動圖可以看到,當我單擊 observe 按鈕后,我們的 entries 數(shù)組里面就包含了兩條數(shù)據(jù),前文中說到,可以通過 target 屬性來判斷是哪個目標元素。

unobserve
const myObserver = new IntersectionObserver(callback, options);
myObserver.observe(target);
myObserver.unobserve(target)
復(fù)制代碼
接收一個目標元素作為參數(shù),當我們不想監(jiān)聽某個元素的時候,需要手動調(diào)用 unobserve 方法來停止監(jiān)聽指定目標元素。通過動圖可以發(fā)現(xiàn),當我們點擊 unobserve 按鈕后,由兩條數(shù)據(jù)變成了一條數(shù)據(jù),說明 target1 已經(jīng)不再接受監(jiān)測了。
disconnect
const myObserver = new IntersectionObserver(callback, options);
myObserver.disconnect()
當我們不想監(jiān)測任何一個目標元素時,我們需要手動調(diào)用 disconnect 方法停止監(jiān)聽工作。通過動圖可以看到,當我們點擊 disconnect 按鈕后,控制臺不再輸出 log ,說明監(jiān)聽工作已經(jīng)停止,可以通過 observe 再次開啟監(jiān)聽工作。
takeRecords
返回所有觀察目標的 IntersectionObserverEntry 對象數(shù)組,應(yīng)用場景較少。
當觀察到交互動作發(fā)生時,回調(diào)函數(shù)并不會立即執(zhí)行,而是在空閑時期使用 requestIdleCallback 來異步執(zhí)行回調(diào)函數(shù),但是也提供了同步調(diào)用的 takeRecords 方法。
如果異步的回調(diào)先執(zhí)行了,那么當我們調(diào)用同步的 takeRecords 方法時會返回空數(shù)組。同理,如果已經(jīng)通過 takeRecords 獲取了所有的觀察者實例,那么回調(diào)函數(shù)就不會被執(zhí)行了。
注意事項
構(gòu)造函數(shù) IntersectionObserver 配置的回調(diào)函數(shù)都在哪些情況下被調(diào)用?
構(gòu)造函數(shù) IntersectionObserver 配置的回調(diào)函數(shù),在以下情況發(fā)生時可能會被調(diào)用
當目標(target)元素與根(root)元素發(fā)生交集的時候執(zhí)行。 兩個元素的相交部分大小發(fā)生變化時。 Observer第一次監(jiān)聽目標元素的時候。
((doc) => {
//回調(diào)函數(shù)
const callback = () => {
console.log('????~ 執(zhí)行了一次callback');
};
//配置對象
const options = {};
//觀察器實例
const myObserver = new IntersectionObserver(callback, options);
//目標元素
const target = doc.querySelector("#target")
//開始觀察
myObserver.observe(target);
})(document)
可以看到,無論目標元素是否與根元素相交,當我們第一次監(jiān)聽目標元素的時候,回調(diào)函數(shù)都會觸發(fā)一次,所以不要直接在回調(diào)函數(shù)里寫邏輯代碼,盡量通過 isIntersecting 或者 intersectionRect 進行判斷之后再執(zhí)行邏輯代碼。
頁面的可見性如何監(jiān)測
頁面的可見性可以通過document.visibilityState或者document.hidden獲得。頁面可見性的變化可以通過document.visibilitychange來監(jiān)聽。
可見性和交叉觀察
當 css 設(shè)置了opacity: 0,visibility: hidden 以及 用其他的元素覆蓋目標元素 ,都不會影響交叉觀察器的監(jiān)測,也就是都不會影響 isIntersecting 屬性的結(jié)果,但是會影響 isVisible 屬性的結(jié)果, 如果元素設(shè)置了 display:none 就不會被檢測了。當然影響元素可視性的屬性不止上述這些,還包括position,margin,clip 等等等等...就靠小伙伴們自行發(fā)掘了
交集的計算
所有區(qū)域均被 Intersection Observer API 當做一個 矩形 看待。如果元素是不規(guī)則的圖形也將會被看成一個包含元素所有區(qū)域的最小矩形,相似的,如果元素發(fā)生的交集部分不是一個矩形,那么也會被看作是一個包含他所有交集區(qū)域的最小矩形。
我怎么知道目標元素來自視口的上方還是下方
目標元素滾動的方向也是可以判斷的,原理是根元素的 entry.rootBounds.y 是固定不變的 ,所以我們只需要計算 entry.boundingClientRect.y 與 entry.rootBounds.y 的大小,當回調(diào)函數(shù)觸發(fā)的時候,我們記錄下當時的位置,如果 entry.boundingClientRect.y < entry.rootBounds.y,說明是在視口下方,那么當下一次目標元素可見的時候,我們就知道目標元素時來自視口下方的,反之亦然。
let wasAbove = false;
function callback(entries, observer) {
entries.forEach(entry => {
const isAbove = entry.boundingClientRect.y < entry.rootBounds.y;
if (entry.isIntersecting) {
if (wasAbove) {
// Comes from top
}
}
wasAbove = isAbove;
});
}
應(yīng)用場景
介紹完基礎(chǔ)知識,總得來幾個實例(演示代碼采用VUE3.0),當然實際場景要比這復(fù)雜的多,如何在自己的工作學(xué)習中應(yīng)用,還是要靠小伙伴們多多開動聰明的大腦~
數(shù)據(jù)列表無限滾動
<template>
<div class="box">
<div class="vbody"
v-for='item in list'
:key='item'>內(nèi)容區(qū)域{{item}}</div>
<div class="reference"
ref='reference'></div>
</div>
</template>
<script lang='ts'>
import { defineComponent, onMounted, reactive, ref } from 'vue'
export default defineComponent({
name: '',
setup() {
const reference = ref(null)
const list = reactive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
onMounted(() => {
let n = 10
//回調(diào)函數(shù)
const callback = (entries) => {
const myEntry = entries[0]
if (myEntry.isIntersecting) {
console.log(`????~ 觸發(fā)了無線滾動,開始模擬請求數(shù)據(jù) ${n}`)
n++
list.push(n)
}
}
//配置對象
const options = {
root: null,
rootMargin: '0px 0px 0px 0px',
threshold: [0, 1],
trackVisibility: true,
delay: 100,
}
//觀察器實例
const myObserver = new IntersectionObserver(callback, options)
//開始觀察
myObserver.observe(reference.value)
})
return { reference, list }
},
})
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.reference {
width: 100%;
visibility: hidden;
}
.vbody {
width: 100%;
height: 200px;
background-color: red;
color: aliceblue;
font-size: 40px;
text-align: center;
line-height: 200px;
margin: 10px 0;
}
</style>
我們只需要在底部添加一個參考元素,當參考元素可見時,就向后臺請求數(shù)據(jù),就可以實現(xiàn)無線滾動的效果了。
圖片預(yù)加載
<template>
<div class="box">
<div class="vbody">內(nèi)容區(qū)域</div>
<div class="vbody">內(nèi)容區(qū)域</div>
<div class="header"
ref='header'>
<img :src="url">
</div>
<div class="vbody">內(nèi)容區(qū)域</div>
</div>
</template>
<script lang='ts'>
import { defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
name: '',
setup() {
const header = ref(null)
const url = ref('')
onMounted(() => {
//回調(diào)函數(shù)
const callback = (entries) => {
const myEntry = entries[0]
if (myEntry.isIntersecting) {
console.log('????~ 預(yù)加載圖片開始')
url.value =
'//img10.360buyimg.com/imgzone/jfs/t1/197235/15/2956/67824/6115e076Ede17a418/d1350d4d5e52ef50.jpg'
}
}
//配置對象
const options = {
root: null,
rootMargin: '200px 200px 200px 200px',
threshold: [0],
trackVisibility: true,
delay: 100,
}
//觀察器實例
const myObserver = new IntersectionObserver(callback, options)
//開始觀察
myObserver.observe(header.value)
})
return { header, url }
},
})
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.box {
}
.header {
width: 100%;
height: 400px;
background-color: blue;
color: aliceblue;
font-size: 40px;
text-align: center;
line-height: 400px;
}
.header img {
width: 100%;
height: 100%;
}
.reference {
width: 100%;
visibility: hidden;
}
.vbody {
width: 100%;
height: 800px;
background-color: red;
color: aliceblue;
font-size: 40px;
text-align: center;
line-height: 800px;
margin: 10px 0;
}
</style>
利用 options 的 rootMargin屬性,可以在圖片即將進入可視區(qū)域的時間進行圖片的加載,即避免了提前請求大量圖片造成的性能問題,也避免了圖片進入窗口才加載已經(jīng)來不及的問題。
吸頂
<template>
<div class="box">
<div class="reference"
ref='reference'></div>
<div class="header"
ref='header'>吸頂區(qū)域</div>
<div class="vbody">內(nèi)容區(qū)域</div>
<div class="vbody">內(nèi)容區(qū)域</div>
<div class="vbody">內(nèi)容區(qū)域</div>
</div>
</template>
<script lang='ts'>
import { defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
name: '',
setup() {
const header = ref(null)
const reference = ref(null)
onMounted(() => {
//回調(diào)函數(shù)
const callback = (entries) => {
const myEntry = entries[0]
if (!myEntry.isIntersecting) {
console.log('????~ 觸發(fā)了吸頂')
header.value.style.position = 'fixed'
header.value.style.top = '0px'
} else {
console.log('????~ 取消吸頂')
header.value.style.position = 'relative'
}
}
//配置對象
const options = {
root: null,
rootMargin: '0px 0px 0px 0px',
threshold: [0, 1],
trackVisibility: true,
delay: 100,
}
//觀察器實例
const myObserver = new IntersectionObserver(callback, options)
//開始觀察
myObserver.observe(reference.value)
})
return { reference, header }
},
})
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.header {
width: 100%;
height: 100px;
background-color: blue;
color: aliceblue;
font-size: 40px;
text-align: center;
line-height: 100px;
}
.reference {
width: 100%;
visibility: hidden;
}
.vbody {
width: 100%;
height: 800px;
background-color: red;
color: aliceblue;
font-size: 40px;
text-align: center;
line-height: 800px;
margin: 10px 0;
}
</style>
思路就是利用一個參考元素作為交叉觀察器觀察的對象,當參考元素可見的時候,取消吸頂區(qū)域的 fixed 屬性,否則添加 fixed 屬性,吸底稍微復(fù)雜一點,但是道理差不多,留給小伙伴們自行研究吧 ~ ~。
埋點上報
<template>
<div class="box">
<div class="vbody">內(nèi)容區(qū)域</div>
<div class="vbody">內(nèi)容區(qū)域</div>
<div class="header"
ref='header'>埋點區(qū)域</div>
<div class="vbody">內(nèi)容區(qū)域</div>
</div>
</template>
<script lang='ts'>
import { defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
name: '',
setup() {
const header = ref(null)
onMounted(() => {
//回調(diào)函數(shù)
const callback = (entries) => {
const myEntry = entries[0]
if (myEntry.isIntersecting) {
console.log('????~ 觸發(fā)了埋點')
}
}
//配置對象
const options = {
root: null,
rootMargin: '0px 0px 0px 0px',
threshold: [0.5],
trackVisibility: true,
delay: 100,
}
//觀察器實例
const myObserver = new IntersectionObserver(callback, options)
//開始觀察
myObserver.observe(header.value)
})
return { header }
},
})
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.header {
width: 100%;
height: 400px;
background-color: blue;
color: aliceblue;
font-size: 40px;
text-align: center;
line-height: 400px;
}
.vbody {
width: 100%;
height: 800px;
background-color: red;
color: aliceblue;
font-size: 40px;
text-align: center;
line-height: 800px;
margin: 10px 0;
}
</style>
通常情況下,我們統(tǒng)計一個元素是否被用戶有效的看到,并不是元素剛出現(xiàn)就觸發(fā)埋點,而是元素進入可是區(qū)域一定比例才可以,我們可以配置 options 的 threshold 為 0.5。
等等等等。。。。
這個 api 可以說是非常強大了,可玩性也是極高,大家自由發(fā)揮 ~ ~
兼容性

為什么有兩張兼容性的圖呢?因為 trackVisibility 和 delay 兩個屬性是屬于 IntersectionObserver V2 的。所以小伙伴們在用的時候一定要注意兼容性。當然也有兼容解決方案,那就是 intersection-observer-polyfill
參考資料
[1] Can I Use:
https://caniuse.com/?search=IntersectionObserver%20
[2] MDN Intersection Observer:
https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
[3] IntersectionObserver API 使用教程:
https://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html
[4] intersection-observer-polyfill:
https://www.npmjs.com/package/intersection-observer-polyfill
