网页开发时,常常需要了解某个元素是否进入了"视口"(viewport),即用户能不能看到它。最常见的需求就是图片的懒加载。
有时,我们希望某些静态资源(比如图片),只有用户向下滚动,它们进入视口时才加载,这样可以节省带宽,提高网页性能。这就叫做"惰性加载"。
传统方式(scroll
)
传统的实现方法是,监听到 scroll
事件后,调用目标元素的 getBoundingClientRect()
方法,得到它对应于视口左上角的坐标,再判断是否在视口之内。这种方法的缺点是,由于 scroll
事件密集发生,计算量很大,容易造成性能问题。
let t;
// 监听滑动事件
$el.addEventListener('scroll', () => {
clearTimeout(t);
t = setTimeout(() => {
let height = $el.getBoundingClientRect().height; // 滚动所在容器的可视高度
let top = $el.scrollTop; // 滚动距离
let end = height + top;
let $lazyimgElem = $el.querySelector('img'); // 懒加载的图片节点
let elemTop = $lazyimgElem.offsetTop - top;
// 始终只加载在当前屏范围内的图片
if (elemTop >= top && elemTop <= end) {
$item.src = $item.getAttribute('lazy-src');
$item.removeAttribute('lazy-src');
if (elemTop <= end) {
let imageSrc = $item.getAttribute('lazy-src');
// 判断图片是否已经加载
if (imageSrc != null) {
$item.src = $item.getAttribute('lazy-src');
$item.removeAttribute('lazy-src');
}
}
}
}, 100)
});
在这里面由于 scroll
事件是每次滚动都能触发所以,在我们的滑动事件里面有可能会频繁获取节点,判断节点是已经加载图片,这里只是获取一个图片,如果是一个列表,我们要获取更多的节点话,就会使的计算量很大,容易造成性能问题。使用下面的方法就能避免这个问题。
IntersectionObserver
目前有一个新的 IntersectionObserver API
,可以自动"观察"元素是否可见。使用这个对象可以很方便简单的实现图片的懒加载。
// 获取所有的懒加载的图片元素
const $images = document.querySelectorAll('.image');
// 构建观察器
let observer = new IntersectionObserver((entries) => {
for (let entry of entries) {
/*
* 检测节点是否可见,可以通过 isIntersecting 验证,布尔值
* 如果目标元素与交叉区域观察者对象(intersection observer)的根相交,返回 true
* 此时描述了变换到交叉时的状态;
* 如果返回 false, 那么可以由此判断,变换是从交叉状态到非交叉状态.
* 同时也可以根据 intersectionRatio 验证
* intersectionRatio:目标元素的可见比例
* 即 intersectionRect 占 boundingClientRect 的比例
* 完全可见时为1,完全不可见时小于等于0
* 如果不可见,就返回:
* if (entry.intersectionRatio <= 0) return;
*/
if (entry.isIntersecting === true) { // 检测节点是否可见
// 处于交叉可见状态
let $image = entry.target as HTMLImageElement;
$image.src = $image.getAttribute('lazy-src') as string;
$image.removeAttribute('lazy-src');
}
}
});
for (let i = 0, len = $images.length; i < len; i++) {
// 往观察器列表注册观察元素
observer.observe($images[i]);
}