实现图片懒加载
使用原生 HTML、JavaScript 和 IntersectionObserver API 实现图片懒加载
问题
如何实现图片懒加载,避免一次性加载所有图片导致的性能问题和流量浪费?
解答
为什么需要懒加载
图片是影响网页性能的主要因素,一次性加载所有图片会带来两个问题:
- 影响用户体验,页面加载缓慢
- 浪费流量,用户可能不会浏览所有内容
懒加载的核心思路是:只加载可视区域内的图片,其他图片在滚动到可视区域时再加载。
方式一:HTML 原生属性
最简单的实现方式是使用 loading 属性:
<img src="./example.jpg" loading="lazy">
该属性兼容性良好,可以直接在生产环境使用。
方式二:JavaScript 实现
基本原理
- 图片初始时将真实地址存在
data-src属性中,src设为占位图 - 监听页面滚动,判断图片是否进入可视区域
- 进入可视区域后,将
data-src的值赋给src
<!-- 初始状态 -->
<img data-src="http://xx.com/xx.png" src="./img/default.png" />
<!-- 进入可视区域后 -->
<img data-src="http://xx.com/xx.png" src="http://xx.com/xx.png" />
背景图的实现方式类似:
<!-- 初始状态 -->
<div data-src="http://xx.com/xx.png" style="background-image: none;"></div>
<!-- 进入可视区域后 -->
<div data-src="http://xx.com/xx.png" style="background-image: url(http://xx.com/xx.png);"></div>
完整示例
HTML 结构:
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Lazyload</title>
<style>
img {
display: block;
margin-bottom: 50px;
height: 200px;
width: 400px;
}
</style>
</head>
<body>
<img src="./img/default.png" data-src="./img/1.jpg" />
<img src="./img/default.png" data-src="./img/2.jpg" />
<img src="./img/default.png" data-src="./img/3.jpg" />
<img src="./img/default.png" data-src="./img/4.jpg" />
<img src="./img/default.png" data-src="./img/5.jpg" />
</body>
</html>
懒加载函数:
function lazyload() {
// 获取可视区高度
let viewHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
let imgs = document.querySelectorAll('img[data-src]')
imgs.forEach((item) => {
if (item.dataset.src === '') return
// 获取元素相对浏览器视窗的位置
let rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
item.src = item.dataset.src
item.removeAttribute('data-src')
}
})
}
性能优化:节流
直接监听 scroll 事件会导致函数频繁触发,需要使用节流函数优化:
function throttle(fn, delay) {
let timer
let prevTime
return function (...args) {
const currTime = Date.now()
const context = this
if (!prevTime) prevTime = currTime
clearTimeout(timer)
if (currTime - prevTime > delay) {
prevTime = currTime
fn.apply(context, args)
} else {
timer = setTimeout(() => {
prevTime = currTime
fn.apply(context, args)
}, delay)
}
}
}
// 使用节流函数
window.addEventListener('scroll', throttle(lazyload, 200))
方式三:IntersectionObserver API
IntersectionObserver 是浏览器原生 API,可以自动观察元素是否进入可视区域,无需手动计算位置和监听滚动事件。
基本用法:
var io = new IntersectionObserver(callback, option)
// 开始观察
io.observe(document.getElementById('example'))
// 停止观察
io.unobserve(element)
// 关闭观察器
io.disconnect()
实现图片懒加载:
const imgs = document.querySelectorAll('img[data-src]')
const config = {
rootMargin: '0px',
threshold: 0,
}
let observer = new IntersectionObserver((entries, self) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let img = entry.target
let src = img.dataset.src
if (src) {
img.src = src
img.removeAttribute('data-src')
}
// 解除观察
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) => {
observer.observe(image)
})
关键点
- 使用
loading="lazy"属性是最简单的实现方式 - JavaScript 实现需要判断元素是否进入可视区域,使用
getBoundingClientRect()获取位置信息 - 监听
scroll事件时必须使用节流函数优化性能 IntersectionObserverAPI 是更现代的解决方案,无需手动计算位置和处理滚动事件- 图片真实地址存储在
data-src中,进入可视区域后再赋值给src属性
目录