LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

让图片学会“等你看到再出场”——懒加载全攻略

zhenglin
2026年2月3日 9:16 本文热度 140

图片懒加载全解析:从传统 Scroll 到现代 IntersectionObserver

在前端开发的世界里,性能优化永远是绕不开的核心话题✨。尤其是在电商、资讯、社交这类图片密集型的页面中,大量图片的加载往往会成为页面性能的 “绊脚石”—— 首屏加载慢吞吞,用户没耐心直接离开;非可视区域的图片白白消耗带宽,服务器压力也徒增。

而图片懒加载(Lazy Load)作为前端性能优化的 “明星方案”,正是为解决这些痛点而生。


今天我们就从概念、原理到实战,全方位拆解图片懒加载的实现逻辑,对比传统与现代方案的优劣,让你彻底吃透这个高频考点!


一、什么是图片懒加载?🤔

图片懒加载,本质是一种 “按需加载” 的资源加载策略:浏览器解析页面时,不会一次性加载所有<img>标签对应的图片,而是先加载首屏可视区域内的图片;当用户滚动页面,使原本隐藏在视口外的图片进入可视区域(Viewport)时,再触发这些图片的真实加载。


核心实现逻辑的关键是 “资源延迟绑定”:将图片的真实地址暂存到data-src(自定义属性)中,而非直接赋值给src属性(src先指向体积极小的占位图,如 1x1 透明图),只有满足 “进入视口” 条件时,才把data-src的值替换到src中,触发真实的图片 HTTP 请求。


二、为什么需要图片懒加载?💡

没有懒加载的页面,浏览器解析<img>标签时,只要看到src属性就会立刻发起请求,这会带来两个致命问题:


  1. 首屏加载速度慢:首页的所有图片请求会和 HTML、CSS、JS 的加载 “抢占” 网络资源,导致首屏 HTML 渲染、样式加载被阻塞,用户面对空白页面的等待时间变长(数据显示,首屏加载超过 3 秒,用户流失率超 50%)。

  2. 无效请求浪费:视口之外的图片(如下滚才能看到的列表项),加载后用户可能永远不会滚动到对应位置,既浪费了用户的移动带宽(尤其是移动端),也增加了服务器的并发压力。


而懒加载的引入,恰好解决了这些问题:

✅ 提升用户体验:首屏内容快速渲染,用户无需长时间等待;

✅ 节省带宽资源:仅加载用户能看到的图片,减少无效请求;

✅ 降低服务器压力:分散图片请求的时间和并发量,避免瞬间高并发。


三、图片懒加载的解决方案核心🔑

所有懒加载方案都围绕两个核心原则展开,缺一不可:

1. 首屏优先

暂时不需要加载的图片,src属性先指向小体积占位图(如 1x1 透明图、加载中占位图),让浏览器优先加载 HTML、CSS、JS 等核心资源,保证首屏内容快速呈现。


2. 按需加载

通过监听页面滚动(或原生 API 监听交集状态),实时判断图片是否进入视口;只有当图片进入视口时,才将data-src中的真实地址赋值给src,触发真实图片的加载。



四、如何实现图片懒加载?🛠️

接下来我们从代码层面,拆解传统方案和现代方案的实现逻辑,对比两者的优劣。

1. 传统方案:监听滚动事件(onscroll + 节流)

这是早期懒加载的主流实现方式,核心是 “监听滚动 + 节流控制 + 手动计算位置”。


1.1 核心思路

① 图片预处理:给非首屏图片添加lazy类,src赋值占位图,真实地址存在data-src自定义属性中;

② 节流控制:给scroll事件绑定节流函数,避免高频触发导致性能卡顿;

③ 视口判断:滚动时遍历所有lazy图片,通过getBoundingClientRect()计算图片与视口的位置关系,判断是否进入视口;

④ 加载图片:若图片进入视口,将data-src赋值给src,移除lazy类、添加loaded类(用于样式过渡),并移除data-src属性;

⑤ 初始化检查:页面加载完成后,先执行一次懒加载判断,避免首屏内的lazy图片未加载。


1.2 代码

javascript


// 节流函数:控制函数高频触发,避免滚动时性能卡顿

function throttle(func, wait) {

    let timeout = null; // 定时器标识,用于控制执行时机

    return function () {

        if (!timeout) { // 若定时器不存在,说明可以执行函数

            timeout = setTimeout(() => {

                func.apply(this, arguments); // 执行目标函数,保留this和参数

                timeout = null; // 执行完成后重置定时器

            }, wait);

        }

    };

}


