图片懒加载

255 阅读2分钟

图片懒加载的意义

在项目中,如果图片数量很大的情况下,如果直接都加载渲染出来,会给网络请求和浏览器渲染性能造成比较大的压力,导致首页白屏时间较长。这是图片懒加载就起到了关键的优化作用。

实现

首次渲染的时候,不渲染真实图片,而是给图片位置设置一个默认的背景图占位,比如下面的代码,一开始加载的时候,给img标签设置一个默认占位图,把真实的图片地址设置到另一个属性上,这样,当需要展示真实图片的时候,js动态替换src路径就可以了。 html

  <div class="lazyImageBox">
      <img src="default.jpg" alt="" lazy-image="images/12.jpg">
  </div>

css

html,
body {
    height: 300%;
}

.lazyImageBox {
    position: absolute;
    left: 50%;
    top: 1500px;
    transform: translateX(-50%);
    width: 400px;
    height: 300px;
    /* 设置背景图占位可以有渐变效果 */
    background: url("./images/default.gif") no-repeat center center #EEE;
}

.lazyImageBox img {
    width: 100%;
    height: 100%;
    opacity: 0;
    transition: opacity .3s;
}

实现懒加载

单张图片懒加载

// 图片容器的dom
const lazyImageBox = document.querySelector('.lazyImageBox');
// 图片标签dom
const img = lazyImageBox.querySelector('img');

const io = new IntersectionObserver(([IntersectionObserverEntry]) => {
    const { isIntersecting } = IntersectionObserverEntry;
    if (isIntersecting) {
        const realSrc = img.getAttribute('lazy-image')
        img.src = realSrc;
        img.onload = () => {
            img.style.opacity = 1;
        };
        // 取消监听
        io.unobserve(lazyImageBox);
    }
}, {
    // 取值范围[0-1],图片展示出来的比例
    // 取0时,刚露出isIntersecting就会变为true
    // 取1时,完全露出isIntersecting才会变为true
    threshold: [0.5]
})
// 监听图片容器是否显示在屏幕中
io.observe(lazyImageBox)
  • 方法二
// 节流函数
function throttle(func, wait = 500) {
    let timer = null,
        previous = 0;
    return function (...params) {
        let now = new Date(),
            remaining = wait - (now - previous);
        if (remaining <= 0) {
            clearTimeout(timer);
            timer = null;
            previous = now;
            func.call(this, ...params);
        } else if (!timer) {
            timer = setTimeout(() => {
                timer = null;
                previous = new Date();
                func.call(this, ...params);
            }, remaining);
        }
    };
}
// 获取盒子容器
const lazyImageBox = document.querySelector('.lazyImageBox');
// 获取img图片
const lazyImage = lazyImageBox.querySelector('img');
// 加载真实图片
const singleLazy = function () {
    let trueImg = lazyImage.getAttribute('lazy-image');
    lazyImage.src = trueImg;
    lazyImage.onload = () => {
        // 真实图片加载成功
        lazyImage.style.opacity = 1;
    };
    // 表示该图片已经被加载过了
    lazyImageBox.isLoad = true;
};

const lazyFunc = function () {
    // 防止重复处理
    if (lazyImageBox.isLoad) return;
    // 图片容器底部到视口顶部的距离
    const A = lazyImageBox.getBoundingClientRect().bottom;
    // 视口高度
    const B = document.documentElement.clientHeight;
    // 容器完全出现在视口(图片容器底部到视口顶部的距离等于是视口高度)
    if (A <= B) {
        // 加载真实图片
        singleLazy();
    }
};

// 浏览器会默认触发一次lazyFunc,然后滚动条滚动时触发(节流处理)
window.onscroll = throttle(lazyFunc);

多张图片延迟加载插件

class LazyImgPlugin {
    constructor({
        context = document,
        attr = 'lazy-image',
        callback = Function.prototype,
        speed = 300,
        threshold = 0
    }) {
        this.context = context;
        this.attr = attr;
        this.speed = speed;
        this.callback = callback;
        this.threshold = threshold;
        this.imageBoxesList = [];
        this.ob = null;
        this.init();
    }
    init() {
        this.ob = new IntersectionObserver(changes => {
            // 遍历所有监听的目标
            changes.forEach(change => {
                const {
                    // 是否在屏幕内
                    isIntersecting,
                    // 监听的dom元素
                    target
                } = change;

                if (isIntersecting) {
                    // 需要加载target
                    this.lazySingle(target);
                    // 移除对target元素的监听
                    this.ob.unobserve(target);
                }
            });
        }, {
            threshold: [this.threshold]
        });
        this.observerAll();
    }
    // 加载目标图片
    lazySingle(imageBox) {
        const img = imageBox.querySelector('img');
        const realSrc = img.getAttribute(this.attr);

        img.src = realSrc;
        img.onload = () => {
            img.style.transition = `opacity ${this.speed}ms`;
            img.style.opacity = 1;
            img.removeAttribute(this.attr);
            this.callback.call(this, imageBox);
        };
    }
    observerAll(isRefresh) {
        // 找到所有带lazy-image属性的img元素
        const imgs = this.context.querySelectorAll(`img[${this.attr}]`);

        imgs.forEach(img => {
            // 找到每一个img的父节点
            const imgBox = img.parentNode;

            // 解决快速下拉出现的问题
            if (isRefresh && this.imageBoxesList.includes(imgBox)) return;
            // 监听这个父节点的出现
            this.ob.observe(imgBox);
            this.imageBoxesList.push(imgBox);
        });
        // 删除无效元素
        this.imageBoxesList = this.imageBoxesList.filter(image => image.querySelector('img').getAttribute(this.attr));
    }
    refresh() {
        // 重新监听
        this.observerAll(true);
    }
}