网页优化——图片的懒加载和预加载

412 阅读6分钟

hello,这里是哆啦美玲,我又来啦!今天主要聊聊两种常用于优化网页性能的技术的手段——懒加载(Lazy Loading)和预加载(Preloading),它们在资源加载上各有优缺点,一起来看看吧。 image.png

在实际开发过程中,页面加载时HTMLCSS 会被优先解析和渲染,但图片等资源通常需要通过额外的网络请求来异步加载。如果页面中图片数量过多或图片文件过大,这些异步加载的请求会增加页面整体加载时间,导致页面响应变慢,影响用户体验。为了解决这个问题,我们采用了懒加载和预加载技术来效优化页面加载,提升性能。

懒加载

1. 懒加载的原理——延迟加载

懒加载是指在页面加载时,只有当用户需要某个资源时,才会加载该资源。

我们以页面上需要获取多张图片为例,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        img {
            height: 300px;
            display: block;
        }
    </style>
</head>

<body>
    <img src=""
        data-src="https://tse4-mm.cn.bing.net/th/id/OIP-C.4r7SLRqOVoSDXiAOPtf2fwHaFj?cb=iwc2&rs=1&pid=ImgDetMain"
        alt="">
    <img src=""
        data-src="https://ts1.tc.mm.bing.net/th/id/R-C.51d265977bf3a4332c5c74b903643679?rik=OOLvxbhfFBlOOg&riu=http%3a%2f%2fpic.sucaibar.com%2fpic%2f201506%2f09%2fe71358f052.jpg&ehk=c0nMccsE8vhPaIfGcD1PsOo1NjYNEUIeK4F1OZRVEB4%3d&risl=&pid=ImgRaw&r=0"
        alt="">
    <img src="" data-src="https://pic4.zhimg.com/v2-f49db917e12d50ebba098285f35a236b_r.jpg" alt="">
    <img src=""
        data-src="https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/f68e1ee3246c16e51d38aac0449c87b5.jpg?x-oss-process=image%2fresize%2cm_lfit%2cw_2560%2ch_1440"
        alt="">
    <img src="" data-src="https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/c9c023166a11901eb96777a5325c50e1.jpg"
        alt="">
    <img src=""
        data-src="https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/ffc5a93fbda140c7e7bc9c52e3abc43e.jpg?x-oss-process=image/resize,m_lfit,w_1920,h_1080"
        alt="">
    <img src=""
        data-src="https://tse4-mm.cn.bing.net/th/id/OIP-C.NtgtTssFHOm6U-ZvoQWBAwHaEK?cb=iwc2&rs=1&pid=ImgDetMain"
        alt="">
    <img src="" data-src="https://www.556z.com/zb_users/upload/2020/05/202005011588265209654333.jpg" alt="">
    <img src="" data-src="https://imgxz.bizhi3.com/20231124/231124104522430.jpg" alt="">
    <img src=""
        data-src="https://ts1.tc.mm.bing.net/th/id/R-C.c7755cd5796addab56b063eff52636cd?rik=m84xmHsh7gyfVA&riu=http%3a%2f%2fn.sinaimg.cn%2ffront20200426ac%2f720%2fw1920h1200%2f20200426%2f497c-isuiksn2962057.jpg&ehk=5J4fFrAzNslWfrANQMusJ8LSu5HLI4GtXl5BxLowdts%3d&risl=&pid=ImgRaw&r=0"
        alt="">
    <img src=""
        data-src="https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/430ec838eb83f895dfe200fc37a0ed83.jpg?x-oss-process=image%2fresize%2cm_lfit%2cw_2560%2ch_1440"
        alt="">
</body>
</html>

在页面中,我们放了多个src为空的、等高的img标签用于存放图片资源,如果使用延迟加载方法,需要实现:当页面加载完毕后,能够先判断在可视区域内的图片先加载;当用户滚动页面时,判断图片是否进入可视区;如果进入可视区,则将图片的src替换为图片的真实路径。

因此,我们分析出以下需要在js中实现的逻辑:

  • 判断可视区一次性会展示哪些图片:需要获取可视区高度和图片元素的几何属性;
  • 让可视区图片的获取链接加载:src的值 == data-src的值
  • 判断其他图片是不是在可视区域内:元素上边框与屏幕顶部的距离 < 屏幕高度
  • 监听页面的滚动

