IntersectionObserver
這個 API 日常可能聽得比較少,caniuse 兼容性報告目前支持率是 90.12%,還不推薦用於大衆化的場景中,可是它的能力和性能很是的好。html
IntersectionObserver
IntersectionObserver
接口 (從屬於Intersection Observer API
) 提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗(viewport
)交叉狀態的方法。祖先元素與視窗(viewport
)被稱爲根(root
)。因爲可見(visible)的本質是,目標元素與視口產生一個交叉區,因此這個 API 叫作"交叉觀察器"。前端
簡單點說就是它能夠觀察 root(默認是視口)和目標元素的交叉狀況,當交叉率是 0% 或者 10% 或者更多的時候,能夠觸發指定的回調。api
當一個 IntersectionObserver
對象被建立時,其被配置爲監聽根中一段給定比例的可見區域。一旦 IntersectionObserver
被建立,則沒法更改其配置,因此一個給定的觀察者對象只能用來監聽可見區域的特定變化值;然而,你能夠在同一個觀察者對象中配置監聽多個目標元素。數組
const observer = new IntersectionObserver(callback, observerConfig)
瀏覽器
建立一個新的 IntersectionObserver
對象,當其監聽到目標元素的可見部分穿過了一個或多個閾(thresholds)時,會執行指定的回調函數。性能優化
用法異步
function cb (entries) {
console.log(entries)
entries.forEach(entry => {
const target = entry.target;
console.log(target)
console.log(entry)
});
}
let observerConfig = {
root: null,
rootMargin: '0px',
threshold: [0],
}
const observer = new IntersectionObserver(cb, observerConfig)
const box = document.getElementById('#box')
// 開始觀察
observer.observe(box)
// 中止觀察
observer.unobserve(box)
// 關閉觀察器,observer 全部的觀察都會中止
observer.disconnect();
複製代碼
entries
是一個監聽目標的數組,每一個成員都是一個 IntersectionObserverEntry
對象。函數
cb
回調函數在最初會調用一次,此次 entries
會是全部的觀察目標對象,在滑動的時候會把可見性變化符合 threshold
的對象做爲 entries
傳進來。性能
初始化(所有被觀察的) 優化
滾動時(符合條件的)
因爲是異步的操做,能夠看到 start
end
這兩個同步操做執行以後,纔會執行 cb
。
IntersectionObserverEntry
{
boundingClientRect: DOMRectReadOnly {x: 8, y: 380, width: 300, height: 300, top: 380, …}
intersectionRatio: 0.023333333432674408
intersectionRect: DOMRectReadOnly {x: 8, y: 380, width: 300, height: 7, top: 380, …}
isIntersecting: true
rootBounds: DOMRectReadOnly {x: 0, y: 0, width: 1903, height: 387, top: 0, …}
target: img.lazy
time: 440149.8099999735
}
複製代碼
含義以下
time
:可見性發生變化的時間,是一個高精度時間戳,單位爲毫秒,返回一個記錄從 IntersectionObserver
的時間原點(time origin)到交叉被觸發的時間的時間戳(DOMHighResTimeStamp).target
:被觀察的目標元素,是一個 DOM 節點對象rootBounds
:根元素的矩形區域的信息,getBoundingClientRect()
方法的返回值,若是沒有根元素(即直接相對於視口滾動),則返回 null
boundingClientRect
:目標元素的矩形區域的信息intersectionRect
:目標元素與視口(或根元素)的交叉區域的信息intersectionRatio
:目標元素的可見比例,即 intersectionRect
佔 boundingClientRect
的比例,徹底可見時爲 1,徹底不可見時小於等於0isIntersecting
是否交叉observerConfig.root
所監聽對象的具體祖先元素(element)。若是未傳入值或值爲 null
,則默認使用頂級文檔的視窗。
observerConfig.rootMargin
計算交叉時添加到根(root
)邊界盒 bounding box 的矩形偏移量, 能夠有效的縮小或擴大根的斷定範圍從而知足計算須要。全部的偏移量都可用像素(pixel)(px)或百分比(percentage)(%)來表達, 默認值爲"0px 0px 0px 0px
",用法和普通的 margin
同樣(top、right、bottom、left)。
observerConfig.threshold
注意 MDN 文檔中的 thresholds
是錯誤的,一個包含閾值的列表, 按升序排列, 列表中的每一個閾值都是監聽對象的交叉區域與邊界區域的比率。當監聽對象的任何閾值被越過期,都會生成一個通知(Notification)。若是構造器未傳入值, 則默認值爲0,也能夠是 [0, 0.25, 0.5, 0.75, 1]
。
常規實現都會監聽滾動事件,經過 el.getBoundingClientRect()
獲取到當前元素與視口的位置關係來肯定圖片是否加載,在加載完成以後爲了性能考慮,刪除 data-src
,這樣就能夠避免重複的執行,要注意的是 getBoundingClientRect
會觸發瀏覽器的迴流。
getBoundingClientRect
這個方法的支持度已經很是高,能夠放心使用。
核心是下面這個判斷,這個方法能夠只加載和視口交叉的元素,不會加載視口上面或者視口下面的圖片。
$img.dataset.src
&& $img.getBoundingClientRect().bottom >= 0
&& windowHeight > $img.getBoundingClientRect().top
複製代碼
data-src
則直接跳過(性能優化);document.addEventListener('DOMContentLoaded', function() {
const imgs = document.querySelectorAll('.lazy')
function lazyLoad() {
const windowHeight = document.documentElement.clientHeight
imgs.forEach(($img, i) => {
// 重點是下面這個判斷
if ($img.dataset.src && $img.getBoundingClientRect().bottom >= 0 && windowHeight > $img.getBoundingClientRect().top) {
$img.src = $img.dataset.src
delete $img.dataset.src
}
})
}
lazyLoad()
document.addEventListener('scroll', debounce(lazyLoad, 200))
})
function debounce(func, wait) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func(...args)
}, wait)
}
}
複製代碼
感謝閱讀,歡迎關注個人公衆號 雲影sky,帶你解讀前端技術。關注公衆號能夠拉你進討論羣,有任何問題都會回覆。