作者:maxin,未经授权禁止转载。
前言
工作中经常通过判断元素是否进入视口,一般有三种方式进行判断
1. el.offsetTop - document.documentElement.scrollTop <= viewPortHeight
2. el.getBoundingClientRect().top <= viewPortHeight
3. intersectionRatio > 0 && intersectionRatio <= 1
根据元素与视口是否相交,可以进行吸顶、吸底、曝光上报、列表加载更多、图片懒加载等操作。
问题
前面两种需要通过监听scroll
事件,为了防止频繁触发,需要做防抖处理。
用户体验
- 当元素进入视口时,总是需要延迟一定时间才能执行判断逻辑。
性能
- 在主线程上运行,因此频繁触发、调用会造成性能问题
- 无论是否触发相交,滚动结束后都会进行判断
- 获取
srollTop
的值和getBoundingClientRect
方法都会导致回流
- 滚动事件会绑定多个事件处理函数,阻塞UI渲染
IntersectionObserver
当目标元素和根元素相交或失交,并且当前 tick 期间没有任务运行时,回调函数就会被执行
构造函数
let observer = new IntersectionObserver(callback, options);
callback
每个entry
描述一个观察到的目标元素的交集变化
let callback = (entries, observer) => {
entries.forEach(entry => {
// entry.boundingClientRect
// 目标元素的区域信息,getBoundingClientRect()的返回值
// entry.intersectionRatio
// 目标元素的可见比例
// entry.intersectionRect
// 目标元素与根元素交叉的区域信息
// entry.isIntersecting
// 目标元素是否进入根元素区域
// entry.rootBounds
// 根元素的矩形区域信息
// entry.target
// 被观察dom节点
// entry.time
// 相交发生时距离页面打开时的毫秒数
});
};
options
root
根元素,不指定默认为视窗rootMargin
根元素的外边距threshold
目标元素与根元素相交比例达到该值触发回调
实例方法
observe
开始监听一个目标元素(target),target
必须是root的后代unobserve
停止监听一个目标元素takeRecords
返回所有监听的目标元素集合disconnect
停止所有监听
参考
MDN Web Docs - Intersection Observer API
vue directive优化懒加载
步骤
防止直接加载图像,先把图片地址存放在data-src上
<img
class="image-item"
:data-src="imageUrl"
v-lazyload
/>
LazyLoadDirective.js
export default {
// hookFunction 当绑定元素插入父节点时调用
insert(el) {
function loadImage() {
el.addEventListener('load', () => {
// 加载完成后延迟添加class可以实现淡入动画
setTimeout(() => {
el.classList.add('loaded')
}, 100);
});
// 加载 data-url 的图片地址
el.src = el.dataset.url;
}
function handleIntersect(entries, observer) {
entries.forEach(entry => {
if (!entry.isIntersecting) {
return;
} else {
// 绑定元素进入视口后触发加载图片
loadImage();
// 并停止观察可见性变化, 防止再次加载图像。
observer.unobserve(el);
}
});
}
function createObserver() {
const options = {
root: null,
threshold: '0'
};
const observer = new IntersectionObserver(handleIntersect, options);
// 订阅观察当前绑定图片元素
observer.observe(el);
}
createObserver();
}
}
思考
如果使用scroll事件来判断图片可见或不可见,每次需要重新计算的几十个图像,显然使用IntersectionObserver更加优雅。
注册指令
// main.js 全局注册
import Vue from 'vue';
import LazyLoadDirective from '@/directives/LazyLoadDirective';
Vue.directive('lazyload', LazyLoadDirective);
小结
使用IntersectionObserver
实现延迟加载非常简单,拥有很好的性能和用户体验,目前系统兼容性覆盖的比较广。当然,如果列表元素节点特别多时,也需要进行长列表优化。
结尾
感谢你的阅读,日前智云健康大前端团队
正在参加掘金人气团队评选活动
。如果你觉得还不错的话,那就来 给我们投几票 吧!
今日总共可以投21票,网页7票,App7票,分享7票。感谢支持,2021我们还会创作更多的技术好文~~~
你的支持是是我们最大的动力~