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

文章目录

图片的懒加载意味着浏览器只在需要的时候才加载图片。在有很多图片的网页上,有效地使用懒加载可以显著地减少初始加载时间。但是背景图片呢?它们不是像标准的图片标签那样被加载的,所以我们经常会看到有大背景图片的网页在性能上有损失。有没有简单的方法来避免这个问题呢?

懒加载 - 以前和现在的对比

图片的懒加载在过去的多年里已经被许多不同的JS库实现了。通常的做法是:

  • 不要设置图片标签的src值,而是把图片的url添加到data-src值里。
  • 给每个图片标签加一个类来表示它是一个懒加载的候选者,比如class=“lazyload”。
  • 附加一个JS库,它会绑定到滚动事件,并检查当一个img.lazyload出现在用户当前的滚动位置附近的时候。
  • 当它出现的时候,把data-src的值换到src属性里,触发图片的加载。
  • 移除lazyload类,这样这个元素就不会在用户继续滚动的时候被重新加载。

这种方法很受欢迎,但是在性能方面经常有很大的问题。绑定到滚动事件是非常影响性能的,Chrome现在会在每次看到这种情况的时候主动警告。

引入loading=lazy

loading属性把这个工作从JS插件的手中拿走,让浏览器来优化它。支持度很广泛,意味着任何有这个属性设置为lazy的图片都不会被加载,直到它接近视口。加载被触发的视口距离由浏览器来处理,并且包含了像用户的连接速度这样的信号:

On fast connections (4G), we reduced Chrome’s distance-from-viewport thresholds from 3000px to 1250px and on slower connections (3G or lower), changed the threshold from 4000px to 2500px.

在快速的连接(4G)上,我们把Chrome的视口距离阈值从3000px降低到1250px,而在慢速的连接(3G或更低)上,把阈值从4000px降低到2500px。

当正确地应用这个属性的时候,它可以对性能产生惊人的影响。通过推迟图片的加载,初始页面加载时间可以大幅度地减少。重要的注意事项是,这个属性不是应用在页面上的所有图片上 - 把视口上方的图片懒加载会损害用户体验,以及核心网络指标。

<img src="photo.jpg" height="450" width="600" alt="house photo"
    loading="lazy"
    />

把这个技术应用到一个图片很多的网站上,我们就能看到在初始渲染所需的数据量上有很大的改进。

在每个视口外的图片标签上添加一个loading="lazy"属性后,首次加载所需的数据量上会有很大的减少!

这导致更快的初始加载,后面的图片只有在用户滚动的时候才被加载。

这是一个很大的改进!但是如果图片是在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

这个基于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 的图片,也许是为了在桌面屏幕上拉伸而没有提供移动版,这种懒加载技术可以给页面加载速度指标带来显著的改进。


也可以看看


全国大流量卡免费领

19元月租ㆍ超值优惠ㆍ长期套餐ㆍ免费包邮ㆍ官方正品