超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

最近接手一个项目 。 结果光安装依赖都出现了一堆 麻烦 。
好不容易处理完一个 , 又来一个 。头疼啊
看到之前有一些预加载的学习笔记。于是又查查找找 ,想想写写 把预加载和懒加载的笔记写完整
发现制图挺麻烦的!不知道你们有没有什么推荐?
写了挺久的这篇文章,有什么不对的地方欢迎评论或私聊指出

图片的预加载和懒加载

预加载和懒加载的字眼总会看到 。 其实预加载和懒加载不仅仅是用于加载图片资源。其他资源,文字,视频。都可以。

我们较常用或较需要使用的场景就是加载图片资源。

这里,我们也只讨论加载图片资源。

图片预加载

什么是图片的预加载?

提前加载所需要的图片资源,加载完毕后会缓存到本地,当需要时可以立马显示出来,以达到在预览的过程中无需等待直接预览的良好体验。

简而言之 : 就是需要显示前先加载

什么场景可能需要使用预加载

  • 漫画

我们知道漫画是一张一张连续的图片组成 。我们在看漫画时,也是一张一张看的。

但如果当我们看完一张,切换到下一张时再加载图片,那么就会有一段空白的加载时间。而且漫画图片一般比较大,如果网络不是很好,那么加载时间就会比较久。体验就会下降。

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

正如图片所示,如果我们看完 2 了 ,想看 33 却还没加载完就会大大降低体验感。

而如果能在我们预览 2 这段时间里就提前加载好 3 , 等到我们看 3 时就直接里面显示。那么就体验会好很多。

  • 图片画廊

希望用户预览图片时能够顺畅 ,而不是看到下一页的图片了,上一页的还没加载出来。

与漫画类似,但加载的图片资源会比较多。

  • 或者其他加载会比较耗时但不希望让用户看到加载时空白的场景

如要一次性显示好几张图片 ,如果让用户在看的时候加载,那么图片先后显示的过程,和出现空白的现象会让用户看见。

特别是网络不好时,用户看见空白不知道是根本没有图片,还是加载不出来。

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

如果我们使用预加载,先显示一个进度条,告诉用户加载了多少了,当加载了百分之百后再一起全部显示出来。

那么用户起码不会看到空白,也知道是在加载中,并且根据进度条能大概判断一下加载速度。那么体验就会上升。

预加载的缺点

  • 预加载会占用较多的后台资源,因为可能一次性加载较多的图片

  • 预加载需要比较长的时间 ,一般是利用用户进行其他操作时进行。(如漫画是在用户看上一个图片时进行预加载 ) 。 或者是在等待的这段时间显示其他。(如显示进度条。

图片预加载的方法

这里介绍两个常用且实用的方法 。每个方法都有优缺点 。 也有自己使用的场景。我们应该根据实际选择合适的方法

1. 通过 CSS

步骤
  • 创建用来预加载的标签
  • 给标签使用背景图,背景图的路径是需要预加载的图片资源。并且将图片移到看不见的地方,或缩小到看不见。不能用 display=none , 否则预加载会失效
  • 当使用到已经预加载好的图片时,会直接使用缓存好的图片资源,而不需要向服务器发送请求。

demo

<!--HTML文件  -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CSS 预加载</title>
    
    <!-- 引入预加载样式文件 -->
    <link rel="stylesheet" href="./preLoad.css">
    
</head>
<body>
    <!-- 点击时显示图片  -->
    <button οnclick="show()">显示</button>
    
    <ul>
        <!-- 用来预加载的标签  -->
        <li id="preImg1"/>
        <li  id="preImg2"/>
        <li sid="preImg3"/>
    </ul>
</body>
<script>
    var show = function () {
        // 点击 ul 里的标签 ,显示图片
        document.querySelector("ul").innerHTML = `
        <li><img src="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"></li>
        <li><img src="https://img2.baidu.com/it/u=2480106139,2144834787&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=552"></li>
        <li><img src="https://img1.baidu.com/it/u=1904128529,1389594536&fm=26&fmt=auto&gp=0.jpg"></li>`
    }
</script>
</html>
/* 预加载样式文件 */
img {
    width: 200px;
}
/* 预加载文件 */
#preImg1 {
    background-image: url('https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg');
    width: 0;
    height: 0;  /* 隐藏用来预加载的标签 */
}

#preImg2 {
    background-image: url('https://img2.baidu.com/it/u=2480106139,2144834787&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=552');
    width: 0;
    height: 0;
}

