前端進階
-JS - Intersection Observer 交叉觀察器
Intersection Observer 交叉觀察器
前陣子做網站時遇到一個需求,當使用者滑到底部時,要獲取新的資料,就是所謂的懶加載 lazy loading。
最一開始的想法是用 scroll 事件處理,判斷最後一個元素是否進入畫面,當進入畫面後,執行獲取資料的函數,不過這樣有個缺點,每次滾動時都會重複執行判斷,計算量大造成性能問題。
後來我發現了 Intersection Observer 交叉觀察器,他是瀏覽器內建的 API,在 JS 中能直接使用,可以用來判斷 DOM 元素是否進入畫面或離開畫面,下面我就來介紹一下這個好用的 API 吧!
甚麼是觀察器
觀察器用來針對一些不是由使用者直接觸發的事件,例如 DOM 元素進入畫面變可見狀態、DOM 元素大小、節點改變...等等,瀏覽器提供特定的 API 讓我們去監控這些變化,Intersection Observer 就是其中一個監控元素是否進入/離開畫面的觀察器。
有那些觀察器?
- IntersectionObserver(交叉觀察器)
- MutationObserver(變化觀察器)
- ResizeObserver(大小觀察器)
- PerformanceObserver(性能觀察器)
- ReportingObserver(報告觀察器)
這篇文章先講交叉觀察器,其他幾種都還在實驗階段,有興趣的可以去 MDN 看看喔!
交叉觀察器如何使用
在 JS 中,通過 new IntersectionObserver(callback, {options})
即可創建觀察器,如下
const target = document.querySelector('#target');
const callback = () => {
console.log('觸發函數')
}
let options = {
root: document.querySelector("#viewarea"),
rootMargin: "0px",
threshold: 0,
};
let observer = new IntersectionObserver(callback, options);
observer.observe(target) // 開始觀察目標
- callback: 當目標 DOM 元素進入物件時,觸發的函數
- options: 針對觀察器的設定,有三種屬性可以設定,分別為
- root: 根元素,用來判斷指目標元素是否進入到這個元素中,一定要是目標元素的父元素,默認是
null
舊式瀏覽器的視窗 - rootMargin: 類似 CSS margin 的屬性值,用來調整根元素的邊界範圍,默認為 0
- threshold: 目標元素進入畫面的百分比,值為 0 ~ 1,當值為 1 的時候,目標元素整個進入畫面時才會觸發 callBack
- root: 根元素,用來判斷指目標元素是否進入到這個元素中,一定要是目標元素的父元素,默認是
new IntersectionObserver()
會返回一個物件,物件裡面有多個方法可以使用,observe
正是其中一個,用來開始觀察指定的目標元素。
需要注意的是,當目標元素進入畫面和離開畫面的時候都會觸發函數喔。
如果想要監聽多的目標元素,只要再用 observe
即可
const target1 = documnt.querySelector('#target1')
const target2 = documnt.querySelector('#target2')
observer.observe(target1);
observer.observe(target2);
範例
const target = document.querySelector('#target') ;
let count = 0;
const callback = () => {
console.log('觸發函數', ++count);
}
const options = {
threshold: 0.5,
};
const observer = new IntersectionObserver(callback, options);
observer.observe(target);
HTML<div id="target"></div>
CSS
* {
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body {
height: 500vh;
background: #23232b;
display: grid;
place-content: center;
}
#target {
width: 20px;
height: 200px;
background-color: #e3e3e3;
}
可以發現當目標元素有一半都進入畫面時,和一半出去畫面時,都會觸發函數。
Intersection Observer Entry 被觀察的元素
除了上面基本的應用外,回調函數還可以接收兩個參數
const callback = (entries, observer) => {
console.log(entries);
console.log(observer);
}
- observer 是觀察器本身,通常比較少使用這個參數
- entries: 是被觀察的目標元素,為一個陣列。它記錄目標元素很多有用的屬性,例如
isIntersecting
是一個boolean
值,若目標元素和根元素相交就返回true
有了 isIntersecting
這個屬性就可避免進入/離開畫面時觸發兩次函數的問題了,我們來改寫一下上面的例子
const target = document.querySelector('#target');
let count = 0;
const callback = (entries) => {
if (entries[0].isIntersecting) {
console.log('進入畫面', count++);
}
}
const options = {
threshold: 0.5,
};
const observer = new IntersectionObserver(callback, options);
observer.observe(target);
可以發現這樣就只會在進入畫面時觸發函數了
除了這個屬性之外,entries
還有很多好用的屬性,例如:
- boundingClientRect: 返回了目標元素的getBoundingClientRect的返回值。
- intersectionRatio:返回了目標元素和根元素交叉区域的getBoundingClientRect的返回值。
- intersectionRect: 目標元素的可見比例,相當於兩者重合多少。
- isIntersecting: 目標元素與根元素是否相交。
- rootBounds: 返回了根元素的getBoundingClientRect的返回值。
- target:目標元素本身。
- time: 從首次創建觀察者到觸髮指定閾值(threshold)發生交叉的時間
observer 觀察器
而觀察器除了前面用到的 observe()
方法,也有其它好用的方法,例如:
- observe: 開始觀察目標元素
- disconnect:停止對所有目標元素的觀察
- unobserve:停止對一個目標元素的觀察,可以用在組件 unmounted 時。
實戰範例
接著我們來做一些簡單的實戰範例:
1. 懶加載
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 獲取目標元素本身
let img = entry.target;
// 設定圖片路徑
img.setAttribute('src', `./images/img${i}.png`)
// 設定完圖片路徑後,取消觀察
observer.unobserve(img);
}
})
}, {});
// 觀察所有 img 元素
Array.from(document.querySelectorAll('img')).forEach((item) => {
observer.observe(item)
});
2. 自動回到頂部 / 無限滾動
const observer =
new IntersectionObserver((entries, observer) => {
if (entrys[0].intersectionRatio <= 0) return;
window.scrollTo({
top: 0,
})
});
observer.observe(
document.querySelector('.footer')
);
結論
總而言之,intersectionOberserver
是非常好用的觀察器,不管事應用在懶加載或是無限滾動都非常方便,希望這篇有幫助到你!