有时候我们需要根据一个元素的位置来修改它的属性,比如图片的 lazyload,比如视频离开视窗之后停止播放。
以前的做法通常是:
- 侦听
window.scroll
事件 scroll
触发,遍历每个要检查的 DOM Element,执行.getBoundingClientRect()
取出它的width
,height
,top
,left
,然后根据 viewport 和宽高scrollTop
scrollLeft
计算对象是否应该出现- 然后做处理
这样做会产生一些问题:
- 如果漏掉移除侦听器,可能造成内存泄漏
- 不同组件之间,很难共享侦听器
- 每一次都计算所有 Element,成本很高
于是现在我们有了 Intersection Observer,专门用来观察一个 Element 是否出现在 viewport 或者父容器里。它可以很好的解决这些问题:
- API 更清晰
- 逻辑原生,速度更快,消耗更少
- 可以自定义阈值,更加可控
关于 Intersection Observer 的详细知识,建议大家认真阅读 MDN – Intersection Observer,我就不抄文档了。
使用 Intersection Observer 大体上分为三步:
- 声明一个 Intersection Observer 实例,这一步最关键的是要确定显隐依据哪个元素,也就是
root
- 将需要检查的元素加入侦听队列
- 在回调函数里处理元素状态
写成代码大概是这样的:
// 声明一个实例
// 因为我的视口即当前 viewport,所以这里不需要 `options`
const observer = new IntersectionObserver(entries => {
// 遍历所有实例,如果它显示出来,即 intersectionRatio 显示比例大于 0
// 那么就让它 `dispatch('visible')`
entries.forEach(({target, intersectionRatio}) => {
const event = new CustomEvent('visible', {
detail: {
isVisible: intersectionRatio > 0,
},
});
target.dispatchEvent(event);
});
});
// 然后可以在 Vue 里侦听这个事件
export default {
template: '<div @visible="onVisible"></div>',
mounted() {
observer.observe(this.$el);
},
beforeDestroy() {
observer.unobserve(this.$el);
},
}
Intersection Observer API 公布一段时间之后,又进行了升级,现在支持更丰富的参数,比如我们可以定义一个函数去判断更复杂情况下的显示状态。不过大部分场景下,我们并不需要很精准的判断,所以我觉得这个只是保障选择的机会。
参考阅读:
欢迎吐槽,共同进步