#preImg3 {
    background-image: url('https://img1.baidu.com/it/u=1904128529,1389594536&fm=26&fmt=auto&gp=0.jpg');
    width: 0;
    height: 0;
}
分析 :

在游览器第一次打开这个文档时,加载 CSS 过程中遇到需要加载的背景图片资源,于是会发送请求到服务器,服务器返回图片资源,游览器本地缓存。

在游览器的开发者根据(F12) 的 NewWork 项我们可以看到请求码是 200 。是服务器返回的新的响应。

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

此时我们先清空游览器请求资源记录

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

然后点击显示按钮,将填写了 src 的图片标签放到文档中。显示图片

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

图片里面显示,而 newWork 栏里也没有向服务器发送新的请求,因为服务器就有缓存。即使有 状态码也是 304 (表示本地资源)

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

2. 通过 JavaScript

步骤
  • 将需要预加载的图片资源的 URL 保存在数组里
  • 循环遍历 URL 数组执行以下步骤,直到结束
  • 创建一个 image 对象 new Image()
  • 将 image 对象的 src 属性的值指定为预加载图片的 URL
demo
<!-- HTML 文件 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>JS 预加载</title>
    <!-- 简单的样式,不用关注 -->
    <style>
        ul {
            float: left;
            margin: 0;
            padding: 0;
        }

        li {
            list-style-type: none;
            float: left;
        }

        img {
            width: 100px;
            height: 100px;
        }

        #mask {
            position: absolute;
            width: 300px;
            height: 100px;
            background-color: rgb(177, 177, 177);
            line-height: 100px;
            text-align: center;
            color: rgb(255, 255, 255);
            font-size: 32px;
            font-weight: 600;
        }
    </style>
</head>

<body>
     <!-- 设置遮罩层,显示加载进度 -->
    <div id="mask">00%</div>
    <ul>
        <!-- 写好标签,会解析到此时会立马加载 -->
        <li><img src="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"></li>
        <li><img src="https://img2.baidu.com/it/u=2480106139,2144834787&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=552">
        </li>
        <li><img src="https://img1.baidu.com/it/u=1904128529,1389594536&fm=26&fmt=auto&gp=0.jpg"></li>
    </ul>
</body>
     <!-- 引入预加载脚本文件 -->
<script src="./preLoad.js"></script>
</html>
// 预加载脚本文件
// 预加载的图片 URL 
const urlList = [
    'https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg',
    'https://img2.baidu.com/it/u=2480106139,2144834787&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=552',
    'https://img1.baidu.com/it/u=1904128529,1389594536&fm=26&fmt=auto&gp=0.jpg']

// 预加载函数 ,会在加载完毕后隐藏遮罩层
function preLoad() {
    let process = document.querySelector('#mask'),  // 遮罩层 ,用于修改进度和隐藏
        len = urlList.length,// 提出 URL 数组长度 ,提供性能
        count = 0,  // 计算已加载数量和修改进度
        ul = document.querySelector("ul"), // 将图片放入此
        imgList="";  // img 标签字符串临时存放点,避免刷新DOM的次数。
 
     // 为了模拟多图片资源和将加载过程慢下来,这里使用了计时器 , 实际情况我们采用遍历 URL 数组。
    let id = setInterval(() => {
        let img = new Image()
        img.src = urlList[count]
        imgList += `<li><img src="${urlList[count]}"></li>`
        img.onload = img.onerror = function () {
            count++;
            process.innerText = (count / len * 100).toFixed(2) + '%'; // 设置进度百分比
            if (count === len) {
                clearInterval(id)
                ul.innerHTML += imgList;
                process.style.display = 'none'
            }
        }
    }, 500)
}

// 调用预加载函数
preLoad()
分析

当需要显示大量图片时,我们先显示其他或者有一些加载的提示。同时执行预加载函数。这里我们使用了遮罩层和进度显示

预加载函数会加载图片资源并本地缓存,不会显示。但有缓存。在加载的过程中,随着加载完成的数量增加,进度增加。

当全部加载完毕后,将图片动态添加到页面上,隐藏遮罩层。图片可被立即显示,无需再发送请求到服务器请求资源。
超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。
超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

预加载分类

无序预加载

如预览大量图片时,或表情包,它关心的是什么时候全部加载完,然后一起显示出来,其顺序不重要

