现代Web开发中,性能优化是提升用户体验的重要因素之一。当页面包含大量图片时,如果一次性加载所有图片,不仅会消耗大量带宽,还会严重影响页面加载速度。图片懒加载(Lazy Loading)技术可以有效解决这个问题,它只在用户滚动到图片可见区域时才加载图片,从而显著提升页面加载速度和用户体验。本文将详细介绍如何使用 HTML、CSS 和 JavaScript 实现一个高效的图片懒加载解决方案。效果演示
本系统通过一个图片网格布局展示懒加载功能的效果。页面初始加载时,只显示在视口内的图片,其他图片会显示加载占位符。当用户向下滚动页面,图片即将进入视口时,才会开始加载实际图片并显示加载动画。这种技术可以显著减少初始页面加载时间,节省带宽,并提升整体性能。
页面结构
页面采用网格布局展示图片内容,包含图片容器区域、加载占位符和图片信息区域。
图片网格容器
图片网格容器使用CSS Grid布局,将图片以2列形式排列,每张图片都有独立的包装容器和信息区域。<div class="container"> <h1>图片懒加载演示</h1> <div class="image-grid" id="imageContainer"></div></div>
图片项结构
每个图片项包含图片包装器和信息区域,其中图片包装器内包含懒加载图片元素和加载占位符。<div class="image-item"> <div class="image-wrapper"> <img class="lazy-image" data-src="https://picsum.photos/800/400?random=13" alt="示例图片 13"> <div class="loading-placeholder"> <div class="loading-spinner"></div> </div> </div> <div class="image-info"> <h3 class="image-title">风景图片 13</h3> <p class="image-desc">这是第 13 张示例图片,展示了美丽的风景。图片会在滚动到视口时才开始加载,节省带宽和提升页面加载速度。</p> </div></div>
核心功能实现
LazyLoader 类实现了图片懒加载的核心逻辑,主要包含以下功能:
初始化Intersection Observer - 监听图片元素是否进入视口
图片加载逻辑 - 当图片进入视口时,从 data-src 属性获取真实图片URL并加载
加载状态管理 - 显示加载动画,处理加载成功/失败的情况
Intersection Observer配置
Intersection Observer 是实现懒加载的关键技术,它提供了一种异步检测目标元素与祖先元素或视口相交情况的方法,相比传统的滚动事件监听更加高效。init() { const config = { rootMargin: '100px 0px', threshold: 0.01 };
this.imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { this.loadImage(entry.target); observer.unobserve(entry.target); } }); }, config);}
图片加载逻辑
当图片元素进入视口时,loadImage 方法会创建一个临时的 Image 对象进行预加载,这样可以确保图片完全加载后再显示到页面上,提供更好的用户体验。同时,该方法还处理了加载失败的情况,显示默认的错误图片。loadImage(img) { const dataSrc = img.getAttribute('data-src'); if (!dataSrc) return; const placeholder = img.parentElement.querySelector('.loading-placeholder'); const tempImg = new Image(); tempImg.onload = () => { img.src = dataSrc; img.classList.add('loaded'); if (placeholder) { placeholder.style.display = 'none'; } }; tempImg.onerror = () => { img.src = 'data:image/svg+xml;utf8,<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="400" height="400" fill="%23F5F5F5"/><text x="200" y="200" font-family="Arial" font-size="16" fill="%23999" text-anchor="middle">图片加载失败</text></svg>'; img.classList.add('loaded'); if (placeholder) { placeholder.style.display = 'none'; } }; tempImg.src = dataSrc;}
完整代码
git地址:https://gitee.com/ironpro/hjdemo/blob/master/image-lazy/index.html<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>图片懒加载</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background-color: #f8f9fa; padding: 20px; min-height: 100vh; color: #333; } .container { max-width: 1020px; margin: 0 auto; } h1 { text-align: center; margin-bottom: 30px; color: #2c3e50; font-size: 28px; font-weight: 500; } .image-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .image-item { background: #fff; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease, box-shadow 0.2s ease; margin-bottom: 0; } .image-item:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } .image-wrapper { position: relative; width: 100%; height: 300px; background-color: #f1f3f4; overflow: hidden; } .lazy-image { width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 0.3s ease; } .lazy-image.loaded { opacity: 1; } .loading-placeholder { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 40px; height: 40px; } .loading-spinner { width: 100%; height: 100%; border: 2px solid #e0e0e0; border-top: 2px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .image-info { padding: 20px; } .image-title { font-size: 18px; margin-bottom: 8px; font-weight: 500; color: #2c3e50; } .image-desc { font-size: 14px; color: #666; line-height: 1.5; }</style></head><body><div class="container"> <h1>图片懒加载演示</h1> <div class="image-grid" id="imageContainer"></div></div><script> class LazyLoader { constructor() { this.imageObserver = null; this.init(); } init() { const config = { rootMargin: '100px 0px', threshold: 0.01 };
this.imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { this.loadImage(entry.target); observer.unobserve(entry.target); } }); }, config); }
loadImage(img) { const dataSrc = img.getAttribute('data-src'); if (!dataSrc) return; const placeholder = img.parentElement.querySelector('.loading-placeholder'); const tempImg = new Image(); tempImg.onload = () => { img.src = dataSrc; img.classList.add('loaded'); if (placeholder) { placeholder.style.display = 'none'; } }; tempImg.onerror = () => { img.src = 'data:image/svg+xml;utf8,<svg width="100%" height="300" viewBox="0 0 400 300" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="400" height="300" fill="%23F5F5F5"/><text x="200" y="150" font-family="Arial" font-size="16" fill="%23999" text-anchor="middle">图片加载失败</text></svg>'; img.classList.add('loaded'); if (placeholder) { placeholder.style.display = 'none'; } }; tempImg.src = dataSrc; }
observe(img) { this.imageObserver.observe(img); } } function generateImageData(count) { const images = []; for (let i = 1; i <= count; i++) { images.push({ title: `风景图片 ${i}`, description: `这是第 ${i} 张示例图片,展示了美丽的风景。图片会在滚动到视口时才开始加载,节省带宽和提升页面加载速度。`, src: `https://picsum.photos/800/400?random=${i}`, alt: `示例图片 ${i}` }); } return images; } function createImageElement(imageData) { const div = document.createElement('div'); div.className = 'image-item'; div.innerHTML = `<div class="image-wrapper"> <img class="lazy-image" data-src="${imageData.src}" alt="${imageData.alt}"> <div class="loading-placeholder"> <div class="loading-spinner"></div> </div> </div> <div class="image-info"> <h3 class="image-title">${imageData.title}</h3> <p class="image-desc">${imageData.description}</p> </div>`; return div; } document.addEventListener('DOMContentLoaded', function() { const container = document.getElementById('imageContainer'); const lazyLoader = new LazyLoader(); const images = generateImageData(30); images.forEach(imageData => { const element = createImageElement(imageData); container.appendChild(element); const img = element.querySelector('.lazy-image'); lazyLoader.observe(img); }); });</script></body></html>
阅读原文:原文链接
该文章在 2025/12/23 10:17:18 编辑过