代码如下:

 <script>
        let height = window.innerHeight // 获取屏幕高度
        function lasyLoad() {
            let imgs = document.querySelectorAll('img[data-src]') // 选择带有data-src属性的img标签
            for (let i = 0; i < imgs.length; i++) {
                let rect = imgs[i].getBoundingClientRect() // 获取元素的几何属性
                // console.log(rect);
                if (rect.bottom > 0 && rect.top < height) {// 在可视区域内
                    let newImg = new Image() // 凭空创建新的img标签
                    newImg.src = imgs[i].getAttribute('data-src') // 获取标签的属性
                    newImg.onload = function () { // 图片被浏览器加载完毕
                        imgs[i].src = newImg.getAttribute('src')  
                    }
                    imgs[i].removeAttribute('data-src') // 加载完成 data-src删除 
                }
            }
        }
        lasyLoad()
        // 滚动的时候重新调用lasyLoad
        window.addEventListener('scroll', lasyLoad)
    </script>

在上面的代码中,值得注意的是:我们使用js创建一个img标签,让js提前加载图片完毕,再赋值到页面上,这样把图片加载的工作让js完成,不放到页面上能够提高效率;另外,浏览器加载过的资源会有缓存,所以已经加载过的资源就不需要再次加载,即加载完成删除 data-src 属性,就不会被我们二次加载。

2. 优点和缺点

优点

  • 减少初次加载时的请求数量和资源占用,提升页面加载速度。
  • 节省带宽,尤其是对于那些不经常访问的资源。

缺点

  • 需要额外的 JavaScript 代码来检测资源是否进入视口。
  • 当图片过大时,图片出现在可视区域的那一刻会因为资源加载不及时出现短暂的白屏,影响用户体验。

小tips

  • html的高度,由内容撑开:document.documentElement.offsetHeight()
  • 整个文档不包括边框的高度:document.documentElement.clientHeight()
  • 整个窗口的高度:window.innerHeight()
  • 获取元素的几何属性:getBoundingClientRect()相对视口 的位置,如果页面有滚动,返回的值会随着滚动变化。 d1ad6ffee9f47f3cba0d9fd16a623f1.png top: 元素的顶部距离视口的距离,right: 元素的右侧距离视口的距离,bottom: 元素的底部距离视口的距离,left: 元素的左侧距离视口的距离,width: 元素的宽度,height: 元素的高度。

预加载

1. 预加载的原理 —— 预先加载

预加载是指提前加载网页中可能会用到的资源,即在用户实际需要这些资源之前,浏览器会开始加载它们。

JavaScript本身是单线程的,但它提供了一些机制来模拟多线程的行为,例如通过 Web WorkersPromiseasync/await 等机制,可以让你在执行长时间任务时不阻塞主线程,实现类似多线程的效果,尤其适合进行资源预加载、数据加载等任务。

同样使用图片加载举例,在加载页面的同时,我使用 Web Workers 开辟了第二线程去加载图片,需要创建worker.js文件,里面存放子线程的执行逻辑;当图片加载完毕后,将图片资源交给第一个线程去展示,从而实现图片的预加载。

这里,我使用代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #pic img{
            width: 500px;
            display: block;
        }
    </style>
</head>

