前言:图片懒加载是前端性能优化中最基础、最常用的技巧之一,尤其适用于电商首页、长图文、相册等图片数量较多的场景。本文将用「原生JS」实现基础版图片懒加载,不依赖任何框架/第三方库,代码简洁、注释详细,新手可直接复制运行,同时讲解核心原理和基础性能优化,适合作为JS实战入门案例。
一、什么是图片懒加载(基础版)?
图片懒加载(Lazy Load),核心思想是「按需加载」------ 网页初始化时,不加载所有图片,只加载用户当前可视区域(视口)内的图片;当用户向下滚动页面,图片进入视口范围时,再动态加载该图片。
基础版懒加载的特点:
-
原生JS实现,无依赖,上手简单
-
聚焦核心逻辑,不添加复杂功能(如预加载距离、错误处理可灵活拓展)
-
适配绝大多数现代浏览器,兼容性良好
-
适合新手学习,理解「视口判断」「DOM操作」「事件监听」的核心用法
适用场景:个人博客、小型网站、Demo演示、新手实战练习,可作为生产级懒加载的入门铺垫。
二、基础版实现原理(3步核心)
懒加载的核心逻辑非常简单,拆解为3步,新手可快速理解:
-
替换图片地址:将图片的真实地址存入自定义属性(如 data-src),而非默认的 src 属性;src 暂时存放占位图(避免浏览器报加载失败,不占用额外请求)。
-
监听滚动事件:监听页面滚动事件,实时检测每张懒加载图片的位置,判断其是否进入用户视口。
-
动态加载图片:当图片进入视口时,将 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实现</h1>
<!-- 图片列表: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:图片加载失败后触发(如网络异常、图片地址错误),显示默认占位图,避免页面出现空白,提升代码健壮性。
五、常见问题排查(新手避坑)
运行代码后,如果出现异常,可对照以下问题排查:
-
图片不加载?
-
检查图片类名是否正确(必须是 lazy-img);
-
检查自定义属性是否是 data-src(不是 src,也不是 other-src);
-
打开浏览器控制台(F12),查看 Network 面板,确认图片是否有请求。
-
-
滚动时图片加载卡顿?
-
检查是否添加了防抖函数,若未添加,scroll 事件频繁触发会导致卡顿;
-
减少图片数量,或降低图片尺寸,避免大图片加载耗时过长。
-
-
浏览器兼容性问题?
-
getBoundingClientRect() 兼容 IE8+、所有现代浏览器,无需担心;
-
dataset 属性兼容 IE11+,若需兼容更低版本 IE,可替换为 img.getAttribute('data-src')。
-
六、总结与拓展
1. 基础版懒加载核心收获
通过这个案例,你可以掌握 3 个前端核心知识点:
-
DOM 操作:querySelectorAll、classList、getAttribute/dataset 等;
-
事件监听:load 事件(页面加载完成)、scroll 事件(页面滚动);
-
性能优化:防抖函数的基础用法、图片按需加载的思路。
2. 进阶方向(后续可拓展)
基础版懒加载可满足新手实战和简单场景,若需用于生产级项目,可拓展以下功能:
-
用 IntersectionObserver API 替代 scroll 监听(更高效,无需手动判断视口);
-
添加加载动画(如 skeleton 骨架屏),提升用户体验;
-
支持响应式图片(不同设备加载不同尺寸的图片);
-
结合后端,实现图片渐进式加载(先加载模糊缩略图,再加载高清图)。
最后,建议新手先把基础版代码吃透,手动修改参数(如提前加载距离、防抖延迟),观察效果变化,再逐步尝试进阶功能。如果觉得本文对你有帮助,欢迎点赞收藏,关注我,每日更新 JS 实战小案例~