简介
在对项目进行性能优化时,最常见的优化就是图片的懒加载,懒加载的目的是延迟加载页面上的图片,只有当图片进入视口的时候才加载,这样可以减少初始页面加载时间,节省带宽,提升用户体验。
懒加载有什么好处?
- 减少首屏加载时间:仅加载用户可见区域的图片,降低首次渲染阻塞。
- 节省带宽和流量:避免加载用户可能永远不会看到的内容(如长页面底部的图片)。
- 提升用户体验:防止页面卡顿,尤其是移动端和弱网环境。
- 优化SEO和性能评分:直接影响 Lighthouse 等工具的性能评分指标(如 LCP、FCP)。
在vue项目中(使用 vue-lazyload/vue3-lazyload
库)
-
安装库:
bashnpm install vue-lazyload --save
-
全局配置 (在
main.js
中):javascriptimport Vue from 'vue'; import VueLazyload from 'vue-lazyload'; Vue.use(VueLazyload, { preLoad: 1.3, // 预加载比例 loading: 'path/to/loading.gif', // 占位图 error: 'path/to/error.png', // 加载失败图 attempt: 3 // 重试次数 });
-
在组件中使用:
html<template> <img v-lazy="imageUrl" alt="description"> </template>
在React 项目中(使用 react-lazyload
库)
-
安装库:
bashnpm install react-lazyload --save
-
包裹组件:
jsximport LazyLoad from 'react-lazyload'; function MyComponent() { return ( <LazyLoad height={200} offset={100} placeholder={<LoadingSpinner />}> <img src="image.jpg" alt="description" /> </LazyLoad> ); }
原生JavaScript的实现方法
原生js的实现方法有两种:使用Intersection Observer API
和通过监听滚动事件(传统方案)。其中,Intersection Observer
更现代,性能更好,而滚动事件监听则是传统方法,兼容性可能更好,但性能较差。
方案1:使用 Intersection Observer API
实现步骤:
- 将图片的真实URL存放在
data-src
属性中 - 当图片进入可视区域时,将
data-src
赋值给src
属性 - 使用
IntersectionObserver
监听元素可见性变化
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>图片懒加载示例</title>
<style>
.lazy-img {
width: 100%;
height: 600px; /* 根据实际需求设置高度 */
background: #f0f0f0;
margin: 20px 0;
object-fit: cover;
}
</style>
</head>
<body>
<!-- 图片容器:真实图片地址存储在data-src -->
<img class="lazy-img" data-src="https://picsum.photos/800/600?image=1" alt="图片1">
<img class="lazy-img" data-src="https://picsum.photos/800/600?image=2" alt="图片2">
<!-- 更多图片... -->
<script>
document.addEventListener('DOMContentLoaded', () => {
// 获取所有需要懒加载的图片元素
const lazyImages = document.querySelectorAll('.lazy-img');
// 配置Intersection Observer
const observerOptions = {
root: null, // 使用视口作为观察区域
rootMargin: '0px 0px 200px 0px', // 提前200px触发加载
threshold: 0.1 // 当元素10%进入视口时触发
};
// 创建观察器实例
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) { // 当元素进入视口
const img = entry.target;
// 替换src属性触发图片加载
img.src = img.dataset.src;
// 加载完成后移除观察
img.addEventListener('load', () => {
img.classList.add('loaded');
observer.unobserve(img);
});
}
});
}, observerOptions);
// 开始观察所有图片
lazyImages.forEach(img => observer.observe(img));
});
</script>
</body>
</html>
代码解析:
-
HTML结构
- 使用
data-src
存储真实图片地址 - 初始
src
为空或使用占位图 - 通过CSS设置图片容器的占位样式
- 使用
-
IntersectionObserver配置
rootMargin: '0px 0px 200px 0px'
:提前200px触发加载threshold: 0.1
:当元素10%进入视口时触发
-
加载流程
- 检测到元素进入视口时替换
src
属性 - 图片加载完成后移除观察器
- 检测到元素进入视口时替换
方案2:传统滚动监听方案(兼容旧浏览器)
html
<script>
// 节流函数:避免频繁触发滚动事件
function throttle(func, delay = 200) {
let lastCall = 0;
return (...args) => {
const now = new Date().getTime();
if (now - lastCall < delay) return;
lastCall = now;
func.apply(this, args);
}
}
// 判断元素是否进入视口
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top <= window.innerHeight * 1.5 && // 提前1.5屏加载
rect.bottom >= 0
);
}
// 加载可见图片
function lazyLoad() {
const images = document.querySelectorAll('.lazy-img[data-src]');
images.forEach(img => {
if (isInViewport(img)) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
});
}
// 初始化
document.addEventListener('DOMContentLoaded', lazyLoad);
window.addEventListener('scroll', throttle(lazyLoad));
window.addEventListener('resize', throttle(lazyLoad));
</script>
实践建议
-
占位符策略
- 使用与图片比例一致的占位容器
- 可添加低质量图片预览(LQIP)
-
性能优化
- 优先使用WebP格式图片
- 配合
loading="lazy"
原生属性(兼容性需注意)
html<img src="image.jpg" loading="lazy" alt="...">
实现效果对比
指标 | IntersectionObserver | 滚动监听 |
---|---|---|
性能消耗 | 低(浏览器原生优化) | 高(频繁触发) |
代码复杂度 | 简单 | 复杂 |
浏览器兼容性 | IE不支持(需polyfill) | 全兼容 |
精确度 | 高 | 依赖计算逻辑 |
注意事项
- 首屏图片不要使用懒加载
- 对SEO关键内容谨慎使用
- 移动端注意
rootMargin
的合理设置 - 优先使用原生
loading="lazy"
(需考虑目标用户浏览器)