今天要分享的是 Intersection Observer API,同樣是 Web Observer 家族 XD,他是用來觀察 DOM 元素與可視區域 (viewport) 或另一個元素之間的相交情況 (intersection)。我們可以監看元素是否進入或退出了可視區域,從而觸發相應的行為。
網路上的範例都是以懶加載 (Lazy Load) 以及無限滾動 (infinite scroll) 居多,我們只要思考一下這兩個技術的實現邏輯,就大概能了解 Intersection Observer API 的監聽方式,核心在於監控元素是否可視區域 (viewport),並根據結果執行特定的操作。
跟 MutationObserver API 一樣,我們要先建立一個 Observer 物件,並設定監聽的元素
首先建立 IntersectionObserver
物件。該物件接受一個 callback 和一個可選的 options:
const observer = new IntersectionObserver(callback, options);
options 常見的屬性如下:
const options = {
root: document.querySelector('.scroll-container'),
rootMargin: '0px',
threshold: 0.5
};
根元素 (Root)
root
選項用來設置觀察的根元素,如果不設置,默認為視窗可視區域 (viewport)。
const options = {
root: document.querySelector('.scroll-container'),
threshold: 0.5
};
const observer = new IntersectionObserver(callback, options);
根元素的邊界 (RootMargin)
rootMargin
可以調整 root 的大小。我們可以擴大或縮小根元素的邊界,從而改變被視為「可見」的區域。使用方式類似 CSS 的 margin 屬性,可以設定四個方向的值,用法與格式也都雷同,例如:rootMargin: '10px 20px 10px 20px'
,就是上下邊界為 10px,左右邊界為 20 px。
我們還能對 rootMargin
設定正負值,正值會擴大觀察區域,負值會縮小觀察區域。常見的情境如下:
閾值 (Threshold)
threshold 的意思是閾值,意思是我們要觸發某個變化時,需要滿足某個條件的值,這個值就是閾值 XXD,小時候理化可能有學過(?)。在此就是用來決定目標元素與 DOM 元素或可視區域 (viewport) 相交的比例達到什麼程度時,會觸發 callback。
threshold 的型態是 number
,或者可以是一個陣列數字 number[]
,範圍從 0 到 1,代表目標元素的可見部分比例。
例如:
const options = {
threshold: [0, 0.5, 1]
};
const observer = new IntersectionObserver(callback, options);
在這個例子中,當目標元素從完全不可見 (0
) 到 50% 可見 (0.5
),再到完全可見 (1
)時,都會執行 callback。
接著使用 observe()
方法監聽目標元素:
const target = document.querySelector('.target-element');
observer.observe(target);
以上的程式碼表示,當 .target-element
與 .scroll-container
元素相交時,就會再看 threshold
的設定來決定是否觸發 callback
讓我們把它完整的寫一遍,再加上一些效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intersection Observer Example</title>
<style>
/* 設置容器樣式以模擬滾動區域 */
.scroll-container {
width: 100%;
height: 300px;
overflow-y: scroll;
border: 1px solid #ccc;
position: relative;
}
/* 設置一個很高的高度來提供滾動空間 */
.scroll-container::before {
content: '';
display: block;
height: 500px;
}
.scroll-container::after {
content: '';
display: block;
height: 500px;
}
/* 目標元素樣式 */
.target-element {
width: 100px;
height: 100px;
background-color: #3498db;
margin: 20px auto;
opacity: 0;
transform: translateY(50px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
/* 當元素進入可視區域時添加的類 */
.visible {
opacity: 1;
transform: translateY(0);
}
</style>
</head>
<body>
<div class="scroll-container">
<div class="target-element"></div>
</div>
<script>
const options = {
root: document.querySelector('.scroll-container'),
threshold: 0.5
};
const observer = new IntersectionObserver(callback, options);
const target = document.querySelector('.target-element');
observer.observe(target);
function callback(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
console.log('目標元素在可視區域內');
} else {
entry.target.classList.remove('visible');
console.log('目標元素在可視區域外');
}
});
}
</script>
</body>
</html>
https://mukiwu.github.io/web-api-demo/observer.html
透過上面的範例,可以清楚地看出 Intersection Observer API 非常適合做懶加載 (Lazy Load )和無限滾動 (Infinity Scroll) 功能。只需要設定好觀察目標和閾值,就能輕鬆實現所需的效果,無需像過去那樣手動處理座標計算。
以上有任何問題,都歡迎留言討論。