🔥如何用懒加载技术让你的网站飞起来?——从入门到精通🔥

139 阅读10分钟

前言

在当今这个信息爆炸的时代,用户对于网页加载速度的要求越来越高。一个响应迅速、加载流畅的网站不仅能提供更好的用户体验,还能显著提升用户的留存率和转化率。然而,随着网页内容的日益丰富和复杂,页面加载时间也变得越来越长,这不仅影响了用户体验,还可能对搜索引擎排名产生负面影响。

为了解决这一问题,懒加载(Lazy Loading) 技术应运而生。懒加载是一种优化网页性能的技术,它能够延迟加载非关键资源,直到用户真正需要它们时才进行加载。这种按需加载的方式不仅减少了初始页面加载的时间,还降低了服务器的负载和带宽使用,从而提升了整体的用户体验。

本文将通过懒加载图片的例子详细介绍懒加载技术实施方法,并通过实际案例展示其带来的显著效果。无论你是前端开发者、网站管理员还是对网页性能优化感兴趣的读者,相信这一篇文章对你会有些益处的,好了,接下来,让我们的网页飞起来!

微信图片_202505120746471.jpg

懒加载用在哪?实现原理是什么?

懒加载的主要应用场景

  1. 图片加载:在网页或移动应用中,当页面含有大量图片时(比如火爆的电商平台),一次性加载所有图片会消耗大量的网络带宽和内存。通过懒加载技术,只有当用户滚动到接近显示图片的位置时,该图片才会被加载,从而提高了用户体验并减少了资源消耗。
  2. 视频播放:在线视频网站通常采用懒加载来优化视频流传输(比如抖音)。开始时只缓冲几秒钟的内容,随着观看进度逐渐下载后续内容,这可以有效降低服务器压力同时保证流畅播放体验。
  3. 数据请求:在构建大型列表或者表格展示的应用时,如果直接从服务器获取全部数据可能会导致长时间等待。通过实现懒加载机制,在用户浏览至列表底部时再向后端请求更多数据,可以显著改善性能表现。
  4. 组件初始化:在某些复杂的UI框架中,可能包含很多功能丰富的组件。使用懒加载可以在首次启动时不立即实例化这些组件,而是等到用户具体访问相关部分时才加载,这样可以加快启动速度。

实现原理

  • 检查视口位置:对于基于滚动触发的懒加载来说,最重要的是确定何时元素进入可见区域。这通常通过监听窗口的滚动事件并通过计算元素相对于视口的位置来实现。
  • 异步加载一旦检测到需要加载的目标,就发起一个异步请求去获取资源或执行特定任务。 现代浏览器支持多种方式来进行异步加载,如XMLHttpRequestfetch API等。
  • 状态管理:为了确保每个项目只加载一次,通常会在加载完成后设置标志位或其他形式的状态信息。

优点与缺点

  • 优点

    • 减少初始加载时间和内存占用。
    • 提升用户体验,特别是在低速网络环境下。
    • 优化服务器负载,避免不必要的资源浪费。
  • 缺点

    • 额外增加了代码复杂度。
    • 如果实现不当可能导致一些视觉上的问题,比如闪现空白区域。(但是这个和性能优化比起来就无伤大雅了)
    • 对于搜索引擎爬虫而言,可能不利于SEO(但可以通过预渲染等技术解决)。

总之,懒加载是一种非常实用的技术手段,它可以帮助开发者构建更加高效且响应迅速的应用程序。不过,在实施过程中也需要考虑到具体的业务需求和技术限制,合理选择最合适的实现方案。

懒加载的实际应用

步骤一:制作demo,src->自定义属性

OK,接下来我们来制作一个demo吧!我们将制作一个页面,里面包含非常多的图片,之后实现懒加载

首先我们把页面的基本轮廓写出来:

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片懒加载</title>
    <style>
        * {
            margin: 0;
            padding: 0;  /* 初始化*/
        }

        .image-item {
            width: 500px;
            height: 500px;   /* 给图片设置宽高,令其能占满视窗,扩展视窗高度*/
            margin-top: 30px;
        }

        body {
            background-color: black;
        }
    </style>
</head>

<body>
   
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />
    <img class="image-item" src='/' />



    <script>
       
    </script>
</body>

</html>

既然我们要实现懒加载,那就是说图片不能一开始就加载出来,应当有个缓冲,等到我们页面的视窗滑到对应的地方,再加载图片。

因此,我们要寻找一个占位图,暂时顶替原来的图片。(占位图一般都比较小,仅用于代替图片,让用户知道这个图片在加载中。)

那么如何做到一开始展示占位图,后来就把真实的图片替换上去呢?

那么就是修改图片的src了:在图片不在视窗的时候,src为占位图的地址;在视窗的时候,src是真实图片的地址,接下来我们就需要用到自定义属性来储存真实图片的src了。