表情包的预加载
const srcList = ['src1','src2','src3',...]
function preLoad() {
    let count = '';
        for (let i = 0; i < emojiSrcs.length; i++) {
            var img = new Image()	
            img.src = srcList[i]  
            
            img.onload = img.onerror = function () {	// 无论加载成功还是失败都会执行
                count++
                if (count == emojiSrcs.length - 1) {		// 如果全部加载完毕
                    mask.style.display = "none";		// 隐藏遮挡层
                    emoji.style.display = "block";		// 显示emoji
                }
            }
        }
    }
 preLoad()

有序预加载

如漫画,它不仅仅关系什么时候全部加载完,还有需要有一定的顺序。

    // 使用全局变量,保存计数
    var count = 0
    const emojiSrcs = ['src1','src2','src3',...]
    function preLoad() {
        var img = new Image()
        img.src = emojiSrcs[count]
        count++
        // 加载成功和失败都会执行
        img.onload = img.onerror = function () {
   		   if(count === emojiSrcs.length)
          	  return
           preLoad()
        }
    }
	preLoad()
	// 当第一张加载后会加载第二张,第二种加载后再加载第三张,于是可以在我们预览第一张时就可以预先加载第二张,第三张
	// 相比无序加载,不需要等使用加载完毕才能预览,而且不会预览到第二张时,第二张没加载好,但是第三张或更后的图片先加载了。

图片的懒加载

什么是懒加载

图片的懒加载是等图片在用户要看到时才进行加载 。

与预加载相反 。 预加载可以说是勤劳,先做完然后万事大吉 。 而懒加载却是需要我再做 , 不要我就准备着。

简而言之 懒加载就是 先显示再加载

什么场景需要懒加载

  • 电商

我们知道大量商品展示是分页请求的 。也就是我们会看到拉到底了再重新加载。但屏幕看到的仅是一页请求内容的部分内容。

电商搜索产品时图片会有很多,但单个图片资源又不大。因此适合应用懒加载。避免不必要的图片的加载和请求。

如 :当我们在某宝,某东,某夕夕买东西时,假设搜索完后看到一个进度条,告诉我们还在加载。或者是显示一堆空白,然后先加载出来的先显示(但可能需要往下翻一翻) 。可是 ,我们可能选择的是第一家店 。 那么其他的就并不需要加载了。

  • 其他图片较多场景

这与画廊在线图库这里场景不一样 。 图片多可能主要显示的不仅仅是图片 。 如显示大量文章,文章有一些配图 。

懒加载的缺点

  • 需要监听图片是否显示,比较耗游览器性能。
  • 图片是显示时才去加载。如果网络不太好可能会有一段时间是空白

图片懒加载的方法

原理

利用自定义属性将图片的 URL 存放到图片标签身上 , 图片的 src 为空或者用其他较小的图片资源代替(提示加载中)

图片的懒加载主要是监听 body 或者其他存放图片且滚动的元素的 scroll 事件.

在每一次事件触发时,通过相关的 API 和属性,检查图片是否显示 。

如果显示就将 URL 填到 src 属性中 ,加载图片 。如果没有显示则不进行操作。
超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

如何检测图片是否显示

对于理解懒加载的原理其实不难 。 懒加载的关键点也是重难点就在于如何检测图片是否处理可视区内。

下面借助两张图来理解和学习。

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

  • clientHeight : 可视区高度

可视区高度可使用 window.innerWidth 获得 。但这个属性 IE9 以下并不支持。 因此兼容性写法为:

let clientWidth=window.innerWidth|| document.documentElement.clientWidth|| document.body.clientWidth;

关于此兼容

  • offsetTop

子图片相对父元素的偏移量。let offsetTop = imgNode.offsetTop

  • scrollTop

滚动元素顶部被滚出显示区的高度。 let scrollTop = scrollNode.scrollTop

**注意 ,这个元素必须是有滚动条才可以,否者是 0 **

如果滚动区域是整个页面,也就是仅 HTML 出现了滚动条 ,其他不设置滚动条 。

那么通过 document.documentElement.scrollTop 来获取 scrollTop 的值

一般,我们只检查整个页面被隐藏部分

如上图所示 : 当 scrollTop + clientHeight > offsetTop 时 , 图片便是显示出来或者在可视区之上了。

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

  • offsetHeight : 图片节点的宽高

可以通过设置好的高度自己设置。写死。

或者

element.clientHeight  // 内容加内边距
element.offsetHeight  // 内容加内边距加边框

