Web性能优化-图片懒加载

211 阅读2分钟

原理

  • 首先,不给懒加载的标签设置‘src’属性,并且设置自定义属性‘data-src’,值为图片路径,并且设置class=“lazy-load”

  • 然后,等页面加载完成,获取class为‘lazy-load’的元素集合并且遍历,看哪个元素在可视区域内,将在可视区域内的元素的src属性设置为‘data-src’的值,并且删除该元素class属性上的‘lazy-load’类(从集合删除)

实现方式

方式1 - getBoundingClientRect

let dom = document.getElementById('id'),
    rect = dom.getBoundingClientRect();

const {top, bottom, left, right} = rect;

根据el.getBoundingClientRect()获取元素的大小及其相对于视口的位置信息
根据document.documentElement.clientHeight获取可视视口的高度
监听scroll事件,每一次滚动都去遍历集合中的img元素,判断其是否在视口的可视范围内
并使用节流函数(throttle)优化滚动事件的性能

// 懒加载函数							
function lazyLoad() {
    // 浏览器可视范围高度							
    let clientH = document.documentElement.clientHeight;

    // 根据标识,获取懒加载img元素集合							
    let imgs = document.querySelectorAll('.lazy-load');

    imgs.forEach(img => {
        // top:图片顶部到视口顶部距离;bottom:图片底部到视口顶部距离							
        let {top, bottom} = img.getBoundingClientRect();
        // 判断img是否在可视范围内							
        if(top <= clientH && bottom >= 0) {
            img.src = img.dataset.src;
            img.classList.remove('lazy-load');
        }
    });
}

// 节流函数返回的函数							
let throttled = throttle(lazyLoad);

// 监听滚动事件							
window.addEventListener('scroll', throttled);							

缺点: 需要监听scroll事件,scroll事件是伴随着大量计算,虽然我们可以通过节流函数来提高性能,但还是会有性能浪费的问题。

方式2 - IntersectionObserver

根据IntersectionObserver构造函数创建观察者,观察元素是否在可视范围内。

function lazyLoad() {
    // 创建观察者							
    let iObserver = new IntersectionObserver((entries, observer) => {
        /*							
        * 观察元素在可视范围内的回调							
        * */
        entries.forEach(enter => {
            // 如过在可视范围内							
            if (enter.isIntersecting) {
                // 获取观察的img元素							
                let img = enter.target;

                img.src = img.dataset.src;
                img.classList.remove('lazy-load');

                // 停止观察							
                iObserver.unobserve(img);
            }
        });
    });

    // 根据标识,获取懒加载img元素集合							
    let imgs = document.querySelectorAll('.lazy-load');
    imgs.forEach(img => {
        // 将img元素添加观察							
        iObserver.observe(img);
    });
}

lazyLoad();							

缺点: IntersectionObserver的兼容性差。

最终方案 - 方式1 + 方式2

直接上代码

class LazyLoad {
    constructor(el) {
        // 懒加载img元素集合							
        this.imgs = document.querySelectorAll(el);
        this.init();
    }

    // 初始化							
    init() {
        // 是否兼容 IntersectionObserver							
        if ('IntersectionObserver' in window) {
            this.lazyLoadByIntersectionObserver();
        } else {
            this.lazyLoadByScroll();
        }
    }

    // 通过IntersectionObserver实现的懒加载							
    lazyLoadByIntersectionObserver() {
        // 创建观察者							
        let iObserver = new IntersectionObserver((entries, observer) => {
            /*							
            * 观察元素在可视范围内的回调							
            * */

            entries.forEach(enter => {
                // 如过在可视范围内							
                if (enter.isIntersecting) {
                    // 获取观察的img元素							
                    let img = enter.target;

                    img.src = img.dataset.src;
                    img.classList.remove('lazy-load');

                    // 停止观察							
                    iObserver.unobserve(img);
                }
            });
        });

        this.imgs.forEach(img => {
            // 将img元素添加观察							
            iObserver.observe(img);
        });
    }

    // 滚动事件的懒加载							
    lazyLoadByScroll() {
        // 节流函数返回的函数							
        let throttled = throttle(this.scrollFn);

        // 监听滚动事件							
        window.addEventListener('scroll', throttled.bind(this));
    }

    scrollFn() {
        // 浏览器可视范围高度							
        let clientH = document.documentElement.clientHeight;

        this.imgs.forEach(img => {
            // top:图片顶部到视口顶部距离;bottom:图片底部到视口顶部距离							
            let {top, bottom} = img.getBoundingClientRect();

            // 判断img是否在可视范围内							
            if (top <= clientH && bottom >= 0) {
                img.src = img.dataset.src;
                img.classList.remove('lazy-load');
            }
        });
    }
}

new LazyLoad('.lazy-load');	

效果


最后附上代码