function lazyLoad() {

    const lazyImages = document.querySelectorAll('img.lazy'); // 获取所有待加载的图片

    const windowHeight = window.innerHeight; // 获取视口高度


    lazyImages.forEach(img => {

        // 跳过已加载的图片(已移除lazy类)

        if (!img.classList.contains('lazy')) return;


        const rect = img.getBoundingClientRect(); // 获取图片的位置信息(相对于视口)

        // 核心判断:图片顶部进入视口下方,且底部未完全离开视口上方 → 图片进入视口

        if (rect.top < windowHeight && rect.bottom > 0) {

            if (img.dataset.src) {

                console.log('Loading image via Scroll:', img.dataset.src);

                img.src = img.dataset.src; // 替换src,触发真实图片加载

                img.removeAttribute('data-src'); // 移除自定义属性,避免重复加载

                img.classList.remove('lazy'); // 移除lazy类,标记为已加载

                img.classList.add('loaded'); // 添加loaded类,实现透明度过渡

            }

        }

    });

}


// 节流处理懒加载函数,200ms执行一次

const throttledLazyLoad = throttle(lazyLoad, 200);


// 监听滚动事件,触发节流后的懒加载

document.addEventListener('scroll', throttledLazyLoad);

// 窗口大小变化时,重新判断图片位置

window.addEventListener('resize', throttledLazyLoad);

// 页面加载完成后,初始化检查首屏图片

document.addEventListener('DOMContentLoaded', lazyLoad);

1.3 代码实现效果

观察界面滚动图片变化与控制台打印:


1.4 该方案的缺点

❌ 性能损耗:即使加了节流,scroll事件仍会高频触发,存在一定的性能开销;

❌ 代码冗余:需要手动计算元素与视口的位置关系,逻辑易出错,维护成本高;

❌ 适配性差:在移动端、嵌套滚动等复杂布局中,位置计算容易失效,适配成本高。


1.5 关键 API 解析
  • throttle (func, wait):自定义节流函数,控制高频事件触发频率,避免性能卡顿。

    • func:需要被节流的目标函数(此处为 lazyLoad);

    • wait:节流等待时间(毫秒),此处为 200ms,即函数每 200ms 最多执行一次;

  • document.querySelectorAll ('img.lazy'):根据 CSS 选择器获取所有带 lazy 类的待加载图片,返回 NodeList 集合。

  • window.innerHeight:获取当前浏览器视口的高度,用于判断图片是否进入视口。

  • Element.classList.contains ('lazy'):布尔值,判断图片元素是否包含 lazy 类,跳过已加载的图片。

  • Element.getBoundingClientRect ():获取元素相对于视口的位置信息(返回 DOMRect 对象),包含 top(元素顶部距视口顶部距离)、bottom(元素底部距视口顶部距离)等属性。

  • img.removeAttribute ('data-src'):移除图片的 data-src 属性,避免重复读取。

  • Element.classList.remove ('lazy'):移除图片的 lazy 类,标记为已加载。

  • Element.classList.add ('loaded'):为图片添加 loaded 类,实现加载后的样式过渡。

  • document.addEventListener ('scroll', throttledLazyLoad):监听页面滚动事件,触发节流后的懒加载函数。

  • window.addEventListener ('resize', throttledLazyLoad):监听窗口大小变化事件,重新判断图片位置,适配视口尺寸变化。

  • document.addEventListener ('DOMContentLoaded', lazyLoad):监听 DOM 加载完成事件,初始化执行懒加载函数,检查首屏图片是否需要加载。


2. 现代方案:IntersectionObserver(推荐)🌟

    为了解决传统方案的痛点,浏览器原生提供了IntersectionObserver API(交集观察器),专门用于监听 “元素是否进入视口 / 与其他元素产生交集”,是目前懒加载的最优解。

2.1 核心思路

① 浏览器原生支持:无需手动监听scrollresize等事件,由浏览器底层优化执行逻辑;

② 交集监听:创建IntersectionObserver实例,指定观察的目标元素(lazy图片);

③ 自动判断:当目标元素与视口产生交集(满足阈值条件)时,触发回调函数;

④ 加载图片:在回调中替换data-srcsrc,移除lazy类,停止观察该元素(避免重复触发);

⑤ 降级处理:若浏览器不支持该 API,直接加载所有图片,保证功能可用


2.2 代码

javascript