如上图所示 :当 offsetTop + offsetHeight > scrollTop 时 , 图片就在可视区内或可视区下。

结合两个图

if ( v.offsetTop < clientHeight + scrollTop &&  v.offsetTop + v.offsetHeight > scrollTop) {
  // 图片在显示区内
}

demo

// 懒加载
// 参数 : 需要进行预加载的图片的 id (#id) , 类名(.class) ,或标签名(img)
export default function lazyLoad(imgSelector) {

  const clientHeight = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, // 兼容获取
    scrollTop = document.documentElement.scrollTop;  //  获取页面被卷出去的顶部的高度大小

  let imgsNodes = document.querySelectorAll(imgSelector);  // 获取预加载图片列表


  // 遍历检查图片是否在可视区内
  imgsNodes.forEach((v) => {
    if (
      v.offsetTop < clientHeight + scrollTop &&
      v.offsetTop + v.offsetHeight > scrollTop
    ) {
      // 设置正确的 src 

      // 实际使用时
      // v.src = v.getAttribute("srcString");

      //  这里使用计时器模拟加载时间 ,为能看到是先显示再加载的效果 。
      setTimeout(() => {
        v.src = v.getAttribute("data-srcString");
      }, 800);
    }
  });
}

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>懒加载</title>
</head>
<style>
    img {
        width: 300px;
        height: 300px;
        display: block;
        margin: 0 auto;
        border: 1px solid #ccc;
    }
</style>

<body>
    <div>
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
        <img data-srcString="https://img1.baidu.com/it/u=3263944338,3726722345&fm=26&fmt=auto&gp=0.jpg"
            src="https://z3.ax1x.com/2021/09/04/hguVeK.png">
    </div>
</body>

<script type="module">
    // 导入模块
    import lazyLoad from './lazyLoad.js'
    // 使用 onl oad , 以免文档没有加载完就进行操作
    window.onload  = function(){
        // 先调用一次,将可能在显示区的照片先加载
        lazyLoad('img')
        // 监听滚动事件
        window.addEventListener('scroll',lazyLoad.bind(this,'img'))  // 利用 bind() 传参
    }
</script>
</html>

懒加载优化

前面我们提高懒加载的缺点 , 它对服务器友好,对游览器却不友好。

因为一直监听滚动事件,一直触发是非常消耗性能的 。

对此我们可以进行防抖和节流来减少性能的消耗 。提高流畅度。

节流

图片是有一定的宽高的,并不是稍微移动一点点就进行获取图片节点呀 然后遍历呀 。

其他不变,只修改 HTML 的 script 标签内的代码

<script type="module">
    import lazyLoad from './lazyLoad.js'

    window.onload  = function(){
        lazyLoad('img')  // 先执行

        let stop = true;  // 节流阀
        
        function throttle (){  // 节流函数
         if(stop){   // 如果被节流了
            setTimeout(() => {
                stop = false
                console.log('节流');
            }, 100);
            return 
         }
         stop=true
         lazyLoad('img');
         return;
        }

        window.addEventListener('scroll',throttle)  //  scroll 的执行函数改为节流函数
    }
</script>

超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。

当滑动滚动条时 , 可以看到 节流时输出节流 和 执行时输出执行 是有交替的 。 达到了一定的节流效果。

防抖

为什么要防抖,因为可能有以下这个场景 。我们知道 我们要的图片得往下滑好几页 。 所以前面的几页图片是可以不需要显示的。

或者前面的之前我们已经预览过了,我希望预览比较靠后的图片。那么前面的也可以不用去加载了。

那么这种情况就可以使用防抖技术了。

其他不变 ,在原来的节流函数进行小修改

    let stop = true;
    let timeID = '';   // 增加一个变量保存倒计时器的 ID

    function throttle (){
     if(stop){
        setTimeout(() => {
            stop = false
        }, 100);

        return;
     }
     stop=true

     // 删除上一个倒计时器  
    clearTimeout(timeID)
	// 重新设置倒计时器
    timeID = setTimeout(()=>{
        lazyLoad('img');
        return 
    },100)  // 延缓 0.1 秒后加载
    }   

但防抖大部分情况是不需要的,除非在防抖这段时间内的空白无太大影响

参考文章

js实现图片懒加载原理

js原生实现高性能懒加载(分步解析)

上一篇:在vue页面监听中如何修改子元素的样式


下一篇:javascript-$(window).scrollTop()在Safari中无法正常工作