【JS实战案例】实现图片懒加载(基础版)原生JS+性能优化,新手可直接复现

前言:图片懒加载是前端性能优化中最基础、最常用的技巧之一,尤其适用于电商首页、长图文、相册等图片数量较多的场景。本文将用「原生JS」实现基础版图片懒加载,不依赖任何框架/第三方库,代码简洁、注释详细,新手可直接复制运行,同时讲解核心原理和基础性能优化,适合作为JS实战入门案例。

一、什么是图片懒加载(基础版)?

图片懒加载(Lazy Load),核心思想是「按需加载」------ 网页初始化时,不加载所有图片,只加载用户当前可视区域(视口)内的图片;当用户向下滚动页面,图片进入视口范围时,再动态加载该图片。

基础版懒加载的特点:

  • 原生JS实现,无依赖,上手简单

  • 聚焦核心逻辑,不添加复杂功能(如预加载距离、错误处理可灵活拓展)

  • 适配绝大多数现代浏览器,兼容性良好

  • 适合新手学习,理解「视口判断」「DOM操作」「事件监听」的核心用法

适用场景:个人博客、小型网站、Demo演示、新手实战练习,可作为生产级懒加载的入门铺垫。

二、基础版实现原理(3步核心)

懒加载的核心逻辑非常简单,拆解为3步,新手可快速理解:

  1. 替换图片地址:将图片的真实地址存入自定义属性(如 data-src),而非默认的 src 属性;src 暂时存放占位图(避免浏览器报加载失败,不占用额外请求)。

  2. 监听滚动事件:监听页面滚动事件,实时检测每张懒加载图片的位置,判断其是否进入用户视口。

  3. 动态加载图片:当图片进入视口时,将 data-src 的值赋值给 src 属性,触发图片加载,加载完成后可添加过渡效果提升体验。

补充:为了避免滚动事件频繁触发导致性能损耗,我们会添加「轻量防抖」优化,这也是基础版中必学的性能优化小技巧。

三、完整可运行代码(新手直接复制)

以下代码包含 HTML 结构、CSS 样式、JS 核心逻辑,注释详细,复制到本地保存为 .html 文件,用浏览器打开即可看到效果:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JS实战案例 - 图片懒加载(基础版)</title>
    <style>
        /* 基础样式:模拟长页面,方便测试滚动加载 */
        body {
            margin: 0;
            padding: 20px 0;
            background-color: #f8f9fa;
        }
        /* 图片容器:统一尺寸,添加占位背景 */
        .img-container {
            width: 80%;
            max-width: 800px;
            height: 500px;
            margin: 50px auto;
            background-color: #e9ecef;
            border-radius: 10px;
            overflow: hidden;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }
        /* 懒加载图片样式:默认透明,加载后过渡显示 */
        .lazy-img {
            width: 100%;
            height: 100%;
            object-fit: cover; /* 保持图片比例,填充容器 */
            opacity: 0;
            transition: opacity 0.5s ease; /* 加载过渡动画,更丝滑 */
        }
        /* 图片加载完成后,显示图片 */
        .lazy-img.loaded {
            opacity: 1;
        }
        /* 页面标题样式 */
        h1 {
            text-align: center;
            color: #333;
            margin: 40px 0;
        }
    </style>
</head>
<body>
    <h1>JS 图片懒加载(基础版)| 原生JS实现&lt;/h1&gt;

    <!-- 图片列表:src放占位图,真实地址存data-src -->
    <div class="img-container">
        <img class="lazy-img" 
             data-src="https://picsum.photos/800/500?random=1" 
             src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
             alt="示例图片1">
    </div>
    <div class="img-container">
        <img class="lazy-img" 
             data-src="https://picsum.photos/800/500?random=2" 
             src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
             alt="示例图片2">
    </div>
    <div class="img-container">
        <img class="lazy-img" 
             data-src="https://picsum.photos/800/500?random=3" 
             src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
             alt="示例图片3">
    </div>
    <div class="img-container">
        <img class="lazy-img" 
             data-src="https://picsum.photos/800/500?random=4" 
             src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
             alt="示例图片4">
    </div>
    <div class="img-container">
        <img class="lazy-img" 
             data-src="https://picsum.photos/800/500?random=5" 
             src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
             alt="示例图片5">
    </div>
    <div class="img-container">
        <img class="lazy-img" 
             data-src="https://picsum.photos/800/500?random=6" 
             src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
             alt="示例图片6">
    </div>

    <script>
        // 1. 核心函数:检测图片是否进入视口,进入则加载
        function lazyLoad() {
            // 获取所有未加载的懒加载图片(排除已加载的,避免重复处理)
            const lazyImgs = document.querySelectorAll('.lazy-img:not(.loaded)');
            
            // 遍历每张图片,判断是否进入视口
            lazyImgs.forEach(img => {
                // getBoundingClientRect():原生API,获取元素相对于视口的位置信息
                const imgRect = img.getBoundingClientRect();
                
                // 判断条件:图片顶部 <= 视口高度 + 100(提前100px加载,避免滚动到图片才加载,更丝滑)
                // 视口高度:window.innerHeight(当前浏览器可视区域高度)
                if (imgRect.top <= window.innerHeight + 100) {
                    // 动态赋值:将data-src的真实地址赋给src,触发图片加载
                    img.src = img.dataset.src;
                    
                    // 图片加载完成后,添加loaded类,显示过渡动画
                    img.onload = function() {
                        img.classList.add('loaded');
                    }
                    
                    // 处理图片加载失败的情况(可选,提升健壮性)
                    img.onerror = function() {
                        // 加载失败时,显示默认占位图(可替换为自己的占位图地址)
                        img.src = "https://picsum.photos/800/500?random=0";
                        img.classList.add('loaded');
                    }
                }
            });
        }

        // 2. 防抖函数:优化滚动事件,避免频繁触发lazyLoad,减少性能损耗
        // 原理:滚动停止后,延迟指定时间(此处100ms)再执行函数
        function debounce(fn, delay = 100) {
            let timer = null; // 定时器标识
            return function() {
                clearTimeout(timer); // 每次滚动,清除上一个定时器
                timer = setTimeout(() => {
                    fn(); // 滚动停止100ms后,执行懒加载函数
                }, delay);
            }
        }

        // 3. 绑定事件,触发懒加载
        // 页面加载完成后,执行一次,加载首屏可见图片
        window.addEventListener('load', lazyLoad);
        // 监听页面滚动事件,用防抖函数优化
        window.addEventListener('scroll', debounce(lazyLoad));
    </script>
