各位老铁,双击屏幕,今天咱们就来聊聊前端性能优化里的一大利器——图片懒加载。 啥? 你说图片懒加载听起来很高大上? 其实啊, 就是咱们让那些暂时看不见的图片先别着急加载, 等它们滚到视窗里了再露脸, 这样就能减轻页面初始加载的负担, 让用户更快地看到内容, 体验嗖嗖地往上涨!
一、 为什么需要图片懒加载?
想象一下,如果你的页面有几百张图片,而且用户只看了最上面的几张,剩下的图片是不是白白浪费了带宽? 这种行为简直是“带宽刺客”! 尤其是在移动端, 流量可是金钱啊!
懒加载的意义就在于:
- 提升页面加载速度: 减少首次加载时HTTP请求的数量,加快页面渲染。
- 节省带宽: 只加载用户可见区域的图片,避免浪费流量。
- 提升用户体验: 更快的加载速度意味着更流畅的体验,谁不喜欢呢?
二、 懒加载的实现方案
实现懒加载的方法有很多,咱们从最基础的开始, 逐步深入。
1. 传统方案: offsetTop
+ window.innerHeight
+ window.pageYOffset
这是最原始,也是兼容性最好的一种方案。 它的核心思想是: 判断图片是否进入了可视区域。
offsetTop
: 图片距离文档顶部的距离。window.innerHeight
: 浏览器视窗的高度。window.pageYOffset
: 页面垂直滚动的距离。
只要 offsetTop
小于 window.innerHeight + window.pageYOffset
, 就说明图片进入了可视区域。
代码示例:
<!DOCTYPE html>
<html>
<head>
<title>传统懒加载</title>
<style>
img {
display: block; /* 确保图片是块级元素,offsetTop才能正确计算 */
margin-bottom: 20px; /* 为了方便演示,图片之间加点间距 */
}
</style>
</head>
<body>
<img data-src="image1.jpg" src="placeholder.gif" alt="Image 1">
<img data-src="image2.jpg" src="placeholder.gif" alt="Image 2">
<img data-src="image3.jpg" src="placeholder.gif" alt="Image 3">
<!-- 更多图片... -->
<script>
function lazyLoad() {
const images = document.querySelectorAll('img[data-src]'); // 获取所有带有data-src属性的img标签
const windowHeight = window.innerHeight;
const scrollY = window.pageYOffset;
images.forEach(img => {
if (img.offsetTop < windowHeight + scrollY) {
img.src = img.dataset.src; // 替换src
img.removeAttribute('data-src'); // 移除data-src,避免重复加载
}
});
}
// 页面加载完成时执行一次,防止首屏图片未加载
window.addEventListener('load', lazyLoad);
// 监听滚动事件
window.addEventListener('scroll', lazyLoad);
</script>
</body>
</html>
data-src
: 我们使用data-src
属性来存储图片的真实地址,src
属性先放一张占位图placeholder.gif
。lazyLoad
函数: 这个函数负责判断图片是否进入可视区域,如果进入了,就把data-src
的值赋给src
, 并移除data-src
属性。- 事件监听: 监听
load
和scroll
事件, 确保在页面加载完成和滚动时都执行lazyLoad
函数。
这个方案的优点是兼容性好,缺点是性能较差,每次滚动都会遍历所有图片,即使图片已经加载过。
2. 优化方案: 函数节流(Throttling)
为了解决传统方案的性能问题, 我们可以使用函数节流。 函数节流是指: 在一定时间内, 只能执行一次函数。 这样可以减少 lazyLoad
函数的执行频率。
代码示例:
function throttle(func, delay) {
let timeoutId;
let lastExecTime = 0;
return function(...args) {
const now = Date.now();
if (!timeoutId) {
// 第一次立即执行
func.apply(this, args);
lastExecTime = now;
timeoutId = setTimeout(() => {
timeoutId = null;
}, delay);
} else if (now - lastExecTime > delay) {
// 超过delay时间,再次执行
func.apply(this, args);
lastExecTime = now;
}
};
}
const throttledLazyLoad = throttle(lazyLoad, 200); // 每200ms执行一次
window.addEventListener('scroll', throttledLazyLoad);
throttle
函数: 这是一个通用的函数节流函数, 接受一个函数func
和一个延迟时间delay
作为参数。throttledLazyLoad
: 使用throttle
函数包装lazyLoad
函数, 得到一个节流后的函数。- 事件监听: 监听
scroll
事件, 使用throttledLazyLoad
函数。
3. Intersection Observer API
这是现代浏览器提供的一种更高效的懒加载方案。 Intersection Observer API
可以监听元素是否进入可视区域, 并且可以设置交叉比例, 非常灵活。
代码示例:
<!DOCTYPE html>
<html>
<head>
<title>Intersection Observer 懒加载</title>
<style>
img {
display: block;
margin-bottom: 20px;
}
</style>
</head>
<body>
<img data-src="image1.jpg" src="placeholder.gif" alt="Image 1">
<img data-src="image2.jpg" src="placeholder.gif" alt="Image 2">
<img data-src="image3.jpg" src="placeholder.gif" alt="Image 3">
<!-- 更多图片... -->
<script>
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img); // 停止监听已加载的图片
}
});
});
images.forEach(img => {
observer.observe(img); // 开始监听每个图片
});
</script>
</body>
</html>
IntersectionObserver
: 创建一个IntersectionObserver
实例, 它的构造函数接受一个回调函数, 当被监听的元素进入或离开可视区域时, 就会触发这个回调函数。entries
: 回调函数的第一个参数是一个entries
数组, 包含了所有被监听的元素的信息。isIntersecting
:entry.isIntersecting
属性表示元素是否进入了可视区域。observer.observe(img)
: 开始监听一个元素。observer.unobserve(img)
: 停止监听一个元素。
Intersection Observer API
的优点是性能高, 并且可以设置交叉比例, 非常灵活。 缺点是兼容性不如传统方案, 需要做一些 polyfill。
4. 原生 loading="lazy"
属性
现代浏览器(Chrome 76+, Firefox 75+, Safari 14+)已经支持原生的懒加载属性 loading="lazy"
。 使用起来非常简单, 只需要在 img
标签上添加这个属性即可。
代码示例:
<img src="image1.jpg" loading="lazy" alt="Image 1">
<img src="image2.jpg" loading="lazy" alt="Image 2">
<img src="image3.jpg" loading="lazy" alt="Image 3">
loading="lazy"
: 告诉浏览器对图片进行懒加载。
loading="lazy"
属性的优点是使用简单, 性能好。 缺点是兼容性不如传统方案, 需要做一些降级处理。
三、 性能优化策略
光实现了懒加载还不够, 我们还需要考虑一些性能优化策略, 让懒加载的效果更好。
1. 使用占位图
在图片加载完成之前, 使用一张占位图来填充 img
标签。 占位图可以是:
- 纯色背景: 使用与图片背景色相同的颜色作为占位图。
- 低质量图片: 使用一张非常小的低质量图片作为占位图。
- SVG 图标: 使用一个 SVG 图标作为占位图。
使用占位图可以避免页面闪烁, 提升用户体验。
2. 预加载
对于一些重要的图片, 例如首屏图片, 我们可以进行预加载, 避免用户等待。
预加载的方法有很多, 例如:
<link rel="preload">
: 使用<link rel="preload">
标签来预加载图片。Image
对象: 使用Image
对象来预加载图片。
代码示例:
<link rel="preload" href="image1.jpg" as="image">
const img = new Image();
img.src = 'image1.jpg';
3. 图片压缩
对图片进行压缩可以减少图片的大小, 加快加载速度。 可以使用一些在线图片压缩工具, 例如 TinyPNG。
4. 使用 CDN
使用 CDN 可以将图片缓存到离用户更近的服务器上, 加快加载速度。
5. 避免重绘和回流
在懒加载的过程中, 尽量避免重绘和回流。 例如, 不要频繁地修改 img
标签的样式。
四、 不同方案的对比
为了方便大家选择合适的懒加载方案, 我整理了一个表格, 对比了不同方案的优缺点:
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
offsetTop + innerHeight + pageYOffset |
兼容性好, 实现简单。 | 性能差, 每次滚动都会遍历所有图片。 | 对兼容性要求高的项目, 可以作为兜底方案。 |
函数节流 | 解决了传统方案的性能问题, 减少了 lazyLoad 函数的执行频率。 |
仍然需要遍历所有图片, 只是降低了频率。 | 对性能有一定要求的项目, 可以作为过渡方案。 |
Intersection Observer API |
性能高, 可以设置交叉比例, 非常灵活, 可以精确控制图片的加载时机。 | 兼容性不如传统方案, 需要做一些 polyfill。 | 对性能要求高的项目, 并且可以接受一定兼容性问题的项目。 |
loading="lazy" |
使用简单, 性能好, 不需要编写任何 JavaScript 代码。 | 兼容性不如传统方案, 需要做一些降级处理。 | 对性能要求高的项目, 并且可以接受一定兼容性问题的项目。 |
五、 总结
图片懒加载是前端性能优化中非常重要的一环, 它可以有效地提升页面加载速度, 节省带宽, 提升用户体验。 选择合适的懒加载方案, 并结合一些性能优化策略, 可以让你的网站飞起来!
好了, 今天的讲座就到这里。 希望大家都能掌握图片懒加载的技巧, 写出更高效、更流畅的网站。 下次再见!