🌟一个指令v-lazy实现图片懒加载?快上车!

3,431 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

适用场景:

图片懒加载的常规解决方案

loading="lazy"

可以利用HTML元素img延迟加载属性,loading属性值lazy允许浏览器选择性加载IMG元素,根据用户滚动操作至其元素附近执行加载,一定程度起到节流的作用。

<img src="..." loading="lazy"/>

缺点:无法设置默认的加载图和加载失败的缺省图。

监听scroll

可以通过监听滚动事件,基于图片宽高与滚动位置进行图片懒加载,实现代码网上一搜一大堆就不贴了。

缺点:图片资源较多时会造成浏览器滚动时明显卡顿(计算量太大,即便加了防抖仍然很难兼顾大量计算与灵敏响应的冲突)。

setInterval

使用setInterval来判断,元素是否进入视图。这种方案比较小众,也不贴代码了。

缺点:setInterval的间隔周期决定了这种方案很难保证灵敏的响应。

IntersectionObserver

2016年初,chrome51为了解决图片懒加载上述两种方案的局限性,提出了新的api:IntersectionObserver,用于监听元素是否进入了设备的可见范围。

//初始化一个实例
var observer = new IntersectionObserver(changes => {
    for (const change of changes) {
        console.log(change.time);
        // Timestamp when the change occurred
        // 当可视状态变化时,状态发送改变的时间戳
        // 对比时间为,实例化的时间,
        // 比如,值为1000时,表示在IntersectionObserver实例化的1秒钟之后,触发该元素的可视性变化

        console.log(change.rootBounds);
        // Unclipped area of root
        // 根元素的矩形区域信息,即为getBoundingClientRect方法返回的值

        console.log(change.boundingClientRect);
        // target.boundingClientRect()
        // 目标元素的矩形区域的信息

        console.log(change.intersectionRect);
        // boundingClientRect, clipped by its containing block ancestors,
        // and intersected with rootBounds
        // 目标元素与视口(或根元素)的交叉区域的信息

        console.log(change.intersectionRatio);
        // Ratio of intersectionRect area to boundingClientRect area
        // 目标元素的可见比例,即intersectionRect占boundingClientRect的比例,
        // 完全可见时为1,完全不可见时小于等于0

        console.log(change.target);
        // the Element target
        // 被观察的目标元素,是一个 DOM 节点对象
        // 当前可视区域正在变化的元素

    }
}, {});

// Watch for intersection events on a specific target Element.
// 对元素target添加监听,当target元素变化时,就会触发上述的回调
observer.observe(target);

// Stop watching for intersection events on a specific target Element.
// 移除一个监听,移除之后,target元素的可视区域变化,将不再触发前面的回调函数
observer.unobserve(target);

// Stop observing threshold events on all target elements.
// 停止所有的监听
observer.disconnect();

目前主流的浏览器已经全面支持了该api,可以放心使用。(ie?!)

封装指令v-lazy

实现指令/directive/lazyimg

/*
 * @Description:图片懒加载指令
 */
// 占位图
import defaultImg from '@/assets/img/empty.png';
const defineDirective = (app) => {
    app.directive('lazy', {
        mounted(el, binding) {
            const observer = new IntersectionObserver(
                ([{ isIntersecting }]) => {
                    if (isIntersecting) {
                        observer.unobserve(el);
                        el.onerror = () => {
                           el.src = defaultImg;
                        };
                        el.src = binding.value;
                    }
                },
                {
                    threshold: 0.01,
                }
            );
            observer.observe(el);
        },
    });
};
//注册插件
export default {
    install(app) {
        defineDirective(app);
    },
};

import lazyimg from '@/directive/lazyimg';
app.use(lazyimg);

使用方式

<img v-lazy="图片地址">

注意,如果图片可能动态变更,记得加上:key

done!