图片懒加载:提升网页性能的艺术

210 阅读7分钟

在当今互联网时代,用户体验是网站成功的关键因素之一,而网页加载速度直接影响着用户体验。随着网站内容越来越丰富,图像作为信息传递的重要组成部分,其数量和质量都在不断提升,但这也给网页加载带来了巨大挑战。图片懒加载(Lazy Loading)技术应运而生,成为前端性能优化的重要手段之一。本文将探讨图片懒加载的工作原理、实现步骤,并分析它如何帮助提升网页性能。

浏览器是怎么做的?

在了解图片懒加载之前,我们应该先知道在加载图片的时候,浏览器会做些什么事情:

  1. 请求与接收文档:

    • 浏览器先向服务器发送一个 HTTP 请求获取 HTML 文档
    • 接收到响应之后,浏览器开始处理接收到的HTML文档
  2. 解析HTML并构建DOM树:

    • 浏览器会先下载 html 标签,然后对 html 标签进行解构并建立DOM树。
  3. 解析CSS并构建CSSOM树:

    • 同时,浏览器会查找页面中的样式信息,包括内联样式、外部CSS文件以及<style>标签中的样式。它解析这些CSS规则,构建CSSOM(CSS对象模型)树。
    • 如果遇到CSS文件,浏览器会异步下载这些文件并构建CSSOM,这个过程与HTML解析可以并行进行。
  4. 构建渲染树:

    • 将DOM树和CSSOM树合并,构建渲染树。渲染树只包含渲染页面所需的可见元素及其样式信息,不可见元素(如display:none)不会被加入此树中。
  5. 布局:

    • 根据渲染树中的信息,计算每个节点的位置和尺寸。这个过程也叫回流(Reflow)
  6. 绘制:

    • 浏览器根据布局阶段计算出的信息,将渲染树的各个节点绘制到屏幕上。这一步可能涉及调用GPU进行加速渲染。
  7. 合成与光栅化:

    • 在某些浏览器中,页面会被分割成多个图层,这些图层可以独立于主文档进行更新。合成阶段将这些图层组合成最终的屏幕显示。光栅化是将图层转换为像素的过程,可以由专门的光栅化线程完成。
  8. JavaScript执行与交互:

    • 在页面加载和渲染的过程中,JavaScript代码可以修改DOM结构、CSS样式或触发重新布局和绘制,这可能会中断当前的渲染流程,引起重新计算布局和绘制。
    • 现代浏览器尝试通过各种机制(如requestAnimationFrame、async/defer属性)来管理JavaScript执行,以减少对渲染性能的影响。
  9. 后续交互与渲染循环:

    • 用户与页面交互(如滚动、点击)后,可能需要更新渲染树、重新布局和绘制部分或全部页面,这个过程会重复上述的一些步骤。

我们可以看到,浏览器渲染一个页面出来,要经过如此多的步骤,在这个过程中,图片是一个不可忽视的负载项。浏览器通常会为每个<img>标签启动一个新的下载线程,这意味着如果一个页面包含大量图片,将会有大量的并发下载请求。这不仅消耗网络资源,还可能导致页面加载缓慢,尤其是对于移动设备或网络条件不佳的用户来说,体验更是大打折扣。

懒加载

了解完浏览器的渲染过程,我们可以看出大量的图片加载,会让我们的上网体验大打折扣,所以我们就需要通过性能优化来提升我们的上网体验。性能优化的目标是尽快展示首屏内容,让用户感受到即时反馈。对于非首屏的图片,采取懒加载策略可以显著提升这一过程。具体来说,懒加载的核心思想是:仅在图片即将进入可视区域时才开始加载,而非一开始就全部下载。这样既能保证用户看到的内容快速呈现,又能有效减少不必要的网络请求,节省带宽资源。

实现懒加载的步骤

  1. 使用data-src替换src属性:默认情况下,不设置src属性(因为设置后浏览器会立即发起请求),而是将图片的真实URL存储在data-src这样的自定义属性中。
  2. 监听滚动事件:通过JavaScript监听窗口的滚动事件,计算图片位置与当前视口的关系。
  3. 判断是否进入可视区域:利用clientHeight(视口高度)、scrollTop(页面已滚动的高度)和offsetTop(元素距离页面顶部的距离)来判断图片是否接近或已经处于可视区域。
  4. 动态加载图片:一旦图片满足加载条件,立即将data-src中的URL赋值给src,触发图片下载并显示。
  5. 优化触发机制:为了减少不必要的计算和事件处理,可以通过防抖(debounce)或节流(throttle)技术来限制滚动事件的触发频率,避免因滚动过快导致的频繁计算和加载请求。