<body>
    <!-- 尽量不使用本地绝对路径 -->
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250313/v2_15ad8ef9eca34830b4a2e081bbc7f57a@000000_oswg172644oswg1536oswg722_img_000?x-oss-process=image/resize,m_mfit,w_960,h_400,limit_0/crop,w_960,h_400,g_center" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_aeaa7a1d51e74c3a8f909c96cd73a687@000000_oswg169950oswg1440oswg600_img_jpeg?x-oss-process=image/format,webp" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_1c88dc26ff9341cf8738d670896ce3a8@5284654_oswg847922oswg1440oswg600_img_png?x-oss-process=image/resize,m_mfit,w_960,h_400,limit_0/crop,w_960,h_400,g_center/format,webp" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_e1d92f43af2c4f47b8852ea8786e606f@6100851_oswg635095oswg1053oswg495_img_png?x-oss-process=image/resize,m_mfit,w_960,h_400,limit_0/crop,w_960,h_400,g_center/format,webp" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250307/v2_9295b22d4a1b4b55ac4c3379b2da80cc@6100851_oswg781048oswg1053oswg495_img_png?x-oss-process=image/resize,m_mfit,w_600,h_400,limit_0/crop,w_600,h_400,g_center/format,webp" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250306/v2_6ea048ac01c3408a9ed6ebe79a8fc8a2@5888275_oswg849213oswg1053oswg495_img_png?x-oss-process=image/resize,m_mfit,w_600,h_400,limit_0/crop,w_600,h_400,g_center/format,webp" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_e4c73b024bcc409fba427adb2d7fb2fa@000000_oswg1251602oswg1080oswg559_img_000?x-oss-process=image/format,jpg/interlace,1" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_9f21750bb37243128b6b1790f9072649@000000_oswg1219724oswg1080oswg601_img_000?x-oss-process=image/format,jpg/interlace,1" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_2acf9b228cd940c1b5fdb5691c0b6e4c@000000_oswg1402679oswg1080oswg527_img_000?x-oss-process=image/format,jpg/interlace,1" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250312/v2_af4dd443d261445d8e903c473cac074c@000000_oswg1118331oswg1080oswg497_img_000?x-oss-process=image/format,jpg/interlace,1" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250313/v2_e7de1cc8e8014122ba303ea036eea532@1743780481_oswg58583oswg1080oswg257_img_000?x-oss-process=image/format,jpg/interlace,1" />
    <img class="image-item" lazyload="true" src='https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif'
        data-original="https://img.36krcdn.com/hsossms/20250313/v2_0f70e0a75a8d4736a050e846cd6ab3e5@1743780481_oswg183216oswg1080oswg629_img_000?x-oss-process=image/format,jpg/interlace,1" />

在这里我们使用自定义属性data-original存储要展示图片的src,后续利用JS将其与现有的占位图的src替换。

我们再自定义一个lazyload属性,用来标记哪些图片需要懒加载,后续利用JS选择这些图片。

自定义属性

自定义属性(也称为数据属性)是HTML5中引入的一个非常有用的功能,允许开发者在HTML标签上添加自定义的数据。这些自定义属性以data-为前缀,可以在JavaScript中轻松访问和操作,从而实现更灵活和动态的页面行为。

同时我们可以利用JS对这些数据进行操作:

<div id="user" data-id="123" data-name="Alice" data-role="admin">
  用户信息
</div>

<script>
const example = document.getElementById('user');
example.dataset.action = 'submit'; // 添加属性 : data-submit
example.dataset.name = 'Skye'; // 修改属性
delete example.dataset.action;
example.dataset.id = null; // 删除属性,也可设为undefined
</script>

注:lazyload虽然也属于自定义属性,但并不能通过dataset访问到。

步骤二:JS实现懒加载

要操作的元素和相应的属性都有了,接下来该利用JS来实现懒加载了,

那么第一步就是要选中这些元素,和我们的视窗高度(因为是否进入视窗是判断是否要加载图片的依据):

const viewHeight = document.documentElement.clientHeight;
            // 返回浏览器视窗的高度
const elses = document.querySelectorAll('img[data-original][lazyload]')
            // 选中所有携带 data-origin & lazyload 属性的 <img> 标签

下面是实现懒加载的逻辑,其实很简单,就是判断元素是否在视窗之内,如果元素在视窗之内,那么就进行加载(也就是src的替换):

const viewHeight = document.documentElement.clientHeight;
const elses = document.querySelectorAll('img[data-original][lazyload]')
const lazyload = function(){
    rect = item.getBoundingClientRect(); // 创建一个DOMRect对象,用于检测元素是否在视窗内
    if (rect.bottom >= 0 && rect.top < viewHeight) { // 如果在视窗内
                    (function () {
                        var img = new Image(); 
                        img.src = item.dataset.original; 
                        img.onload = function () {
                            item.src = item.dataset.original;
                            item.removeAttribute('data-original');
                            item.removeAttribute('lazyload');
                        }
                    })()
                }
}

getBoundingClientRect()

getBoundingClientRect() 是 JavaScript 中的一个方法,用于获取元素的大小及其相对于视口的位置。这个方法返回一个 DOMRect 对象,该对象包含了元素的 top、right、bottom、left、width 和 height 等属性。这些属性提供了关于元素在视口中的位置和尺寸的信息。

