一、图片对网页性能优化的重要性
对电商网页的性能而言,图片优化是至关重要的事情,一个典型的电商网页加载的图片无论从数量还是字节数都不容小觑。
而图片优化的思路非常清晰明了,常见的有三个方向:
- 提前首屏图片的加载时机
- 降低加载图片的体积
- 减少加载图片的数量
随着图片压缩技术和浏览器渲染技术的发展,既淘汰了很多过时的图片性能优化技巧,又应运而生了不少简单、可靠的图片优化手段。
二、提前首屏图片的加载时机
一般首屏使用的图片决定了页面的 LCP[1]指标,首屏图片的加载优先级至关重要,而尽可能提前加载图片是图片性能优化的首要问题。
2.1 优化网络建连
在用户首次访问居多的场景,网络建连时间是一个被大部分人忽略,但至关重要的因素,也许我们的性能优化输在了起跑线上,这部分介绍的内容不止对图片加载有效,对于所有静态资源乃至 HTML、异步接口等道理相似。
使用 CDN
CDN 的重要性不用赘述,将内容缓存到离用户更近的边缘服务器上,可以显著提升网络建连效率,当然更重要的是使用 CDN 减少了数据在用户和服务器之间传输的距离,大幅提升资源下载速度。
预连接
HTML 默认支持两种预建连机制:
- dns-prefetch:用来提前解析外部域名的 DNS 查询。当浏览器知道将来可能会从某个特定的外部域名请求资源时,使用 dns-prefetch 可以减少等待 DNS 查询的时间;
- preconnect:不仅会告诉浏览器提前进行 DNS 查询,还会提前建立到资源服务器的 TCP 连接,并在使用 HTTPS 的情况下执行 TLS 握手。这意味着当浏览器实际请求资源时,它已经有了一个开放的连接,可以立即开始数据传输;
<head> <link rel="dns-prefetch" href="https://examplecdn.com"> <link rel="preconnect" href="https://examplecdn.com"> </head>
收敛域名
在 HTTP/1.1 协议下,由于浏览器通常会对每个域名并行连接数的限制(大部分浏览器限制在6个左右),在多个域名间分散图片曾经是常见的优化手段,以此突破对单一域名的并发限制。然而这也意味着对于每个新的域名,浏览器必须进行额外的 DNS 查找,并且可能需要建立新的TCP连接,这可能会增加一定的延迟。
HTTP/2 开始支持多路复用,意味着多个请求可以在单个TCP连接上同时进行,减少了对多个域名的需要。因此在 HTTP/2 环境中,收敛图片域名反而可以优化图片加载,因为:
- 减少DNS查询:减少域名数量可以减少DNS查询次数,因为浏览器需要为每个新的域名解析IP地址。
- 减少连接建立:多路复用使得在一个连接上并行发送请求成为可能,因此减少域名可以减少连接建立的次数和相应的延迟。
- 提高TLS效率:对于HTTPS连接,收敛域名意味着可以在较少的TLS握手上复用连接,TLS握手是创建安全连接所必需的步骤,它在总体连接时间中占有显著份额。
- 缓存效率:使用较少的域名可以提高缓存效率,因为浏览器可能会为每个域名分别维护缓存记录。
升级 HTTP/3
HTTP/3 是下一代 HTTP 协议,基于 QUIC(Quick UDP Internet Connections)协议。QUIC 是由 Google 开发并随后由 IETF 标准化的传输层协议。HTTP/3 对网络建连进行了优化,和建连、传输性能相关的主要有
- 减少连接建立时间:HTTP/2 基于 TCP 和 TLS,需要多个往返时间(RTT)来完成握手。HTTP/3 使用 QUIC 协议,它将加密和传输合并为一个过程,允许在一个 RTT 完成连接建立,在最佳情况下甚至可以在零 RTT 中恢复会话。
- 多路复用无阻塞:HTTP/2 虽然支持多路复用,但 TCP 层的队头阻塞问题仍然存在。HTTP/3 通过 QUIC 改进的多路复用能力,在 QUIC 中由于是基于数据报的 UDP,独立的流可以在其他流发生丢包时继续传输,解决了 TCP 的队头阻塞问题。
- 快速丢包恢复和拥塞控制:QUIC 实现了更快速的丢包恢复机制。TCP 需要等待一段时间来确认丢包,而 QUIC 可以利用更精细的确认机制来迅速响应丢包情况,并相应调整拥塞控制策略。
- 连接迁移:QUIC 支持连接迁移,允许客户端在网络环境变化(如从 Wi-Fi 切换到移动网络)时,保持现有的连接状态。在 HTTP/2 这种情况通常会导致连接中断和需要重新建连。
2.2 流式渲染 preload
很多页面为了性能优化引入了 SSR 技术,这样 HTML 请求发起后,页面组建在服务器进行渲染,完成后返回给客户端。如果没有配合流式渲染,会让页面等待服务器取数、渲染出现较长时间的白屏。
流式渲染通过 HTTP 1.1 引入分块传输 Transfer-Encoding: chunked 特性,允许一个 HTTP 的请求的连接中可以多次响应,在 SSR 的场景中,服务端在响应一个 HTML 页面的请求时至少可以拆分成两个分块。
- 头部静态内容:页面 CSS、JavaScript、字体文件等;
- 后续动态渲染内容;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>流式渲染优化页面性能</title> <link rel="preload" href="页面LCP图片地址" as="image" /> <link rel='dns-prefetch' href='https://s.alicdn.com'> <link rel='preconnect' href='https://i.alicdn.com'> <link rel="stylesheet" href="页面样式地址"> <script src="页面脚本地址"></script> </head> <body> <!--骨骼图--> <!--流式渲染后续内容--> </body> </html>
在流式渲染首段返回内容中可以通过 preload 让页面提前加载首屏确定性的图片,提升页面图片加载速度。当然流式渲染不仅仅可以优化图片加载,充分利用服务器计算时间,页面可以对部分域名提前建连、提前加载页面 CSS 和 JavaScript、加载骨骼图,等手段优化页面性能。
如果使用的 CDN 厂商支持边缘计算,可以将页面静态部分换存在 CDN,用户请求时第一时间返回,同时 CDN 向源站请求页面后续动态内容,来进一步提升网页性能。
前端性能优化:当页面渲染遇上边缘计算-阿里云开发者社区[2]。
2.3 fetch-priority
在 web 开发中资源的加载顺序对页面的性能有显著影响。浏览器通常会根据资源类型、它们在HTML文档中的位置以及一些内部算法来决定资源加载的优先级。然而浏览器的默认优先级可能并不总是与开发者的意图或页面性能最优化的目标一致。
fetch-priority 特性就是为了解决这个问题而提出的。通过显式地设置资源的fetch-priority 属性,开发者可以指示浏览器按照特定的优先级顺序加载资源。一般情况下图片的加载优先级相对较高,但为了更精准控制,可以使用 fetch-priority 调整。
<img src="important-image.png" fetch-priority="high" alt="Important Image"> <img src="less-important-image.png" fetch-priority="low" alt="Less Important Image">
fetch-priority 属性可以设置不同的优先级值,high、low 和 auto(默认)。可以应用于各种资源,如<img>、<link>、<script>等元素。目前 Chrome、Safari、Edge 均已支持。
三、降低加载图片的体积
在保证清晰度满足要求的前提下,减少图片的字节数明显可以改善图片加载性能。
3.1 图片字节数的构成
图像的尺寸可以表示为横向像素数×纵向像素数,图像的总像素数(即分辨率)是横向像素数和纵向像素数的乘积。例如,一个1920×1080的图像含有2,073,600个像素点,通常称为二百万像素。决定图片字节数的有几个关键因素。
- 分辨率:也就是我们常说的像素,表示图片在 width 和 height 分别有多少个点,16x16 表示图片一共有 256 个像素点;
- 色深:每个像素能够显示的颜色数量。常见的色深包括8位(256色)、16位(65,536色)、24位(约1677万色,也称为真彩色)等。色深越高,每个像素需要的位数(bits)就越多;
- 图片格式与压缩算法:图像格式决定了图片的存储和压缩方式。常见的格式包括JPEG(有损压缩)、PNG(无损压缩)、GIF(无损压缩,但限制为256色)、BMP(通常无压缩)、WEBP(支持有损和无损压缩)等。不同的压缩算法会导致不同的文件大小;
- 文件头信息和元数据:图片文件通常包括一个文件头,其中包含了文件类型、尺寸、色深、压缩类型等基本信息。元数据包括拍摄信息(如ISO、快门速度、光圈)、版权信息、编辑历史、ICC配置文件等;
显然图片格式、分辨率可以显著影响图片的字节数。
3.2 图片缩放、裁剪、压缩
根据显示场景不同,调整图片的尺寸、分辨率、质量可以改变图片的字节数,最常见的方法就是:
- 对图片进行剪裁:丢弃图片不会显示的部分,让图片的尺寸变小,不牺牲图片质量。
- 调整图片的分辨率:类似 800x800 改成 400x400,过程中会对图片进行重新采样、像素去处或合并,一些像素会被去除,或者原来独立的多个像素会被合并成一个新的像素。
- 降低图片质量:当降低图片质量时,压缩算法会更激进地减少图片中的数据量,包括减少颜色的数量、合并类似颜色或模糊细节等,因为存储的数据更少了,文件大小会显著减少。
设计师、开发可以通过工具实现对图片的调整,但成本略高,比较简单的做法是让源站或者 CDN 可以根据图片 URL 参数对图片进行处理。阿里云目前具备完整的图片处理能力
有了图片裁剪、缩放能力,在必要的时候可以响应式加载图片:
screen and (min-width: 1200px) { img { background-image: url('a.png?image_process=resize,fw_200,fh_200.jpg'); } } screen and (min-width: 1400px) { img { background-image: url('a.png?image_process=resize,fw_250,fh_250.jpg'); } }
也可以使用 HTML5 的 picture 标签:
<picture> <source srcset="a.png?image_process=resize,fw_200,fh_200.jpg" media="(min-width: 1200px)" /> <source srcset="a.png?image_process=resize,fw_250,fh_250.jpg" media="(min-width: 1400px)" /> <img src="a.png?image_process=resize,fw_100,fh_100.jpg" /> </picture>
甚至可以每次用户加载页面,根据用户的性能表现进行快慢网分级,并记录到图片域名的 cookie 中。下次用户发起图片请求,CDN 可以根据 cookie 中的快慢网信息,决定返回给用户的图片质量。
3.3 选择合适的图片格式
大部分 Web 开发者对 WebP 格式非常熟悉了,但可能对 AVIF 格式还没有开始应用。AVIF 是一种基于 AV1 视频编码的新图像格式,用于将AV1压缩的图片或图片序列存储为HEIF文件格式。相对于JPEG,WEBP 这类图片格式来说,它的压缩率更高,并且画面细节更好,AVIF vs JPEG 大小节省约 50%,AVIF vs WebP 大小节省约 20%。
Comparing AVIF vs WebP file sizes at the same DSSIM
以 JPEG 做基点总体来看,AVIF全面领先,甚至是边界条件下,也表现较好。而 WebP 边界条件下可能会超过 JEPG。
类型 |
50分位数压缩率 |
85分位数压缩率 |
WebP |
-30% |
-20% |
AVIF |
-50% |
-40% |
主流浏览器的支持情况非常不错,唯一的遗憾是 Edge 还不支持。
浏览器在在其图片请求时候会在 Accept 头部信息中声明支持的图片格式,可以利用这个在 CDN 识别,使用相同的图片地址,返回不同格式的图片内容。
避免前端加载 1px 透明图判断浏览器是否支持特定图片格式,然后修改图片 URL 来获取对应格式图片。这样的处理方式有两个弊端:
- 发起图片请求依赖前端格式判断的异步过程,请求时机被推迟;
- 使用新格式的图片包括后期的调整等,需要修改前端代码;
在 Chrome Dev Tools 网络面板中可以看到淘宝、京东等网站都已经开始使用 AVIF 格式图片。
3.4 堪称双刃剑的渐进式加载
图片的渐进式加载是一种在网页浏览过程中逐步显示图片的技术。图片没有完全下载前用户先看到图片的低质量版本,然后图片会逐渐变得更清晰,直到完全加载完成。一般有两种做法:
- 使用支持渐进式的图片格式:PJPEG 或者渐进式 WebP 都原生支持渐进式加载;
- 使用小图占位,然后替换为大图;
图片渐进式加载效果类似于加强版的骨骼图,然而渐进式加载也有几个问题
- 用户体验:渐进式图片虽然可以让用户更快看到内容,但是模糊的图片也可能导致用户感到困惑,不同用户对模糊图片的感受不同,在图片加载时间较长情况下甚至会理解为网页故障;
- 性能开销:有限的图片格式或者加载多个图片,两种渐进式加载的方案都会增加图片体积,还会带来了文件处理、编码复杂性等开销。尤其是使用小图占位,前端替换的方案可能还会带来 LCP 指标的恶化;
To be or not to be, that is the question.
四、减少加载图片数量
4.1 CSS sprites 可能过时了
CSS sprites 将多个小图像合并成一个大图像,利用 CSS 的背景定位属性,可以仅显示合并图像中相应的部分,来代替单独的图像文件。减少HTTP请求的数量,这在HTTP/1.1时代是提升页面加载速度的常用方法。
然而在 HTTP/2 情况发生了变化,HTTP/2 引入多路复用、头部压缩等特性,显著改善了同时发送多个请求的性能。多路复用允许多个请求通过单一的TCP连接并行传输,减少了由于建立多个连接而产生的延迟。因此在HTTP/2 环境下,CSS sprites 的性能优势不如HTTP/1.1时那么明显,甚至可能产生反效果,因为:
- 缓存效率: 如果 sprite 图中某个图像发生变化,即使其他图像没有改变,整个sprite图都需要重新下载和缓存,导致缓存失效;
- 过度下载:当页面只需要 sprite 图中的几个图像时,仍然需要下载整个合并的图像,这可能导致不必要的数据传输;
- 渲染性能:大型的 sprite 图可能对浏览器的渲染性能产生影响,尤其是在移动设备上,因为需要更多的 CPU和内存去处理大图像的解码、背景定位;
同时 CSS sprites 需要额外的维护工作,每当图像发生变化时,都需要重新生成整个sprite图,并更新CSS定位,这使得管理起来更加复杂。在 HTTP/2 时代 CSS sprites 可能不再是性能优化的最佳方案,icon fonts、base64 或 SVG 图像可能是更好的选择。
4.2 load="lazy" 不依赖 JavaScript 的懒加载
在图片较多的场景通常会对非首屏图片懒加载,一般通过 JavaScript 实现,现在大部分主流浏览器通过load="lazy"原生支持了图片懒加载,使用方法也非常简便。
<img src="image-to-lazy-load.jpg" loading="lazy">
这个属性有三个可能的值:
1.lazy:启用懒加载。浏览器会在图片即将进入视口时才开始加载。2.eager:禁用懒加载。图片会随着页面加载立即开始加载,无论图片位置如何。
3.auto:浏览器自行决定何时加载图片,这是默认值。
当对图片设置了这个属性后,浏览器会根据自己的启发式算法决定图片的加载时机。这些算法会考虑多个因素,比如图片即将进入视口的距离,或者用户当前的网络条件等。通常启发式算法的工作方式如下:
- 视口接近度:浏览器会监测页面滚动,检查懒加载图片距离视口的距离。当图片快要出现在视口内时,浏览器会开始加载图片。具体开始加载图片的距离阈值并没有统一的标准,不同的浏览器可能会有不同的实现。
- 网络状况:一些浏览器可能会根据用户的网络状况(例如是否使用数据流量或者Wi-Fi)来决定是否提前加载图片。
- CPU和内存使用情况:如果用户设备的CPU或内存使用率很高,浏览器可能会延迟加载图片,直到资源使用减少。
- 电池状态:对于移动设备,浏览器可能会在电池电量充足时更积极地加载资源。
虽然开发者无法精准控制图片加载的时机,但浏览器原生支持考虑的因素不仅仅是滚动位置,相对而言更加合理。顺便说一句,使用 JavaScript 懒加载本身也有性能开销,可能会影响到页面的 FPS。
4.3 content-visibility 另外一种懒加载
content-visibility 是 CSS 属性,允许浏览器跳过不在屏幕上的元素的渲染工作,直到用户滚动到它们的位置。通过跳过不可见内容的渲染,content-visibility 可以显著减少页面的初始加载时间,并降低内存的使用,从而改善用户体验。配合 contain-intrinsic-size 属性可以对容器进行渲染前的占位。
<style> .image-gallery { content-visibility: auto; contain-intrinsic-size: 1000px 500px; /* 设置一个合适的占位大小 */ } </style> <div class="image-gallery"> <img src="image1.jpg" alt="描述1"> <img src="image2.jpg" alt="描述2"> <!-- 更多图片 --> </div>
content-visibility 的浏览器兼容性并不是非常乐观,需要开发者在使用时候加以判断。
4.4 decoding="async" 非首屏图片异步解码
解码图像和视频是计算密集型的操作,可能会占用大量的CPU资源,特别是对于高分辨率或者复杂编码格式的媒体文件,如果主线程被图像或视频的解码操作阻塞,用户在滚动页面或尝试交互时可能会感受到卡顿或延迟。
对非首屏图片或视频添加 decoding="async" 可以允许浏览器在后台处理图片、视频解码,而不阻塞主线程,继续处理和渲染页面的其余部分,这样可以有助于改善页面的加载性能,减少用户感知到的延迟,并提供更加平滑的用户体验。
<img src="image.jpg" decoding="async">
参考链接:
[1]https://web.dev/articles/lcp?hl=zh-cn
[2]https://developer.aliyun.com/article/762599
来源 | 阿里云开发者公众号
作者 | 谦行