超详细的图片预加载和懒加载教程

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

图片的预加载和懒加载

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

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

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

图片预加载

什么是图片的预加载?

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

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

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

  • 漫画

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

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

超详细的图片预加载和懒加载教程_第1张图片

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

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

  • 图片画廊

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

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

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

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

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

超详细的图片预加载和懒加载教程_第2张图片

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

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

预加载的缺点

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

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

图片预加载的方法

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

1. 通过 CSS

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

demo





    
    CSS 预加载
    
    
    
    


    
    
    
    
/* 预加载样式文件 */
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 。是服务器返回的新的响应。

超详细的图片预加载和懒加载教程_第3张图片

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

超详细的图片预加载和懒加载教程_第4张图片

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

超详细的图片预加载和懒加载教程_第5张图片

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

超详细的图片预加载和懒加载教程_第6张图片

2. 通过 JavaScript

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


<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 += `
  • ${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()
    分析

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

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

    当全部加载完毕后,将图片动态添加到页面上,隐藏遮罩层。图片可被立即显示,无需再发送请求到服务器请求资源。
    超详细的图片预加载和懒加载教程_第7张图片
    超详细的图片预加载和懒加载教程_第8张图片

    预加载分类

    无序预加载

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

    表情包的预加载
    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 属性中 ,加载图片 。如果没有显示则不进行操作。
    超详细的图片预加载和懒加载教程_第9张图片

    如何检测图片是否显示

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

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

    超详细的图片预加载和懒加载教程_第10张图片

    • innerHeight : 可视区高度

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

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

    关于此兼容

    • offsetTop

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

    • scrollTop

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

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

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

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

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

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

    超详细的图片预加载和懒加载教程_第11张图片

    • 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.innerHeight|| document.documentElement.clientHeight || document.body.clientHeight , // 兼容获取
        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);
        }
      });
    }
    
    
    
    
    
    
        
        
        
        懒加载
    
    
    
    
        

    懒加载优化

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

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

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

    节流

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

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

    
    

    超详细的图片预加载和懒加载教程_第12张图片

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

    防抖

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

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

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

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

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

    你可能感兴趣的:(前端优化,预加载,懒加载,前端,前端优化)