返回的 DOMRect 对象的属性

  • top: 元素上边框到视口顶部的距离。
  • right: 元素右边框到视口左边的距离。
  • bottom: 元素下边框到视口顶部的距离。
  • left: 元素左边框到视口左边的距离。
  • width: 元素的宽度(包括内边距和边框,但不包括外边距)。
  • height: 元素的高度(包括内边距和边框,但不包括外边距)。
  • x: 元素左边框到视口左边的距离(与 left 相同)。
  • y: 元素上边框到视口顶部的距离(与 top 相同)。

使用场景

  • 懒加载:检测图片或其他资源是否进入视口,从而决定是否加载它们。
  • 动画:获取元素的位置,进行精确的 CSS 动画或 JavaScript 动画。
  • 布局计算:动态调整元素的位置或大小。
  • 滚动效果:实现滚动时的效果,如固定导航栏、滚动触发事件等。

当然,我们希望每一个元素都要进行这样的操作,所以我们要用到Array.prototype.forEach.call:

const viewHeight = document.documentElement.clientHeight;
        const elses = document.querySelectorAll('img[data-original][lazyload]');
        const lazyload = function () {
            Array.prototype.forEach.call(elses, function (item, index) { // 利用forEach遍历每个元素
                if (!item.dataset.original) return;
                rect = item.getBoundingClientRect();
                if (rect.bottom >= 0 && rect.top < viewHeight) {
                    (function () {
                        var img = new Image(); 
                        img.src = item.dataset.original;
                        img.onload = function () {
                            item.src = item.dataset.original;
                            item.removeAttribute('data-original');
                            item.removeAttribute('lazyload');
                        }
                    })()
                }

            })
        }
        // 触发lazyload
        window.addEventListener('scroll', lazyload);
        document.addEventListener('DOMContentLoaded', lazyload);

Array.prototype.forEach.call

Array.prototype.forEach.call 是一种在 JavaScript 中将数组方法应用于类数组对象的技术。类数组对象是指具有 length 属性和索引元素的对象,但不是真正的数组(例如 NodeList、arguments 对象等)。

为什么需要 Array.prototype.forEach.call

类数组对象:一些 DOM 方法返回的是类数组对象(如 NodeList、HTMLCollection),这些对象虽然有 length 属性和索引,但没有数组的方法(如 forEach、map 等)。

扩展功能:通过 Array.prototype.forEach.call,可以将数组的方法应用到这些类数组对象上,从而方便地进行遍历或其他操作。 语法

Array.prototype.forEach.call(arrayLike, callback[, thisArg])
  • arrayLike:类数组对象,如 NodeList 或 arguments 对象。
  • callback:为每个元素调用的函数,该函数接收三个参数:
    • currentValue:当前元素的值。
    • index:当前元素的索引。
    • array:被遍历的类数组对象。
  • thisArg(可选):执行 callback 时使用的 this 值。

步骤三:稍微一修改

这样就完成了,拿着这一段JS与上面的HTML结合就实现了懒加载了,但是呢,你或许会发现这种情况:

image.png

此时它们的srcundefined

image.png

这可能是因为:

  • 当图片快速进入视口时,lazyload 函数可能被多次触发
  • 每次触发都会创建一个新的 Image 对象
  • 当第一个 Image 对象加载完成后,移除了 data-original 属性
  • 但其他已经创建的 Image 对象仍然会尝试设置 src,而此时 item.dataset.original 可能已被移除

应对这种情况我们可以加一个属性item.dataset.loading来标识是否正在加载:

const lazyload = function () {
    Array.prototype.forEach.call(elses, function (item) {
        // 添加检查是否已在加载
        if (!item.dataset.original || item.dataset.loading) return;
        
        const rect = item.getBoundingClientRect();
        if (rect.bottom >= 0 && rect.top < viewHeight) {
            // 设置加载状态
            item.dataset.loading = 'true';
            
            (function () {
                var img = new Image(); 
                img.src = item.dataset.original;
                img.onload = function () {
                    // 确保元素仍然存在
                    if (!item.dataset.original) return;
                    
                    item.src = item.dataset.original;
                    item.removeAttribute('data-original');
                    item.removeAttribute('lazyload');
                    // 移除加载状态
                    delete item.dataset.loading;
                }
               
            })()
        }
    })
}

或者我们使用节流函数减少执行次数:

function throttle(fn, delay) {
            let lastTime = 0;
            return function () {
                const now = new Date().getTime();
                if (now - lastTime > delay) {
                    fn.apply(this, arguments);
                    lastTime = now;
                }
            };
        }

        const throttledLazyload = throttle(lazyload, 200); // 每200毫秒最多触发一次

        window.addEventListener('scroll', throttledLazyload);
        document.addEventListener('DOMContentLoaded', lazyload);

总结

这一期就是这样了,我们首先介绍了懒加载的重要性,它是性能优化不可或缺的一环,尤其在当今电商平台火热和短视频平台火热的时候,它们发挥的作用远超我们的想象。

我们接下来手写了一个懒加载,下篇将会带大家认识loading='lazy'以及IntersectionObserver这两个更现代化的实现懒加载的方法。

如果有错误,欢迎大家指出呀!制作不易,请大家给个赞吧~

111.jpg