document.addEventListener("DOMContentLoaded", function() {

    const lazyImages = document.querySelectorAll("img.lazy"); // 获取所有待加载图片


    // 检查浏览器是否支持IntersectionObserver

    if ("IntersectionObserver" in window) {

        // 创建交集观察器实例

        const imageObserver = new IntersectionObserver(function(entries, observer) {

            // 遍历所有被观察的元素的交集状态

            entries.forEach(function(entry) {

                // entry.isIntersecting:元素是否进入视口(产生交集)

                if (entry.isIntersecting) {

                    const img = entry.target; // 获取当前触发的图片元素

                    console.log('Loading image via IntersectionObserver:', img.dataset.src);

                    img.src = img.dataset.src; // 替换src,加载真实图片

                    img.classList.remove("lazy"); // 标记为已加载

                    img.classList.add("loaded"); // 添加样式过渡类

                    observer.unobserve(img); // 停止观察该图片,避免重复触发

                }

            });

        }, {

            root: null, // 观察的根元素:null表示视口

            rootMargin: "0px", // 根元素的边距,扩展/缩小观察区域

            threshold: 0.1 // 阈值:图片10%可见时触发回调

        });


        // 遍历所有lazy图片,开始观察

        lazyImages.forEach(function(image) {

            imageObserver.observe(image);

        });

    } else {

        // 降级处理:不支持时直接加载所有图片

        console.log("IntersectionObserver not supported, loading all images.");

        lazyImages.forEach(function(img) {

            img.src = img.dataset.src;

            img.classList.remove("lazy");

            img.classList.add("loaded");

        });

    }

});

2.3 代码实现效果

观察界面滚动图片变化与控制台打印:

 

2.4 该方案的优势

✅ 无性能损耗:浏览器底层实现,无需手动节流 / 防抖,性能远超scroll监听;

✅ 代码简洁:无需手动计算元素位置,逻辑清晰,维护成本低;

✅ 适配性强:完美兼容移动端、嵌套滚动等复杂布局;

✅ 可扩展:支持自定义观察规则(如rootMargin扩展观察区域、threshold调整触发阈值)。


2.5 关键 API 解析

IntersectionObserver(callback, options):构造函数,创建交集观察器实例。

callback:交集状态变化时的回调函数,接收两个参数:

  • entries:数组,每个元素是IntersectionObserverEntry对象,包含元素的交集状态、位置等信息;

  • observer:当前的IntersectionObserver实例。

options:配置项(可选):

  • root:观察的根元素,默认null(视口);
  • rootMargin:根元素的边距,格式同 CSS margin(如 "100px 0"),可扩展 / 缩小观察区域;

  • threshold:触发回调的阈值(0~1),0 表示元素刚进入视口就触发,1 表示元素完全进入视口才触发


  • entry.isIntersecting:布尔值,判断元素是否与根元素产生交集(进入视口)。

  • observer.observe(target):开始观察指定的目标元素。

  • observer.unobserve(target):停止观察指定的目标元素。


五、CSS 与 HTML 代码

CSS:

<style>

        /* 页面基础样式 */

        body {

            font-family: Arial, sans-serif;

            margin: 0;

            padding: 0;

            text-align: center;

        }


        /* 

         * 空白间隔区样式

         * 用于撑开页面高度,模拟长页面滚动效果

         */

        .spacer {

            height: 150vh;

            /* 核心:高度设置为 1.5 倍视口高度 (150vh) */

            display: flex;

            flex-direction: column;

            align-items: center;

            justify-content: flex-start;

            /* 内容从顶部开始排列 */

            padding-top: 50vh;

            /* 核心:提示词距离顶部 1/3 (50vh / 150vh ≈ 0.33) */

            box-sizing: border-box;

            background-color: #f9f9f9;

            border-bottom: 1px solid #ddd;

        }


        /* 图片容器样式 */

        .image-wrapper {

            padding: 50px 0;

            background-color: #fff;

            min-height: 400px;

            /* 最小高度,防止图片加载前高度塌陷 */

            display: flex;

            align-items: center;

            justify-content: center;

        }


        /* 

         * 懒加载图片样式

         * .lazy 类表示图片尚未加载

         */

        img.lazy {

            max-width: 80%;

            height: auto;

            display: block;

            margin: 0 auto;

            opacity: 0.3;

            /* 初始低透明度,显示占位效果 */

            transition: opacity 0.5s;

            /* 添加过渡效果,使加载更平滑 */

        }


        /* 

         * 图片加载完成后的样式

         * .loaded 类在 JS 中加载完成后添加

         */

        img.loaded {

            opacity: 1;

            /* 恢复完全不透明 */

        }


        h1,

        h2 {

            color: #333;

        }

    </style>

