# Intersection Observer API
觀察目標元素 (target) 與其根層 (root) 或瀏覽器 (viewport) 的交互狀態,當目標元素進入/離開設定的條件時就呼叫 callback
可以取代監聽 Scroll 事件的方式來實作:
- Lazy Loading 圖片,當圖片出現在可視範圍時才開始載入
- Infinite Scroll 無限加載內容,當滾動到頁面底端,或者指定的底部區塊出現時自動載入更多內容
- Scroll Spy 滾動監控,滾動到對應的區塊時才加上 active 的樣式
- 當元素出現在可視範圍時才展示的動畫
# 使用
# 建立一個 observer 觀察者
const options = {
root: null,
rootMargin: '0px',
threshold: 0
}
const observer = new IntersectionObserver(callback, options);
# Options
root
Element | 欲觀察的根層,default 為 null,也就是 window 層的 viewportrootMargin
根層的範圍偏移量,default 為 "0px 0px 0px 0px",寫法跟 css 的 margin 一樣threshold
目標進入/離開觀察範圍多少比例時觸發 callback,default 為 [0],也就是當目標一進入/離開觀察範圍就會觸發
可以設定單一值,在到達該比例時觸發 ( 範圍: 0 ~ 1 )
或是設定 Array ex. [0, 0.25, 0.5, 1] 每達到一次設定的值就觸發一次
# Callback
callback 能接收到兩個參數
function (entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
...
} else {
...
}
})
}
- entries
因為可以同時觀察多個目標,所以會是一個 IntersectionObserverEntry 的 Array- boundingClientRect: DOMRectReadOnly
- rootBounds: DOMRectReadOnly
- intersectionRect: DOMRectReadOnly
- intersectionRatio: 觸發的比例值
- time: 觸發的時間戳
target
: 觸發的目標元素isIntersecting
: 目標是否進入 (true) 或 離開 (false) 根層
# 觀察目標元素
// 開始觀察
observer.observe(document.querySelector('.js-target'))
# 其他
// 停止觀察
observer.disconnect()
// 停止觀察特定目標
observer.unobserve(document.querySelector('.js-target'))
# 範例
# Lazy Loading 圖片
<img class="... js-lazyload" data-src="https://picsum.photos/128" />
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.getAttribute('data-src')
observer.unobserve(img)
}
})
},{ threshold: 1 });
document.querySelectorAll('.js-lazyload').forEach(el => {
observer.observe(el)
});
# Infinite Scroll 無限加載內容
內容數量 20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
・・・
<template>
<div ref="loadmore-wrap" class="... overflow-y-auto overscroll-y-contain">
<div class="..." v-for="(num, index) in numList" :key="index">{{ index + 1 }}</div>
<div ref="loadmore" class="text-center py-2">
{{ isLoading ? '載入中...' : isMax ? '沒有更多了' : '・・・' }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
isLoading: false,
numList: Array(20).fill('')
}
},
computed: {
isMax() {
return this.numList.length >= 200;
}
},
mounted() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadMore();
}
})
},{
root: this.$refs['loadmore-wrap'],
threshold: 0.3
});
observer.observe(this.$refs.loadmore)
},
methods: {
loadMore() {
if (this.isMax) return;
this.isLoading = true;
setTimeout(() => {
this.isLoading = false;
this.numList.push(...Array(20).fill(''))
}, 1000)
}
}
}
</script>
# Scroll Spy 滾動監控
Active Number: 1
1
2
3
4
5
1
2
3
4
5
<template>
<div ref="scrollspy-wrap" class="relative ... overflow-y-auto overscroll-y-contain">
<div class="sticky top-0 w-1/2">
<div v-for="j in 5"
:key="j" class="..."
:class="{ 'text-red-400 text-3xl': activeNum === j }">
{{ j }}
</div>
</div>
<div class="w-1/2">
<div :ref="`spy${n}`"
:data-num="n"
class="..."
v-for="n in 5"
:key="n">
{{ n }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
activeNum: 1,
}
},
mounted() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const num = entry.target.getAttribute('data-num')
this.activeNum = +num;
}
})
},{
root: this.$refs['scrollspy-wrap'],
threshold: 0.4
});
Array(5).fill('').forEach((_, i) => {
const temp = this.$refs[`spy${i + 1}`];
observer3.observe(temp?.[0] ?? temp)
})
},
}
</script>