前端优化之图片懒加载

4,833 阅读2分钟

http请求

如果你学过计算机网络你就会知道,我们请求一个带有n张图片的html文件实际上会发送n+1次请求,因为在浏览器解析html的时候遇到了src,就会请求src后面的内容。

设想一下如果我们的页面有1000000张图片,那么如果等待这些图片响应成功并加载完成时延是非常大的,用户体验非常差。

那么我们可不可以让图片按需加载呢?当图片出现在可视区的时候再加载它而不是一开始就加载完全部图片。

图片懒加载

template:

<div @scroll="lazyLoad" ref="lazy">
    <img v-for="(src, index) in imgs" src="##" :dataSrc="src" :key="index">
    <!--more img-->
</div>

改变图片src

监听最外层div的滚动事件,触发滚动时遍历图片检测图片位置,若在可视区内则显示

loadImg() {
    var img = this.$refs.lazy.getElementsByClassName("lazyImg"); 
    // 已滚动高度+可视区高度
    var top = this.$refs.lazy.scrollTop + this.$refs.lazy.clientHeight;
    
    for(var i = 0; i < img.length; i++) {
        if(img[i].offsetTop <= top) {  // 在可视区内则显示图片
            img[i].src = img[i].getAttribute("datasrc");
        }
    }
},
lazyLoad() { 
    this.loadImg();
}

以上就实现了一个图片懒加载,本篇文章就到这里,再见。

桥豆麻袋,突然发现一个严重的问题:滚动过程中会不断触发lazyLoad对图片做一个遍历并判断,那么就会做无数次for循环,更可怕的是,修改一次src会发送一个请求,在滚动的时候我们的for循环每次都从头判断并修改src请求图片,那么请求次数可想而知。

函数防抖

如果在滚动过程中不断触发遍历并判断图片是否在可视区的监听事件,会耗费很大的性能,这里采用函数防抖:当用户停止滚动时统一遍历判断图片位置

debounce(fn) {
    // 函数防抖:用户停止操作之后触发
    clearTimeout(this.timer);
    this.timer = setTimeout(() => {
        fn();
    }, 1000);
}

我们可以将加载图片的方法放在debounce中

lazyLoad() {
    this.debounce(this.loadImg);
}

这样当用户滚动页面时,松开手才会执行loadImg来遍历判断图片位置。

又出现了一个问题:如果用户在滚动时从页面底部上拉到顶部一直没有松手,那么在这期间都不会执行loadImg,这意味着页面的图片都不会显示,非常影响用户体验

防抖优化

我们规定,若用户上拉高度大于500px那么就自动加载一次可视区内图片,这里我们用oldScrollTop记录上次上拉高度

lazyLoad() {
    // 如果上拉距离大于500px则自动加载
    if(this.$refs.lazy.scrollTop - this.oldScrollTop > 500) {
        this.loadImg();
        this.oldScrollTop = this.$refs.lazy.scrollTop; // 更新oldScrollTop
    } else {  // 如果向下拉但小于500px则防抖加载
        this.debounce(this.loadImg);
    }
}

下拉优化

当用户下拉的时候我们并不需要执行lazyLoad,因为我们之前的图片已经加载过了,所以可以修改一下lazyLoad

lazyLoad() {
    // 如果上拉距离大于500px则自动加载
    if(this.$refs.lazy.scrollTop - this.oldScrollTop > 500) {
        this.loadImg();
        this.oldScrollTop = this.$refs.lazy.scrollTop;
    } else if(this.$refs.lazy.scrollTop - this.oldScrollTop < 0) {  // 如果向下拉则不做操作
        return ;
    } else {  // 如果向下拉但小于500px则防抖加载
        this.debounce(this.loadImg);
    }
}

减少遍历个数

最重要的优化已经做完了,但是还可以从一些小细节更加优化一下,我们的loadImg方法中每次都是从0号下标开始遍历检查图片,但是在用户上拉操作之后一部分图片已经被加载了,就不需要再次去检查了。

我们可以用一个变量len记录上一次被加载后的最后一个图片,然后修改一下loadImg

 loadImg() {
    var img = this.getImages(); 
    var top = this.$refs.lazy.scrollTop + window.screen.height;
    
    // 从len开始检查
    for(var i = this.len; i < img.length; i++) {
        if(img[i].offsetTop <= top) {
            img[i].src = img[i].getAttribute("datasrc");
            this.len = i;  // 更新len
        }
    }
}

结语

一个完整的优化版图片懒加载就完成了,我将该功能封装成了一个vue的插件

源码:vue-plugins

该插件库会持续更新,欢迎star & fork~