HTML

代码高亮:

<body>

    <!-- 

      第一部分:首屏空白区

      作用:展示标题和提示,迫使用户向下滚动

    -->

    <div class="spacer">

        <h1>传统懒加载方案</h1>

        <h2>⬇️ 向下滑动加载第一张图片 ⬇️</h2>

    </div>


    <!-- 

      第二部分:第一张图片

      data-src 存储真实图片地址,src 存储占位图

    -->

    <div class="image-wrapper">

        <img class="lazy"

            src="https://img10.360buyimg.com/wq/jfs/t24601/190/890984006/4559/731564fc/5b7f9b7bN3ccd29ab.png"

            data-src="https://img.36krcdn.com/hsossms/20260119/v2_53cad3f2226f48e2afc1942de3ab74e4@5888275@ai_oswg1141728oswg1053oswg495_img_png~tplv-1marlgjv7f-ai-v3:960:400:960:400:q70.jpg?x-oss-process=image/format,webp"

            alt="Image 1">

    </div>


    <!-- 

      第三部分:中间间隔区

      作用:分隔两张图片,确保加载第二张图片需要继续大幅滚动

    -->

    <div class="spacer">

        <h2>⬇️ 向下滑动出现第二张图片 ⬇️</h2>

    </div>


    <!-- 第四部分:第二张图片 -->

    <div class="image-wrapper">

        <img class="lazy"

            src="https://img10.360buyimg.com/wq/jfs/t24601/190/890984006/4559/731564fc/5b7f9b7bN3ccd29ab.png"

            data-src="https://img.36krcdn.com/hsossms/20260117/v2_1e74add07bb94971845c777e0ce87a49@000000@ai_oswg421938oswg1536oswg722_img_000~tplv-1marlgjv7f-ai-v3:960:400:960:400:q70.jpg?x-oss-process=image/format,webp"

            alt="Image 2">

    </div>


    <!-- 底部留白,确保能滚到底部,方便观察最后一张图的加载 -->

    <div style="height: 50vh; background-color: #f9f9f9;"></div>

</body>

疑问:

  1. 图片懒加载的核心原理是什么?

答:核心是 “按需加载”,将非首屏图片的真实地址存到data-src(自定义属性),src指向占位图;通过监听滚动(传统)或IntersectionObserver(现代)判断图片是否进入视口,进入后将data-src赋值给src,触发真实图片加载。


  1. 传统懒加载方案中,为什么要使用节流函数?

答:scroll事件会在滚动过程中高频触发(每秒数十次),若直接执行懒加载逻辑,会导致大量 DOM 操作和计算,引发页面卡顿;节流函数能控制函数在指定时间内只执行一次,减少性能损耗。


  1. IntersectionObserver 相比传统 scroll 方案有哪些优势?

答:① 性能更好:浏览器底层优化,无需手动节流;② 代码更简洁:无需手动计算元素位置;③ 适配性强:兼容复杂布局;④ 可扩展:支持自定义观察规则。


  1. 如何判断一个元素是否进入视口?

答:传统方案用element.getBoundingClientRect()获取元素的位置信息,判断rect.top < window.innerHeight && rect.bottom > 0;现代方案直接通过IntersectionObserverisIntersecting属性判断。


  1. 懒加载的降级方案是什么?

答:若浏览器不支持IntersectionObserver(如部分老旧浏览器),直接遍历所有lazy图片,将data-src赋值给src,保证图片能正常加载。


七、结语🎯

图片懒加载作为前端性能优化的 “基础操作”,其核心始终是 “按需加载”—— 优先保证首屏体验,减少无效资源消耗。传统的scroll+节流方案兼容旧浏览器,但存在性能和适配痛点;而IntersectionObserver作为现代方案,凭借浏览器原生优化、简洁的代码逻辑,成为当前懒加载的首选。


在实际开发中,我们需要根据项目的兼容需求选择方案:若需兼容老旧浏览器,可采用 “IntersectionObserver+scroll 降级” 的混合方案;若面向现代浏览器,直接使用IntersectionObserver即可。


性能优化没有银弹,但图片懒加载是列表类页面(电商、资讯、社交)的 “必做优化”,小小的改动就能显著提升页面加载速度和用户体验。希望这篇文章能帮你彻底吃透图片懒加载,无论是面试还是实战,都能游刃有余!


参考文章:原文链接


该文章在 2026/2/3 9:16:22 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved