Intersection Observer API 实现图片懒加载
利用 Intersection Observer API 可以观察目标元素与祖先元素或顶级文档视口的交叉点变化的能力实现图片的懒加载
client preview
最近对 Intersection Observer API 做了一些学习(感兴趣的可以查看 Intersection Observer API), 顺便写了一个demo。
本文将带领大家使用 Intersection Observer API实现滚动时图片的动态加载效果。
核心代码:
javascript
const options = {
root: null,
rootMargin: "0px 0px 50px 0px", // 提前50px加载
threshold: 0.01,
};
// create observer
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
const placeholder = img.nextElementSibling;
// add src to img
img.src = img.dataset.src;
// class loaded
img.onload = function () {
img.classList.add("loaded");
placeholder.classList.add("hidden");
// update loaded count
loadedCountElement.textContent =
parseInt(loadedCountElement.textContent) + 1;
};
// remove observer
observer.unobserve(img);
}
});
}, options);
// observe all images
lazyImages.forEach((img) => {
observer.observe(img);
});
利用 Intersection Observer API 实现图片的懒加载非常简单。
核心思想:
所有图片最初不加载,使用占位符显示。
图片元素使用 data-src 属性存储真实图片地址。
创建一个IntersectionObserver 实例观察所有的img元素,当图片进入视口时,将 data-src 的值赋给 src 属性。
图片加载完成后显示,并隐藏加载占位符。
完整代码:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Intersection Observer 实现图片懒加载</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(120deg, pink, #b21f1f, #fdbb2d);
color: #fff;
line-height: 1.6;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.content {
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 30px;
margin-bottom: 40px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 25px;
margin-top: 30px;
}
.card {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 20px rgba(0, 0, 0, 0.3);
}
.image-container {
height: 250px;
position: relative;
overflow: hidden;
}
.lazy-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: opacity 0.5s ease;
opacity: 0;
}
.lazy-image.loaded {
opacity: 1;
}
.placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, #2c3e50, #4a5568);
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.5s ease;
}
.placeholder.hidden {
opacity: 0;
pointer-events: none;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fdbb2d;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.card-content {
padding: 20px;
}
.stats {
display: flex;
justify-content: space-around;
background: rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 10px;
margin-top: 30px;
}
.stat-box {
text-align: center;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: #fdbb2d;
}
.stat-label {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.7);
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="stats">
<div class="stat-box">
<div class="stat-value">12</div>
<div class="stat-label">待加载图片</div>
</div>
<div class="stat-box">
<div class="stat-value" id="loaded-count">0</div>
<div class="stat-label">已加载图片</div>
</div>
<div class="stat-box">
<div class="stat-value" id="observed-count">0</div>
<div class="stat-label">被观察元素</div>
</div>
</div>
</div>
<div class="grid" id="image-grid"></div>
</div>
<script>
const images = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 6 },
{ id: 7 },
{ id: 8 },
{ id: 9 },
{ id: 10 },
{ id: 11 },
{ id: 12 },
];
const imageGrid = document.getElementById("image-grid");
images.forEach((img) => {
const card = document.createElement("div");
card.className = "card";
card.innerHTML = `
<div class="image-container">
<img class="lazy-image"
data-src="https://picsum.photos/id/${
img.id + 10
}/600/400"
alt="${img.title}">
<div class="placeholder">
<div class="spinner"></div>
</div>
</div>
`;
imageGrid.appendChild(card);
});
// 懒加载实现
document.addEventListener("DOMContentLoaded", function () {
const lazyImages = document.querySelectorAll(".lazy-image");
const loadedCountElement = document.getElementById("loaded-count");
const observedCountElement = document.getElementById("observed-count");
observedCountElement.textContent = lazyImages.length;
// Intersection Observer options
const options = {
root: null,
rootMargin: "0px 0px 50px 0px", // 提前50px加载
threshold: 0.01,
};
// create observer
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log(entry);
const img = entry.target;
const placeholder = img.nextElementSibling;
// add src to img
img.src = img.dataset.src;
// class loaded
img.onload = function () {
img.classList.add("loaded");
placeholder.classList.add("hidden");
// update loaded count
loadedCountElement.textContent =
parseInt(loadedCountElement.textContent) + 1;
};
// remove observer
observer.unobserve(img);
}
});
}, options);
// observe all images
lazyImages.forEach((img) => {
observer.observe(img);
});
});
</script>
</body>
</html>
