如何使用背景图片的懒加载来提高加载时间性能

文章目录

当一个网页包含大量图片时,懒加载技术可以有效地提升页面加载速度。然而,对于背景图片的懒加载,传统方法往往效果不佳。本文将为您深入解析背景图片懒加载的实现方法,从传统到现代的技术改进,让您的网页加载速度更快。

什么是图片懒加载?

懒加载(Lazy Loading)是一种性能优化技术,意味着浏览器只在图片即将进入视口时加载它们。传统上,懒加载技术主要适用于标准的<img>标签,而对于background-image这样的 CSS 样式应用,则需要借助 JavaScript 和更高级的技术来实现懒加载。

传统懒加载的方式和不足之处

在过去,懒加载依赖于 JavaScript 库,例如 LazySizes 和 Lazysizes.js,通过替代图片的src属性来控制图片的加载:

  1. 不设定src属性,而是将图片的 URL 存储在data-src中。
  2. 使用.lazyload类标记需要懒加载的图片。
  3. 使用 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。

这里有一个重要的注意事项,那就是我们通常不想等到一个图片容器在视口内才触发加载。加载图片需要时间,而且延迟对用户是可以感知的。理想的情况是,我们希望在图片容器接近屏幕,可能很快就会出现在视口,但是还没有到的时候,就开始图片的加载过程。为了做到这一点,我们可以使用IntersectionObserverrootMargin。这是一个属性,它使用 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 的图片,也许是为了在桌面屏幕上拉伸而没有提供移动版,这种懒加载技术可以给页面加载速度指标带来显著的改进。


也可以看看