如何实现图片懒加载?

2,213 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

有时候我们的前端页面可能会包含很多图片,例如一些购物网站或者图片网站。页面图片多,加载的图片就多,服务器压力就会很大。不仅影响页面渲染速度还会浪费带宽,比如一张1M大小的图片,同时有1000个人访问,就会产生1G的带宽。 为了解决这些问题,提高用户体验,就出现了懒加载的方式,那什么是懒加载呢,就是进入页面的时候,只请求可视区域的图片资源。懒嘛,我不会把全部图片都给你,你要看多少,我就给你多少。

实现方式

html实现

这个是最简单的一种方式,直接给img标签加上loading="lazy"

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

js实现

我们通过js监听页面的滚动,判断当前图片是否到了可视区域:

  • 拿到所有的图片dom;
  • 遍历每张图片判断当前图片是否到了可视区域范围内;
  • 如果到了就设置图片的src属性;
  • 绑定scroll事件,对其进行事件监听。 在页面初始化的时候,图片的src放在data-src属性上,当元素处于可视范围内的时候,就把data-src赋值给src属性。
<body> 
    <img src="./img/default.png" data-src="./img/1.jpg" />
    <img src="./img/default.png" data-src="./img/2.jpg" />
    <img src="./img/default.png" data-src="./img/3.jpg" />
    <img src="./img/default.png" data-src="./img/4.jpg" />
    <img src="./img/default.png" data-src="./img/5.jpg" />
    <img src="./img/default.png" data-src="./img/6.jpg" />
    <img src="./img/default.png" data-src="./img/7.jpg" />
    <img src="./img/default.png" data-src="./img/8.jpg" />
    <img src="./img/default.png" data-src="./img/9.jpg" />
    <img src="./img/default.png" data-src="./img/10.jpg" />
</body>

先获取所有图片dom,通过document.body.clientHeight获取可视区高度,再使用element.getBoundingClientRect()直接得到元素相对浏览的top值,遍历每张图片判断是否到了可视区内。

function lazyload() { 
    let viewHeight = document.body.clientHeight //获取可视区高度 
    let imgs = document.querySelectorAll('img[data-src]') 
    imgs.forEach((item, index) => { 
        if (item.dataset.src === '') return 
        // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置 
        let rect = item.getBoundingClientRect() 
        if (rect.bottom >= 0 && rect.top < viewHeight) { 
            item.src = item.dataset.src item.removeAttribute('data-src') 
        } 
    }) 
}

最后绑定scroll事件

window.addEventListener('scroll', lazyload)

这样就完成了图片的懒加载操作了,不过呢,这样又重新带来了一个新的性能问题,因为页面滚动会不断地触发scroll事件,所以我们还需要一个“节流”函数。不知道节流函数的,可以看我前面的文章《JavaScript“防抖”和“节流”详解与应用》

// 节流函数
function throttle(fn, delay) {
    let flag = true;
    return function() {
        if(!flag) return;
        flag = false;
        setTimeout(() => {
            fn.apply(this, arguments);
            flag = true;
        }, delay)
    }
}

然后给scroll加上节流

window.addEventListener('scroll', throttle(lazyload, 200))

大功告成!

如果是在vue中的话,我们可以使用自定义指令v-lazy来完成图片懒加载

const LazyLoad = { 
    // install方法 
    install(Vue,options){
        // 代替图片的loading图 
        let defaultSrc = options.default; 
        Vue.directive('lazy',{ 
            bind(el,binding){ 
                LazyLoad.init(el,binding.value,defaultSrc); 
            }, 
            inserted(el){ 
                // 兼容处理 
                if('IntersectionObserver' in window){ 
                    LazyLoad.observe(el); 
                }else{ 
                    LazyLoad.listenerScroll(el); 
                } 
            }, 
        }) 
    }, 
    // 初始化 
    init(el,val,def){ 
        // data-src 储存真实src 
        el.setAttribute('data-src',val); 
        // 设置src为loading图 
        el.setAttribute('src',def); 
    }, 
    // 利用IntersectionObserver监听el 
    observe(el){ 
        let io = new IntersectionObserver(entries => { 
            let realSrc = el.dataset.src; 
            if(entries[0].isIntersecting){ 
                if(realSrc){ 
                    el.src = realSrc; 
                    el.removeAttribute('data-src'); 
                } 
            } 
        }); 
        io.observe(el); 
    }, 
    // 监听scroll事件 
    listenerScroll(el){ 
        let handler = LazyLoad.throttle(LazyLoad.load,300); 
        LazyLoad.load(el); 
        window.addEventListener('scroll',() => { 
            handler(el); 
        }); 
    }, 
    // 加载真实图片 
    load(el){ 
        let windowHeight = document.documentElement.clientHeight 
        let elTop = el.getBoundingClientRect().top; 
        let elBtm = el.getBoundingClientRect().bottom; 
        let realSrc = el.dataset.src; 
        if(elTop - windowHeight<0&&elBtm > 0){ 
        if(realSrc){ 
            el.src = realSrc; 
            el.removeAttribute('data-src'); 
        } 
    } 
}, 
// 节流 
throttle(fn, delay) {
    let flag = true;
    return function() {
        if(!flag) return;
        flag = false;
        setTimeout(() => {
            fn.apply(this, arguments);
            flag = true;
        }, delay)
    }
}
export default LazyLoad;