当一个网页包含大量图片时,懒加载技术可以有效地提升页面加载速度。然而,对于背景图片的懒加载,传统方法往往效果不佳。本文将为您深入解析背景图片懒加载的实现方法,从传统到现代的技术改进,让您的网页加载速度更快。
什么是图片懒加载?
懒加载(Lazy Loading)是一种性能优化技术,意味着浏览器只在图片即将进入视口时加载它们。传统上,懒加载技术主要适用于标准的<img>
标签,而对于background-image
这样的 CSS 样式应用,则需要借助 JavaScript 和更高级的技术来实现懒加载。
传统懒加载的方式和不足之处
在过去,懒加载依赖于 JavaScript 库,例如 LazySizes 和 Lazysizes.js,通过替代图片的src
属性来控制图片的加载:
- 不设定
src
属性,而是将图片的 URL 存储在data-src
中。 - 使用
.lazyload
类标记需要懒加载的图片。 - 使用 JS 库绑定滚动事件,当图片接近视口时,将
data-src
转为src
,触发加载。
尽管这种方法可以延迟图片加载,减少初始流量,但由于频繁绑定滚动事件,可能引发性能问题,尤其在移动端设备上负担较大。
loading="lazy"
属性的出现
为了解决 JS 库带来的性能问题,浏览器引入了loading
属性,允许开发者直接在 HTML 中设置懒加载:
<img
src="photo.jpg"
height="450"
width="600"
alt="house photo"
loading="lazy"
/>
loading=lazy
无需额外的 JS 代码支持,让浏览器自动决定图片的加载时机,大幅降低页面的加载负担。但是,loading=lazy
仅适用于<img>
标签,不支持 CSS 声明的背景图片。
那么,如果图片是在 css 中无条件加载的,又该如何进行懒加载呢?
<div class="header-container">
<h2>blog.axiaoxin.com</h2>
</div>
<style>
.header-container {
background-image: url("bg.png");
}
</style>
loading=lazy
对于图片标签的 src 值很好用,但是背景图片呢?它们通常是通过background-image:url()
这样的 css 规则来声明的,目前还不能被loading
属性控制。我们怎么才能懒加载它们呢?
使用 IntersectionObserver 实现图片懒加载
Intersection Observer API 是现代浏览器提供的接口,可以高效地检测元素是否接近视口,从而触发加载行为。相较于滚动事件,它能更精准地判断何时加载图片,同时也提供了 rootMargin 属性,让我们在图片接近视口 100px 时就加载,避免延迟显示。
这个基于 JS 的对象类似于以前那些绑定到滚动事件的 JS 插件。它仍然经常被用作loading=lazy
策略上面提到的一个替代实现。由于 IntersectionObserver 是直接在浏览器级别实现的,它比挂在滚动监听器上更有效率,但是概念上做的是同样的事情 - 等到元素 X 在视口内或者接近视口,然后做动作 Y。
<!-- 在不支持loading="lazy"的浏览器中懒加载图片 -->
<img data-src="image.jpg" class="lazyload" />
<script>
function handleIntersection(entries) {
entries.map((entry) => {
if (entry.isIntersecting) {
// 元素已经穿过了我们的观察
// 阈值 - 从data-src加载src
entry.target.src = entry.target.dataset.src;
entry.target.classList.remove('lazyload');
// 这个元素的任务完成了 - 不需要再观察它了!
observer.unobserve(entry.target);
}
});
}
const images = document.querySelectorAll('.lazyload');
const observer = new IntersectionObserver(handleIntersection);
images.forEach(image => observer.observe(image));
</script>
上面的代码会在图片进入视口的时候加载它的 src。
这里有一个重要的注意事项,那就是我们通常不想等到一个图片容器在视口内才触发加载。加载图片需要时间,而且延迟对用户是可以感知的。理想的情况是,我们希望在图片容器接近屏幕,可能很快就会出现在视口,但是还没有到的时候,就开始图片的加载过程。为了做到这一点,我们可以使用IntersectionObserver
的rootMargin
。这是一个属性,它使用 CSS 值来有效地扩展容器的大小。
比如说,我们想让一个图片在离视口 100px 的时候开始加载(在任何方向 - 视口上方或者下方)。我们可以通过给我们的 IntersectionObserver 的初始化添加一个rootMargin
值来做到这一点。这个值的作用类似于 CSS 的 padding 或者 margin 指令 - 100px 100px 100px 100px
可以压缩成一个值100px
:
const observer = new IntersectionObserver(
handleIntersection,
{ rootMargin: "100px" }
);
我们怎么把这个应用到我们的背景图片问题上呢?
背景图片懒加载
我们来看一个 div,它有一个背景图片bg.png
。我们可以做的是把这个 div 的 CSS 分成两个类 - 一个包含除了背景图片以外的所有属性(.header-container
),另一个只包含背景图片(.header-bg-img
)。
<div class="header-container">
<h2>blog.axiaoxin.com</h2>
</div>
<style>
.header-container {
height: 150px;
width: 300px;
font-weight: bold;
background-size:cover;
}
h2 {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
text-shadow: 2px 2px white;
transform: translate(-50%, -50%);
font-weight: bold;
}
.header-bg-img {
background-image: url('bg.png');
}
</style>
当交叉观察器交叉的时候:
- 给我们的主 div 添加 bg-image 类。这会触发背景图片的加载。
- 移除观察器。这可以防止重复地操作 DOM。
if (entry.isIntersecting) {
// 添加有背景图片的类
entry.target.classList.add('header-bg-img');
// 这个元素的任务完成了 - 不需要再观察它了!
observer.unobserve(entry.target);
}
这个方法非常好用。但是在很多实现中,我们经常会有动态的背景图片,而且事先不知道它们,所以不能把它们添加到我们的打包好的 CSS 文件中。一个例子是为不同的 div 设置不同的背景图。有没有其他的方法呢?
使用数据属性来懒加载背景图片
我们可以通过使用data-
属性来支持一个更动态的分配背景图片的方法。这样我们就可以不用动我们的打包好的 CSS 文件,而是把任何需要的背景图片作为数据属性设置在相关的元素上。这改变了我们的过程:
- 给 div 添加一个数据属性,比如 data-bgimage=x.png。
- 使用一个交叉观察器,当这个 div 穿过我们的阈值的时候,把这个 data-bgimage 的值设置为 background-image:url()的值。
- 整理 - 取消观察已经应用了背景的元素。
<div class="header-container" data-bgimage="bg.png">
<h2>blog.axiaoxin.com</h2>
</div>
<style>
.header-container {
height: 150px;
width: 300px;
font-weight: bold;
background-size:cover;
}
h2 {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
text-shadow: 2px 2px white;
transform: translate(-50%, -50%);
font-weight: bold;
}
</style>
<script>
// 检查是否支持IntersectionObserver
if ('IntersectionObserver' in window) {
document.addEventListener("DOMContentLoaded", function() {
function handleIntersection(entries) {
entries.map((entry) => {
if (entry.isIntersecting) {
// 元素已经穿过了我们的观察
// 阈值 - 从data-src加载src
entry.target.style.backgroundImage = "url('"+entry.target.dataset.bgimage+"')";
// 这个元素的任务完成了 - 不需要再观察它了!
observer.unobserve(entry.target);
}
});
}
const headers = document.querySelectorAll('.header-container');
const observer = new IntersectionObserver(
handleIntersection,
{ rootMargin: "100px" }
);
headers.forEach(header => observer.observe(header));
});
} else {
// 没有交互支持?自动加载所有背景图片
const headers = document.querySelectorAll('.header-container');
headers.forEach(header => {
header.style.backgroundImage = "url('"+header.dataset.bgimage+"')";
});
}
</script>
通过这种方法,我们可以懒加载背景图片,确保我们的初始页面加载快速且性能良好,同时不影响用户体验。
由于背景图片通常是比较大的,hero-style 的图片,也许是为了在桌面屏幕上拉伸而没有提供移动版,这种懒加载技术可以给页面加载速度指标带来显著的改进。