</body>
</html>

四、核心代码拆解(新手必看)

很多新手复制代码后,只知道"能运行",却不懂"为什么能运行",这里逐点拆解核心代码,帮你吃透原理:

1. 占位图处理(关键细节)

代码中 src 用的是 1x1 的透明 GIF(base64 格式),而非空值或普通图片,原因有2点:

  • 避免浏览器报"图片加载失败"的错误提示,影响页面美观;

  • base64 格式无需额外发送 HTTP 请求,不占用网络资源,比单独引用一张占位图更高效。

如果不想用 base64,也可以替换为自己的占位图地址(如 loading 动图)。

2. 视口判断核心 API:getBoundingClientRect()

这是原生 JS 中获取元素位置的核心 API,无需额外引入库,返回一个对象,包含元素相对于视口的 top、bottom、left、right 等属性:

  • imgRect.top:图片顶部距离视口顶部的距离;

  • window.innerHeight:当前浏览器可视区域的高度;

  • 判断条件 imgRect.top <= window.innerHeight + 100:提前 100px 加载图片,避免用户滚动到图片时,才开始加载导致的空白等待,提升体验。

3. 防抖优化(性能关键)

页面滚动时,scroll 事件会被频繁触发(每秒可能触发几十次),如果直接绑定 lazyLoad 函数,会导致浏览器频繁执行 DOM 操作,造成性能损耗(页面卡顿)。

防抖函数的作用:让 lazyLoad 函数只在滚动停止后,延迟 100ms 再执行,减少函数执行次数,优化页面性能。新手可先记住用法,后续再深入理解防抖的原理。

4. 图片加载完成/失败处理

  • img.onload:图片加载成功后触发,添加 loaded 类,让图片通过 opacity 过渡显示,更丝滑;

  • img.onerror:图片加载失败后触发(如网络异常、图片地址错误),显示默认占位图,避免页面出现空白,提升代码健壮性。

五、常见问题排查(新手避坑)

运行代码后,如果出现异常,可对照以下问题排查:

  1. 图片不加载?

    1. 检查图片类名是否正确(必须是 lazy-img);

    2. 检查自定义属性是否是 data-src(不是 src,也不是 other-src);

    3. 打开浏览器控制台(F12),查看 Network 面板,确认图片是否有请求。

  2. 滚动时图片加载卡顿?

    1. 检查是否添加了防抖函数,若未添加,scroll 事件频繁触发会导致卡顿;

    2. 减少图片数量,或降低图片尺寸,避免大图片加载耗时过长。

  3. 浏览器兼容性问题?

    1. getBoundingClientRect() 兼容 IE8+、所有现代浏览器,无需担心;

    2. dataset 属性兼容 IE11+,若需兼容更低版本 IE,可替换为 img.getAttribute('data-src')。

六、总结与拓展

1. 基础版懒加载核心收获

通过这个案例,你可以掌握 3 个前端核心知识点:

  • DOM 操作:querySelectorAll、classList、getAttribute/dataset 等;

  • 事件监听:load 事件(页面加载完成)、scroll 事件(页面滚动);

  • 性能优化:防抖函数的基础用法、图片按需加载的思路。

2. 进阶方向(后续可拓展)

基础版懒加载可满足新手实战和简单场景,若需用于生产级项目,可拓展以下功能:

  • 用 IntersectionObserver API 替代 scroll 监听(更高效,无需手动判断视口);

  • 添加加载动画(如 skeleton 骨架屏),提升用户体验;

  • 支持响应式图片(不同设备加载不同尺寸的图片);

  • 结合后端,实现图片渐进式加载(先加载模糊缩略图,再加载高清图)。

最后,建议新手先把基础版代码吃透,手动修改参数(如提前加载距离、防抖延迟),观察效果变化,再逐步尝试进阶功能。如果觉得本文对你有帮助,欢迎点赞收藏,关注我,每日更新 JS 实战小案例~

相关推荐
摇滚侠1 小时前
基于 session 的登录认证方式,基于 token 的登录认证方式,对比
java·开发语言·intellij-idea
北国1371 小时前
【Java】多线程输出滞后/错误解决&&线程创建方式与原理
java·开发语言
Coder_Boy_1 小时前
【Java核心】JVM核心知识清单
java·开发语言·jvm
你的冰西瓜2 小时前
C++中的queue容器详解
开发语言·c++·stl
Elastic 中国社区官方博客2 小时前
从向量到关键词:在 LangChain 中的 Elasticsearch 混合搜索
大数据·开发语言·数据库·elasticsearch·搜索引擎·ai·langchain
学编程的闹钟2 小时前
C语言GetLastError函数
c语言·开发语言·学习
梵刹古音2 小时前
【C++】多态
开发语言·c++
hello 早上好2 小时前
07_JVM 双亲委派机制
开发语言·jvm
前端程序猿i3 小时前
第 8 篇:Markdown 渲染引擎 —— 从流式解析到安全输出
开发语言·前端·javascript·vue.js·安全