InterpObserver
这个 API 平常可能听得比较少,caniuse[1] 兼容性报告目前支持率是 90.12%,还不推荐用于大众化的场景中,但是它的能力和性能非常的好。
InterpObserver
MDN InterpObserver[2]
阮一峰 InterpObserver API 使用教程[3]
InterpObserver
接口 (从属于Interp Observer API
) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport
)交叉状态的方法。祖先元素与视窗(viewport
)被称为根(root
)。由于可见(visible)的本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"。
简单点说就是它可以观察 root(默认是视口)和目标元素的交叉情况,当交叉率是 0% 或者 10% 或者更多的时候,可以触发指定的回调。
当一个 InterpObserver
对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 InterpObserver
被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。
const observer = new InterpObserver(callback, observerConfig)
创建一个新的 InterpObserver
对象,当其监听到目标元素的可见部分穿过了一个或多个阈(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 InterpObserver(cb, observerConfig)
const box = document.getElementById('#box')
// 开始观察
observer.observe(box)
// 停止观察
observer.unobserve(box)
// 关闭观察器,observer 所有的观察都会停止
observer.disconnect();
entries
是一个监听目标的数组,每个成员都是一个 `InterpObserverEntry`[4] 对象。
cb
回调函数在最初会调用一次,这次 entries
会是所有的观察目标对象,在滑动的时候会把可见性变化符合 threshold
的对象作为 entries
传进来。
由于是异步的操作,可以看到 start
end
这两个同步操作执行之后,才会执行 cb
。
InterpObserverEntry
{
boundingClientRect: DOMRectReadOnly {x: 8, y: 380, width: 300, height: 300, top: 380, …}
interpRatio: 0.023333333432674408
interpRect: 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
:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒,返回一个记录从 InterpObserver
的时间原点(time origin)到交叉被触发的时间的时间戳(DOMHighResTimeStamp).
target
:被观察的目标元素,是一个 DOM 节点对象
rootBounds
:根元素的矩形区域的信息,getBoundingClientRect()
方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null
boundingClientRect
:目标元素的矩形区域的信息
interpRect
:目标元素与视口(或根元素)的交叉区域的信息
interpRatio
:目标元素的可见比例,即 interpRect
占 boundingClientRect
的比例,完全可见时为 1,完全不可见时小于等于 0
`isIntersecting`[5] 是否交叉
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()
获取到当前元素与视口的位置关系来确定图片是否加载,在加载完成之后为了性能考虑,删除 src
,这样就可以避免重复的执行,要注意的是 getBoundingClientRect
会触发浏览器的回流。
`getBoundingClientRect`[6] 这个方法的支持度已经非常高,可以放心使用。
getBoundingClientRect核心是下面这个判断,这个方法可以只加载和视口交叉的元素,不会加载视口上面或者视口下面的图片。
$img.dataset.src
&& $img.getBoundingClientRect().bottom >= 0
&& windowHeight > $img.getBoundingClientRect().top
如果不存在 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,带你解读前端技术。关注公众号可以拉你进讨论群,有任何问题都会回复。
公众号 交流群[1]
caniuse: https://caniuse.com/#search=InterpObserver
[2]MDN InterpObserver: https://developer.mozilla.org/zh-CN/docs/Web/API/InterpObserver
[3]阮一峰 InterpObserver API 使用教程: https://www.ruanyifeng.com/blog/2016/11/interpobserver_api.html
[4]InterpObserverEntry
: https://developer.mozilla.org/zh-CN/docs/Web/API/InterpObserverEntry
isIntersecting
: https://developer.mozilla.org/zh-CN/docs/Web/API/InterpObserverEntry/isIntersecting
getBoundingClientRect
: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect