一、底层原理
懒加载的核心思想是按需加载,将资源加载时机与用户行为绑定,主要基于以下技术实现:
1. 浏览器渲染机制
- 浏览器解析 HTML 时,遇到
<img>、<script>等标签会立即发起资源请求,可能阻塞主线程。 - 懒加载通过动态创建 DOM 元素或修改元素属性,将资源请求延迟到用户需要时触发。
2. 元素可视区域检测
- 滚动事件监听:监听
window.scroll事件,结合Element.getBoundingClientRect()判断元素是否进入视口。
二、代码完整案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: #000;
}
.image-item {
width: 500px;
height: 500px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<!-- 图片标签结构说明:
- src:初始占位图(小尺寸加载快的图片)
- data-original:存储真实图片URL
- lazyload:标记需要懒加载的元素
-->
<img class="image-item" lazyload="true"
src="https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif"
data-original="https://img.36krcdn.com/hsossms/20250313/v2_15ad8ef9eca34830b4a2e081bbc7f57a@000000_oswg172644oswg1536oswg722_img_000?x-oss-process=image/resize,m_mfit,w_960,h_400,limit_0/crop,w_960,h_400,g_center" />
<img class="image-item" lazyload="true"
src="https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif"
data-original="https://img.36krcdn.com/hsossms/20250312/v2_aeaa7a1d51e74c3a8f909c96cd73a687@000000_oswg169950oswg1440oswg600_img_jpeg?x-oss-process=image/format,webp" />
<img class="image-item" lazyload="true"
src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
data-original="https://img.36krcdn.com/hsossms/20250312/v2_1c88dc26ff9341cf8738d670896ce3a8@5284654_oswg847922oswg1440oswg600_img_png?x-oss-process=image/resize,m_mfit,w_960,h_400,limit_0/crop,w_960,h_400,g_center/format,webp" />
<!-- 省略其他相似图片标签... -->
<script>
// 获取视口高度(浏览器可见区域高度)
const viewHeight = document.documentElement.clientHeight;
// 选中所有包含data-original和lazyload属性的img标签
const eles = document.querySelectorAll('img[data-original][lazyload]');
// 懒加载核心函数
const lazyload = function() {
// 将类数组转换为数组并遍历每个图片元素
Array.prototype.forEach.call(eles, function(item, index) {
// 若data-original为空则跳过(防御性判断)
if (item.dataset.original === "") return;
// 获取元素相对于视口的位置信息(矩形对象)
const rect = item.getBoundingClientRect();
// 判断元素是否在视口内或即将进入视口
// rect.bottom >= 0:元素底部未完全超出视口顶部
// rect.top < viewHeight:元素顶部已进入视口底部
if (rect.bottom >= 0 && rect.top < viewHeight) {
// 使用立即执行函数创建闭包,避免变量污染
(function() {
// 创建内存中的Image对象用于预加载图片
var img = new Image();
// 设置Image对象的src为真实图片地址
img.src = item.dataset.original;
// 图片加载成功的回调
img.onload = function() {
// 将真实图片地址赋值给DOM中的img标签
item.src = item.dataset.original;
// 移除data-original属性(避免重复加载)
item.removeAttribute('data-original');
// 移除lazyload标记(优化后续遍历性能)
item.removeAttribute('lazyload');
};
// 注:代码中未处理onerror事件,实际应用中建议添加错误处理
})();
}
});
};
// 监听滚动事件,滚动时触发懒加载检查
window.addEventListener('scroll', lazyload);
// DOM加载完成后立即检查一次(处理首屏可见的图片)
document.addEventListener('DOMContentLoaded', lazyload);
</script>
</body>
</html>
Array.prototype.forEach:
- 数组原生的
forEach方法,用于遍历数组元素。.call():
- JavaScript 的函数上下文绑定方法,格式为
func.call(thisArg, arg1, arg2, ...)。- 作用:将
forEach方法的执行上下文(this)指向第一个参数,并传递后续参数。eles:
- 一个类数组对象(如
NodeList、HTMLCollection或带有length属性的对象),非真正的数组。callback:
- 遍历每个元素时执行的回调函数,参数为
(item, index, array)。
核心原理分段解析
1. 页面结构与标记设计
-
占位图与真实图分离:
src属性指向小尺寸占位图(如 loading.gif 或透明图),避免浏览器首次加载大图片。data-original存储真实图片 URL,通过自定义属性实现数据与 DOM 的解耦。
-
懒加载标记:
lazyload="true"属性用于标识需要懒加载的图片,便于 JS 筛选目标元素。
2. 视口检测机制
-
核心 API:
getBoundingClientRect()- 返回值包含
top、bottom等属性,表示元素相对于视口的位置。 - 当
rect.bottom >= 0且rect.top < viewHeight时,认为元素在视口可见范围内。
- 返回值包含
-
视口高度获取:
document.documentElement.clientHeight获取浏览器可视区域高度,用于判断元素是否进入视口。
3. 图片预加载逻辑
-
内存预加载:
- 通过
new Image()创建独立的图片对象,在内存中提前加载图片。 - 避免直接修改 DOM 元素的
src,防止因图片加载慢导致的页面闪烁。
- 通过
-
事件回调处理:
onload事件触发时,将真实图片地址赋值给 DOM 元素,并移除标记属性。- 优化点:加载完成后移除
data-original和lazyload,减少后续遍历的无效检查。
4. 事件监听与触发时机
-
滚动事件监听:
window.addEventListener('scroll', lazyload)在滚动时检查图片是否进入视口。- 注意:此实现未使用节流函数,高频滚动时可能触发多次函数调用,实际应用中建议优化。
-
初始加载检查:
DOMContentLoaded事件触发时立即执行一次懒加载,确保首屏可见图片被加载。
三、总结
懒加载实现基于以下核心技术:
- 延迟加载:通过占位图和自定义属性分离真实资源,避免首次加载时请求大图片。
- 视口检测:利用
getBoundingClientRect()判断元素是否在可视区域,决定是否加载资源。 - 内存预加载:使用
new Image()在内存中提前加载图片,减少 DOM 渲染阻塞。 - 事件驱动:通过滚动事件和 DOM 加载事件触发懒加载逻辑,实现按需加载。
这种方案在传统浏览器中兼容性较好,但需注意滚动事件的性能优化,建议结合节流函数或现代IntersectionObserver API 进一步提升性能。