<body>
    <div id="pic"></div>
    <script>
        let arr = [
            "https://tse4-mm.cn.bing.net/th/id/OIP-C.4r7SLRqOVoSDXiAOPtf2fwHaFj?cb=iwc2&rs=1&pid=ImgDetMain",
            "https://ts1.tc.mm.bing.net/th/id/R-C.51d265977bf3a4332c5c74b903643679?rik=OOLvxbhfFBlOOg&riu=http%3a%2f%2fpic.sucaibar.com%2fpic%2f201506%2f09%2fe71358f052.jpg&ehk=c0nMccsE8vhPaIfGcD1PsOo1NjYNEUIeK4F1OZRVEB4%3d&risl=&pid=ImgRaw&r=0",
            "https://pic4.zhimg.com/v2-f49db917e12d50ebba098285f35a236b_r.jpg",
            "https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/f68e1ee3246c16e51d38aac0449c87b5.jpg?x-oss-process=image%2fresize%2cm_lfit%2cw_2560%2ch_1440",
            "https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/c9c023166a11901eb96777a5325c50e1.jpg",
            "https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/ffc5a93fbda140c7e7bc9c52e3abc43e.jpg?x-oss-process=image/resize,m_lfit,w_1920,h_1080",
            "https://tse4-mm.cn.bing.net/th/id/OIP-C.NtgtTssFHOm6U-ZvoQWBAwHaEK?cb=iwc2&rs=1&pid=ImgDetMain",
            "https://www.556z.com/zb_users/upload/2020/05/202005011588265209654333.jpg",
            "https://imgxz.bizhi3.com/20231124/231124104522430.jpg",
            "https://ts1.tc.mm.bing.net/th/id/R-C.c7755cd5796addab56b063eff52636cd?rik=m84xmHsh7gyfVA&riu=http%3a%2f%2fn.sinaimg.cn%2ffront20200426ac%2f720%2fw1920h1200%2f20200426%2f497c-isuiksn2962057.jpg&ehk=5J4fFrAzNslWfrANQMusJ8LSu5HLI4GtXl5BxLowdts%3d&risl=&pid=ImgRaw&r=0",
            "https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/430ec838eb83f895dfe200fc37a0ed83.jpg?x-oss-process=image%2fresize%2cm_lfit%2cw_2560%2ch_1440",
            "https://tse1-mm.cn.bing.net/th/id/OIP-C.vmPCOxrXiCFqMUbW8nac5wHaEK?cb=iwc2&rs=1&pid=ImgDetMain",
            "https://pic2.zhimg.com/v2-1f850275f54c472115ee20a188b4e4da_r.jpg?source=1940ef5c",
            "https://img-baofun.zhhainiao.com/pcwallpaper_ugc/static/a29ab4b4c6bbb79f771c848d71c72269.jpg?x-oss-process=image%2fresize%2cm_lfit%2cw_1920%2ch_1080",
            "https://ts1.tc.mm.bing.net/th/id/R-C.57c569320323e225d98f858764e8f633?rik=8%2ffkWEwdf3gR0g&riu=http%3a%2f%2fn.sinaimg.cn%2ffront20200426ac%2f720%2fw1920h1200%2f20200426%2f0771-isuiksn2961759.jpg&ehk=dfxgaV7N8UmQF%2fR48S4N0gvML4wql9%2fcOiSNTukePEU%3d&risl=&pid=ImgRaw&r=0"
        ]
        let pic = document.getElementById('pic') // 获取pic容器
        
        const worker = new Worker('worker.js') // 开辟一个新的进程
        worker.postMessage(arr)  // 将数据发送给子线程
        worker.onmessage = function(e){ // 接收子线程返回的数据
            // console.log(e.data);
            const img = new Image()  // 使用js创建img标签
            // console.log(window.URL.createObjectURL(e.data))
            img.src = window.URL.createObjectURL(e.data) // 把blob对象转成url
            pic.appendChild(img) // 展示在页面中
        }
    </script>
</body>
</html>

子线程代码如下:

// 监听消息
self.onmessage = function(e){
    // console.log(e.data);
    let arr = e.data
    
    // 行不通:子线程里面没有Image构造函数可用
    // for(let i = 0; i < arr.length; i++){
    //     let img = new Image()
    //     img.src = arr[i]
    //     img.onload = function(){
    //         //图片加载完成
    //         self.postMessage(arr[i])
    //     }
    // }

    // 将数组中的地址资源加载出来 使用ajax
    for(let i = 0; i < arr.length; i++){
        let xhr = new XMLHttpRequest();
        xhr.open('get', arr[i], true)
        xhr.responseType =  'blob' 
        xhr.send();
        xhr.onload = function(){
            if(xhr.readyState === 4 && xhr.status === 200){
                // console.log(xhr.response);
                self.postMessage(xhr.response);
            }
        }
    }

}

上面6-14行代码行不通:是因为Web Worker(子线程)没有DOM对象访问权限,因此无法直接使用Image对象。Web Workers只能在后台线程中执行JavaScript逻辑,不包含浏览器的DOM API(例如Imagedocument等)。要在Web Worker中处理图片,可以通过 fetchajax 来加载图片。

xhr.responseType = 'blob' 是添加关于图片资源的类型描述—— blob是文件类型,所有的文件可以转成blob类型。 330cf5784aa692c778fdf6c6549afd9.png

其次,blob类型可以在主线程使用 window.URL.createObjectURL() 处理成本地地址,其他人用不了你的资源,起到了反爬虫效果。如图: 175ebc6479ddfdc83122b5d40cafcd2.png

2. 优点和缺点

优点

  • 确保关键资源在页面渲染时已经准备好,避免延迟。
  • 提前加载下一步需要的资源,提升用户体验。

缺点

  • 会增加初次加载时的带宽消耗。
  • 同一时间加载多张图片,对服务器压力较大。
  • 如果预加载的资源并未被使用,可能会浪费带宽。

好啦,本次的知识点分享结束啦!喜欢记得点个赞喔,谢谢大家!下次见~ image.png