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

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


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

图片的预加载和懒加载

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

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

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

图片预加载

什么是图片的预加载?

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

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

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

  • 漫画

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

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

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

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

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

  • 图片画廊

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

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

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MnO4gfyt-1630744528325)(C:\Users\Administrator\Desktop\桌面\前端\预加载大量图片.png)]

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

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

预加载的缺点

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

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

图片预加载的方法

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

1. 通过 CSS

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

demo

        CSS 预加载                  
var show = function () { // 点击 ul 里的标签 ,显示图片 document.querySelector("ul").innerHTML = `
  • 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。
  • 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。
  • ` }
    /* 预加载样式文件 */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
    <!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 += `
  • <img src="${urlList[count]}">
  • `
    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);    }  });}
                    懒加载    img { width: 300px; height: 300px; display: block; margin: 0 auto; border: 1px solid #ccc;    }    
    超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。 超详细的图片预加载和懒加载教程。分析应用场景。大量插图。完整的代码。累死我了。
    // 导入模块 import lazyLoad from './lazyLoad.js' // 使用 onload , 以免文档没有加载完就进行操作 window.onload = function(){ // 先调用一次,将可能在显示区的照片先加载 lazyLoad('img') // 监听滚动事件 window.addEventListener('scroll',lazyLoad.bind(this,'img')) // 利用 bind() 传参 }

    懒加载优化

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

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

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

    节流

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

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

        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 的执行函数改为节流函数    }

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

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

    防抖

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

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

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

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

        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原生实现高性能懒加载(分步解析)