实战

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./common.css">
    <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
</head>
<body>
    <img  data-price="20" data-src="https://img.36krcdn.com/20190808/v2_1565254363234_img_jpg">
    <img  data-src="https://img.36krcdn.com/20190905/v2_1567641293753_img_png">
    <img  data-src="https://img.36krcdn.com/20190905/v2_1567640518658_img_png">
    <img  data-src="https://img.36krcdn.com/20190905/v2_1567642423719_img_000">
    <img  data-src="https://img.36krcdn.com/20190905/v2_1567642425030_img_000">
    <img  data-src="https://img.36krcdn.com/20190905/v2_1567642425101_img_000">
    <img  data-src="https://img.36krcdn.com/20190905/v2_1567642425061_img_000">
    <img  data-src="https://img.36krcdn.com/20190904/v2_1567591358070_img_jpg">
    <img  data-src="https://img.36krcdn.com/20190905/v2_1567641974410_img_000">
    <img  data-src="https://img.36krcdn.com/20190905/v2_1567641974454_img_000">
    
<script>
    // 获取页面上所有的img元素
    const imgs = document.getElementsByTagName('img');
    // 记录img元素的数量
    const num = imgs.length;
    // 初始化计数器,追踪已加载的图片数量
    let n = 0;
    // 当文档内容加载完成时,开始加载图片
    document.addEventListener('DOMContentLoaded', () => {
        loadImage();
    });
    // 图片加载函数
    function loadImage() {
        // 获取一屏高度
        let screenHeight = document.documentElement.clientHeight;
        // 获取当前滚动条位置,考虑浏览器兼容性
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        // 遍历所有img元素
        for (let i = 0; i < num; i++) {
            // 判断图片是否在当前可视区域内
            if (imgs[i].offsetTop < screenHeight + scrollTop) {
                // 使用数据属性(data-src)来存储原始图片URL,这里打印出来查看
                console.log(imgs[i].dataset.src, imgs[i].dataset.price);
                // 将data-src属性值赋给src属性,实现图片的真正加载
                imgs[i].src = imgs[i].getAttribute('data-src');
                // 更新已加载图片数量
                n = i + 1;
                // 如果所有图片都已加载,则移除滚动事件监听器
                if (n === num) {
                    // 注释掉的代码用于在控制台提示所有图片加载完成
                    // console.log('所有图片加载完成');
                    window.removeEventListener('scroll', throttleLayLoad);
                }
            }
        }
    }

    // 使用lodash库的throttle函数来限制loadImage函数的执行频率,防止滚动过快时频繁执行
    const throttleLayLoad = _.throttle(loadImage, 300);
    // 添加滚动事件监听器,当用户滚动页面时,触发throttleLayLoad函数
    window.addEventListener('scroll', throttleLayLoad);
</script>
</body>

项目展示

QQ202478-183352-HD-ezgif.com-video-to-gif-converter.gif

我们可以看到,控制台中的src是逐步加载出来的,这大大提升了我们的上网体验

收获与思考

实施图片懒加载,不仅要求前端开发者理解浏览器的工作原理和网络传输机制,还需具备良好的性能优化意识。通过实践懒加载,我们深刻认识到:

  • 性能优化是持续的过程:随着技术进步和用户需求变化,不断探索和应用新的优化策略是必要的。
  • 理解底层原理的重要性:只有深入理解TCP/IP协议、浏览器渲染机制等底层知识,才能更高效地进行性能调优。
  • 细节决定成败:如通过dataset访问自定义属性、合理使用事件监听优化等,这些细节处理能够显著提升用户体验。
  • 用户体验优先:在追求技术实现的同时,始终将提升用户感知的加载速度放在首位。

ok,今天的图片懒加载的分享就到这了,小编还在学习中,欢迎大佬们提出建议!!!非常感谢!!!