图片的懒加载

目录

需求

思路分析

判断图片是否在可视区域内

图片设置

父元素设置

滚动事件回调函数

问题

优化

只记录还没有加载的图片

节流

实现效果

总结

代码


需求

工作的时候暂时没有活,所以写个懒加载的踩坑记录。需求就是最平常的图片懒加载,使用一个数组存储所有图片的地址,等到图片出现在可视区域内的时候才去设置其src属性,这样图片才会加载。

思路分析

判断图片是否在可视区域内

这个是最关键的地方,只有正确判断图片中可视区域中才可以正确加载图片。img是图片,parent是其容器,我的具体判断公式如下:

img.offsetHeight + img.offsetTop > parent.scrollTop && img.offsetTop < parent.scrollTop + parent.clientHeight

公式分为两部分。前半部分是判断图片是否能够进入可视区域,后半部分是判断图片是否离开可视区域。其逻辑可以画图捋一下,不再细说。

图片设置

对于一个图片,我们一开始不想加载它,所以用background设置背景图片作为占位。并且,我们还要把它的地址存到它的身上,以便于加载。可以考虑存在元素的dataset中,比如img.dataset.src。这样,对于每个图片,应该做如下设置:

这是使用原生HTML写的,如果采用vue等框架可以直接遍历数组地址产生img。还需要给img标签指定类名,我写为lazy。然后给lazy类设置统一样式:

.lazy {
            width: 400px;
            height: 400px;
            background-image: url("https://cn.vuejs.org/images/logo.svg");
            background-repeat: no-repeat;
            background-size: contain;
            margin-bottom: 50px;
        }

这里用背景实现占位,background-image就是默认图片。后面如果给图片设置了src,就可以盖住背景了。

父元素设置

我简单使用一个类名为container的div包裹图片:

然后给container类设置样式:

.container {
            background-color: floralwhite;
            width: 60vw;
            height: 100%;
            overflow-y: auto;
            margin: 0 auto;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

这样,所有图片就是水平居中排列的了。

滚动事件回调函数

要实现懒加载,必须监听滚动事件,在监听函数中判断哪些图片在可视区域,然后加载它。刚刚已经实现了判断图片是否在可视区域的逻辑,现在封装滚动事件的回调函数:

// 判断图片是不是在可视区域内
// parent:父元素 img:图片元素
function ifInview(parent, img) {
       return img.offsetHeight + img.offsetTop > parent.scrollTop && img.offsetTop < parent.scrollTop + parent.clientHeight
        }

// 滚动事件的回调函数
function load() {
       console.log('load')
       let parent = document.getElementsByClassName('container')[0];
       let img_list = document.getElementsByClassName('lazy');
       for (let i = 0; i < arr.length; i++) {
           let img = img_list[i];
           if (ifInview(parent, img)) {
               img.src = img.dataset.src;
           }
    }
}

然后给父元素绑定scroll事件监听即可:

let container = document.getElementsByClassName('container')[0]
container.addEventListener('scroll', load);

大功告成。

问题

写完这些代码发现,滚动的时候并没有触发scroll事件的回调函数。这是为什么?

检查代码,发现问题:在container中,给它设置高度为100%。container的高度继承自body,body继承自html。html中包含了6个图片,因此它就具有6个图片的高度,因此container就具有6个图片的高度。因此,对于container来讲,它根本没有溢出,所以它的overflow-y:auto根本就没有起作用!!!

解决方法就是,给container设置一个固定高度,我设为100vh。

设置后发现仍然不能触发。再细读,发现我对container使用了flex布局,这样就导致子元素的高度被限制在父元素内,无法超过父元素的高度,自然也就无法触发scroll事件。因此,需要放弃flex布局。我将img设置为块元素,然后让它们居中显示,效果一样。

改动后的代码:

.container {
            background-color: floralwhite;
            width: 60vw;
            height: 100vh;
            overflow-y: scroll;
            margin: 0 auto;
}
.lazy {
            width: 400px;
            height: 400px;
            display: block;
            background-image: url("https://cn.vuejs.org/images/logo.svg");
            background-repeat: no-repeat;
            background-size: contain;
            background-position: center;
            margin: 20px auto;
}

再次尝试,就成功了。

优化

基本功能实现后,可以对它进行优化。

只记录还没有加载的图片

首先考虑一点,每次滚动事件都会调用load函数。但是,如果现在所有图片都已经加载完毕了,就没有必要调用它了。所以,需要记录目前还没有加载的图片,下次调用只需要遍历它们即可。为此,我把获取到的子元素列表存在load函数外面,然后在每次load函数调用的时候,移除掉加载完成的图片。要实现移除,我使用Array.from将子元素伪数组转化为数组,这样就可以调用splice方法了。

改进后的代码如下:

// 获取父元素和所有子元素
let parent = document.getElementsByClassName('container')[0];
// 转换为数组
let img_list = Array.from(document.getElementsByClassName('lazy'));

// 滚动事件的回调函数
function load() {
    if (!img_list.length) {
         return;
    }
    for (let i = 0; i < img_list.length; i++) {
         let img = img_list[i];
         if (ifInview(parent, img)) {
                img.src = img.dataset.src;
                img_list.splice(i, 1)
         }
    }
}

这样,每次加载一张图片,就会把它从子元素列表中移除。当img_list的长度为0时,会直接返回。

节流

现在还有一个问题,就是每次滚动,均会触发load函数,过于频繁。因此,可以采用防抖或节流的方式,减少调用的次数。这里我采用了节流。具体思路就是,封装一个实现节流函数的方法,对load进行节流包装,然后把scroll事件的回调函数设置为这个节流函数即可。

实现如下:

// 节流函数
function throttle(func, delay) {
     let last = Date.now();
     return function () {
          let now = Date.now();
          if (now - last < delay) {
               return;
          }
          last = now;
          func.apply(window);
     }
}
// 获取节流后的回调函数
let throttle_load = throttle(load, 500);

let container = document.getElementsByClassName('container')[0]
// 将节流回调设置为scroll事件的回调函数
container.addEventListener('scroll', throttle_load);

 设置每500毫秒触发一次,实现节流效果。

实现效果

这里我使用了20张图片,效果更明显些。

懒加载演示

总结

在完成懒加载时,需要注意这么几点:

1、图片的父元素高度不能设置为100%

2、所有子元素的高度一定要超过父元素,触发溢出

3、图片是否在可视区域内的判断要准确

4、节流等优化手段

代码

附上简陋的代码。因为是实习闲暇写的,所以比较简单,没有什么复杂的逻辑,望批评指正。




    
    
    
    Document
    



    

你可能感兴趣的:(工作,css,html,css3,前端,javascript)