[Day 30] Tips for Lazy Loading Images

Lazy loading 是广为人知的网页优化技巧,尤其应用在图片上时能够大幅减少效能和流量的浪费,目前也有许多图片 Lazy loading 的套件可以使用,甚至已经有浏览器内建图片 Lazy loading 功能,不过在使用这些技巧的同时,其实隐藏了一些小陷阱,本文整理了应用图片 Lazy loading 时需要注意的重点。

 

浏览器内建 Lazy Loading

除了 IE 和 Safari 之外,主流的浏览器已经内建了图片 Lazy loading 功能,不需要任何 JavaScript、CSS,同时也省去了额外处理无法执行 JavaScript 的环境,只需要在 img 加上 loading 属性好了:

<img src="image.png" loading="lazy" alt="..." />

loading 属性

  • auto – 和没放一样,使用浏览器预设的载入行为
  • lazy – 一开始就在 Viewport 内或是靠近 Viewport 时开始载入
  • eager – 无论图片位置,马上开始载入

 

避免 CLS 影响使用者体验

所有 Lazy loading 的图片都必须用 Placeholder 撑开图片载入完时所需的空间,避免载入完成时影响网页的排版,如果事先知道图片大小,可以直接在 img 元素加上 widthheight 属性,在图片另外包一层 Container 来确保图片显示的空间也是常见的作法。

<img src="image.png" alt="" width="200" height="200" />
<img src="image.png" alt="" style="width: 200px; height: 200px;" />
<div class="img-container">
  <img src="image.png" alt="" />
</div>

关於 CLS 和其他网页体验指标的介绍可以参考 Web Vitals

 

避免 Lazy loading 所有图片

使用 Lazy loading 就必须等到浏览器第一次转译完毕,判断图片位置後才能决定是否载入图片,也就是说在转译完成前可能会有流量的空窗期,应该善用这段时间开始载入必要的图片。

Header 内的图片、商标、文章首图等等网页转译完成就会映入眼帘的图片,都不应该进行 Lazy loading,另外当网页长度较短,或是有虽然看不到但非常靠近 Viewport 的图片也不适合使用 Lazy loading,应该让浏览器在解析到 imgpicture 元素时就能够直接开始下载图片。

 

准备 Placeholder

为了避免使用者在图片还没载入完成时看到空空的区块,加入好看的 Placehodler 能够提升使用者体验,最简单的方式就是加入灰色背景,并在中间放一个小图示,但其实还有更好的做法。

代表色

可以根据图片的代表色来当作图片的 Placeholder,以 Pinterest 为例:

Low Quality Image Placeholder (LQIP)

以原图的超小版本放大当作 Placeholder,比起单色看起来比较有趣,且可以从 Placeholder 中看出一些图片的内容,以 Medium 为例:

SQIP

除了直接把图片变小外,SQIP 套件提供了其他 Placeholder 的制作方式,从左到右分别是原图、LQIP、SQIP 预设、SQIP pixels、SQIP art:

我很喜欢最右边的 SQIP art,是用 SVG 组成的。

 

提早载入

当使用者网速较慢或是移动页面的速度较快时,如果在图片进入 Viewport 时才开始载入图片会让使用者看到看到还没读取完毕的图片,因此一般在进行图片 Lazy loading 时会加入 Margin,例如图片在 Viewport 外 1000px 范围内就开始载入,减少使用者的等待时间。

在 Chrome 中使用内建的 loading 属性就有提早载入图片的机制,例如在 4G 连线状态,只要图片进入 Viewport 外 1250px 范围内就会开始载入,3G 则是 2500px,根据 Google 的研究平均载入等待时间会在 10 毫秒以下。

 

处理不能执行 JavaScript 的情况

当页面中的图片都使用 JavaSciprt 进行 Lazy loading 时,遇到不能执行 JavaScript 的环境就无法触发图片载入,常见的解决方式是把图片放入 noscript 元素,只有不能执行 JavaScript 时才会转译 noscript 的内容,但这又牵涉到一个问题,如果已经帮图片准备 Placeholder 就会意外载入两张图片,例如以下程序码:

<img class="lazy" src="placeholder.jpg" data-src="image.jpg" alt="..." />
<noscript>
  <img src="image.jpg" alt="..." />
</noscript>

可以用一种简单的解决方式,先隐藏所有 Lazy loading 的图片,并在一开始手动显示,如果不能执行 JavaScript 的话,这些图片就不会显示,可以执行 JavaScript 的状况下 noscript 的内容也不会被转译出来。

<html class="no-js">
.no-js .lazy {
  display: none;
}

页面载入後执行 JavaScript:

document.documentElement.classList.remove("no-js");

 

CSS background

目前浏览器内建的图片 Lazy loading 还不支援 CSS background-image,需要使用套件或是另外处理,CSS background-image 只要被放入 Render tree 就会开始载入,反过来说,如果没被放进 Render tree,就算把图片来源写在 CSS 中也不会触发载入。

.lazy-background {
  background-image: url("placeholder.jpg"); 
}

.lazy-background.visible {
  background-image: url("image.jpg");
}

如此一来就能在元素快要进入 Viewport 时加上 visible Class 来切换 background-image 的值,该 CSS 被放进 Render tree 时就会开始载入,修改 Styles 的时机和逻辑就和一般 img Lazy loading 差不多,可以透过 Scroll event、Intersection Observer 等方式完成。

关於 Render tree 和浏览器的转译流程,可以参考 How Rendering Works

 

错误处理

只要有经过网路,就一定有出错的可能,例如重新部署网页时有使用者正在浏览网页,还没 Lazy loading 的图片可能会因为网址改变了无法正确载入图片,或是单纯因为 Server 炸了、流量过大等导致 Server 无法正常回应图片,使用者就可能会看到以下图示。

可以利用通知、显示错误讯息等方式告知使用者现在发生了什麽事,当图片是网页中非常重要的内容时可以提供使用者重新载入图片的按钮等等错误复原机制,这些方式都能提升不少使用者体验。

 

非同步解码

在 DOM 内动态插入图片时,需要在主线程上经过解码(Decode)才能绘制出来,当图片较小时不太需要在意,但图片较大时所需的解码时间就比较长,很有可能会在显示图片前让整个画面卡住一下,为了确保插入图片时不影响画面流畅度,可以在插入前先把解码的部分做完,避免造成延迟:

const img = new Image();
img.src = 'large-image.jpg';
img.decode()
  .then(() => {
    imageContainer.appendChild(img);
  })

 

测试

无论是自己来、用套件、浏览器内建图片 Lazy loading,开发完成後都务必在真实环境测试图片的载入行为是否符合预期,在浏览器中可以用无痕模式、降低网速和右键清除快取来模拟第一次进入页面的状况。

此外也可以利用 Lighthouse 来检查是否有漏掉或是可以再进行其他优化的图片。

 

相关套件

参考资料

https://web.dev/lazy-loading-images/
https://web.dev/lazy-loading-best-practices/
https://web.dev/preload-responsive-images/
https://css-tricks.com/the-complete-guide-to-lazy-loading-images/


<<:  Day29 人物骨架 - 瞄准动作篇

>>:  Day 28 - 不想再写 if 啦,来研究 NestJS 的 Pipe 看看

Day14-TypeScript(TS)使用成员存取修饰词(Access Modifier)

今天要来介绍TypeScript(TS)使用成员存取修饰词(Access Modifier), 控制...

第 28 集:Bootstrap 客制化 component 元件样式

此篇会介绍如何修改 Bootstrap 元件样式。 事前准备 须先了解变数设置、通用类别设置,再继...

[DAY04] 建立 Datastore 和 Dataset (下)

DAY04 建立 Datastore 和 Dataset (下) 今天我们就要把昨天建立好的 dat...

Day1 javascript简单介绍

JavaScript 是 Web 的编程语言,几乎所有现代的 HTML 页面都会使用到 JavaSc...

30天学会C语言: Day 27-指标当参数

函式没办法使用其他函式中的变数 #include <stdio.h> #include ...