解锁懒加载:提升性能的神奇魔法
一、什么是懒加载
懒加载是一种资源加载优化技术,核心是 “按需加载”,只在资源即将进入视野时才加载。
二、懒加载的作用
- 减少初始加载时间,提升页面打开速度。
- 降低服务器初始请求压力,节省带宽资源。
- 减少设备内存占用,提升页面运行流畅度。
三、常见应用场景
- 长列表页面(如商品列表、资讯流)的图片加载。
- 包含大量视频的页面(仅加载当前可视区域视频)。
- 非首屏的组件或脚本(如底部评论区、统计脚本)。
四、懒加载的实现原理
懒加载的核心实现原理可以概括为 “延迟加载 + 条件触发”—— 初始不加载非必要资源,通过监听特定事件判断资源是否 “即将被使用”,满足条件时再动态加载资源。
具体拆解为 4 个关键步骤,结合技术细节和逻辑闭环说明:
1、核心前提:资源 “占位” 与初始状态标记
懒加载的第一步是让浏览器不主动加载目标资源,同时预留资源位置避免页面布局抖动:
1.1 不直接赋值资源地址:
比如图片,不把真实地址写在 src 属性(src 会触发浏览器自动加载),而是存到自定义属性(如 data-src、data-original);视频同理,不设置 src 或 source 的 src,改用自定义属性存储真实地址。
<!-- 图片懒加载示例:初始不加载,真实地址存在 data-src -->
<img class="lazy" data-src="真实图片地址.jpg" alt="示例" width="300" height="200">
1.2 预留布局空间:
通过 width/height 属性或 CSS 固定资源尺寸(如 aspect-ratio),避免资源加载后页面重排(回流)。
2、关键步骤:监听 “触发条件” 事件
要判断资源 “是否即将被访问”,需要监听用户行为或页面状态变化,常见触发事件有 3 类:
2.1 滚动事件(最常用):
监听 window.scroll 事件(或元素的 scroll 事件,如滚动容器内的资源),实时判断资源位置。
❗ 优化点:用 throttle(节流)限制事件触发频率(如 100ms 触发一次),避免滚动时频繁计算导致性能消耗。
2.2 视口交叉检测(现代方案):
使用 Intersection Observer API(浏览器原生 API),无需手动监听滚动,直接检测 “资源元素是否进入视口(或与视口交叉)”,性能更优、代码更简洁。
2.3 其他触发场景:
点击事件(如点击 “加载更多” 时加载下方资源);
页面加载完成后的初始检测(首屏内的 “懒加载资源” 需要初始判断是否在视口,避免漏加载)。
3、核心逻辑:判断 “是否需要加载”
通过事件触发后,计算资源位置与视口的关系,核心判断条件是:资源是否进入视口,或即将进入视口(预留缓冲)。
3.1 传统计算方式(手动判断)
通过 getBoundingClientRect() 获取元素的位置信息,结合视口高度 / 宽度判断:
- 元素顶部距离视口顶部的距离(element.top)≤ 视口高度(window.innerHeight)+ 缓冲距离(如 200px,提前加载,避免用户看到空白);
- 元素左侧距离视口左侧的距离(element.left)≤ 视口宽度(window.innerWidth)+ 缓冲距离(适配横向滚动场景)。
核心计算代码示例:
function isInViewport(element) {const rect = element.getBoundingClientRect();const viewportHeight = window.innerHeight || document.documentElement.clientHeight;const viewportWidth = window.innerWidth || document.documentElement.clientWidth;const buffer = 200; // 提前200px加载,提升体验// 元素顶部进入视口(含缓冲),且元素左侧在视口内return rect.top <= viewportHeight + buffer && rect.left <= viewportWidth + buffer;
}
3.2 现代方案(Intersection Observer)
无需手动计算,API 自动监听元素与视口的 “交叉状态”:
- 定义一个观察者(Observer),指定交叉阈值(如元素进入视口 10% 时触发);
- 当元素满足交叉条件时,触发回调函数,执行加载逻辑。
4、最终动作:动态加载资源并 “解锁”
当资源满足加载条件时,执行真正的加载操作,同时避免重复加载:
4.1 动态赋值真实地址:
把自定义属性(data-src)的值赋给原生属性(src),浏览器会自动发起请求加载资源;
视频需创建 source 元素并赋值 src,或直接修改视频 src。
4.2 标记 “已加载”,避免重复处理:
加载后移除 lazy 类或添加 loaded 类,后续事件触发时跳过已加载的资源。
4.3 清理监听(可选):
若资源加载后不会再 “离开视口并需要重新加载”(如长列表图片),可停止监听该元素(如 Intersection Observer 调用 unobserve),减少性能消耗。
<!-- 1. 初始占位 -->
<img class="lazy" data-src="image1.jpg" alt="示例" width="300" height="200">
<img class="lazy" data-src="image2.jpg" alt="示例" width="300" height="200"><script>
// 2. 监听触发事件(以 Intersection Observer 为例)
const lazyImages = document.querySelectorAll('.lazy');// 3. 定义加载逻辑
function loadImage(img) {if (img.dataset.src) {img.src = img.dataset.src; // 动态赋值,触发加载img.removeAttribute('data-src'); // 移除自定义属性img.classList.add('loaded'); // 标记已加载}
}// 4. 创建观察者,判断交叉条件
const observer = new IntersectionObserver((entries) => {entries.forEach(entry => {if (entry.isIntersecting) { // 元素进入视口loadImage(entry.target); // 加载资源observer.unobserve(entry.target); // 停止监听该元素}});
}, { rootMargin: '200px' }); // 预留200px缓冲,提前加载// 5. 对所有懒加载图片启动监听
lazyImages.forEach(img => observer.observe(img));
</script>
5、核心总结
懒加载的实现原理本质是:“先占位不加载 → 监听触发事件 → 判断是否进入视口(含缓冲) → 动态加载资源并标记”
其中,Intersection Observer 是现代浏览器推荐方案(性能优、代码简洁),传统滚动 + 手动计算适用于需要兼容旧浏览器的场景。
五、懒加载实现代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><title>Document</title><style>
.bg,.bg1,.small,.big{overflow: hidden;width: 100%;height: 800px;background-size: cover; /* 保留原有样式 */background-repeat: no-repeat;background-position: center center;
}</style>
</head>
<body>
<div class="bg" data-bg="https://b0.bdstatic.com/ugc/7vzLq4vmPMp_X3SVZJaslg0ea528533a78abb146f8d2c8426b952d.jpg"></div>
<div class="bg1" data-bg="https://img0.baidu.com/it/u=894097787,217968889&fm=253&app=138&f=JPEG?w=500&h=889"></div>
<div class="small" data-bg="https://pic.rmb.bdstatic.com/bjh/3f11932f0ab1/250105/8cf77de872c0c4fc63b96b7a9679d3fe.jpeg"></div>
<div class="big" data-bg="https://pics4.baidu.com/feed/ae51f3deb48f8c54a4a7709a9ce510fae1fe7f6e.jpeg@f_auto?token=284dceaa76d389c9c4bc2632df15a9c1"></div>
<script>// 等待页面DOM加载完成
$(document).ready(function() {// 定义懒加载函数function lazyLoad() {// 1. 处理img标签(logo、秒杀图标等)$("img[data-src]").each(function() {const $img = $(this);// 判断元素是否进入可视区域if (isInViewport($img)) {$img.attr("src", $img.data("src")).removeAttr("data-src"); // 加载图片并移除自定义属性}});// 2. 处理背景图(大背景、商品卡片背景等)$("[data-bg]").each(function() {const $el = $(this);if (isInViewport($el)) {$el.css("background-image", `url(${ $el.data("bg") })`).removeAttr("data-bg"); // 加载背景图}});}// 辅助函数:判断元素是否在可视区域内function isInViewport($el) {const rect = $el[0].getBoundingClientRect();// 元素顶部进入视口底部,或元素底部进入视口顶部,即判定为可见return (rect.top <= $(window).height() + 100 && // 提前100px加载,避免滚动时空白rect.bottom >= 0);}// 初始加载一次(加载首屏可见元素)lazyLoad();// 监听滚动事件,触发懒加载$(window).scroll(function() {lazyLoad();});// 监听窗口 resize 事件(适配屏幕变化)$(window).resize(function() {lazyLoad();});
});
</script>
</body>
</html>
