开发在任何地方都能快速加载的网站可能是一项艰巨的任务。设备功能的多样性以及设备所连接网络的质量,可能会让这项任务看起来难以完成。虽然我们可以利用浏览器功能来提高加载性能,但我们如何知道用户设备的功能或网络连接的质量?解决方案是客户端提示!
客户端提示是一组选择性启用的 HTTP 请求标头,可让我们深入了解用户设备和所连接网络的这些方面。通过在服务器端利用这些信息,我们可以根据设备和/或网络条件更改内容传送方式。这有助于我们打造更具包容性的用户体验
一切都与内容协商有关
客户端提示是另一种内容协商方法,即根据浏览器请求标头更改内容响应。
内容协商的一个示例涉及 Accept 请求标头。它描述了浏览器可以理解的内容类型,服务器可以使用这些类型来协商响应。对于图片请求,Chrome 的 Accept 标头内容如下:
Accept: image/webp,image/apng,image/*,*/*;q=0.8
虽然所有浏览器都支持 JPEG、PNG 和 GIF 等图片格式,但在此示例中,Accept 标头表明浏览器还支持 WebP 和 APNG。利用这些信息,我们可以为每个浏览器协商最佳图片类型:
<?php
// Check Accept for an "image/webp" substring.
$webp = stristr($_SERVER["HTTP_ACCEPT"], "image/webp") !== false ? true : false;
// Set the image URL based on the browser's WebP support status.
$imageFile = $webp ? "whats-up.webp" : "whats-up.jpg";
?>
<img src="<?php echo($imageFile); ?>" alt="I'm an image!">
与 Accept 类似,客户端提示是另一种内容协商途径,但它是在设备功能和网络条件的背景下进行的。借助客户端提示,我们可以根据用户的个人体验做出服务器端性能决策,例如决定是否应向网络条件不佳的用户提供非关键资源。在本指南中,我们将介绍所有可用的提示,以及如何使用这些提示让内容传送更符合用户的需求。
选择启用
与 Accept 标头不同,客户端提示不会凭空出现(Save-Data 除外,我们稍后会讨论)。为了尽可能减少请求标头,您需要在用户请求资源时发送 Accept-CH 标头,以选择要接收的客户端提示:
Accept-CH: Viewport-Width, Downlink
Accept-CH 的值是一个逗号分隔列表,其中包含网站在确定后续资源请求的结果时将使用的请求提示。当客户端读取此标头时,它会收到“此网站需要 Viewport-Width 和 Downlink 客户端提示”的消息。不用担心具体的提示本身。我们稍后会介绍这些内容。
您可以使用任何后端语言设置这些选择启用标头。例如,可以使用 PHP 的 header 函数。
您甚至可以使用 <meta> 标记上的 http-equiv 属性设置这些选择启用标头:
<meta http-equiv="Accept-CH" content="Viewport-Width, Downlink" />
所有客户端提示!
客户端提示描述了以下两项内容之一:用户使用的设备,以及他们用于访问您网站的网络。我们来简要介绍一下所有可用的提示。
设备提示
一些客户端提示会描述用户设备的特征,通常是屏幕特征。其中一些可以帮助您为特定用户的屏幕选择最佳媒体资源,但并非所有这些资源都一定以媒体为中心。
在查看此列表之前,了解一些用于描述屏幕和媒体分辨率的关键术语可能会有所帮助:
固有尺寸:媒体资源的实际尺寸。例如,如果您在 Photoshop 中打开一张图片,图片大小对话框中显示的尺寸描述的就是其固有尺寸。
密度校正后的固有尺寸:媒体资源在经过像素密度校正后的尺寸。它是图片的固有尺寸除以设备像素比。 例如,假设有以下标记:
<img
src="whats-up-1x.png"
srcset="whats-up-2x.png 2x, whats-up-1x.png 1x"
alt="I'm that image you wanted."
/>
假设在本例中,1x 图片的固有尺寸为 320x240,2x 图片的固有尺寸为 640x480。如果此标记由安装在设备像素比为 2 的屏幕设备(例如 Retina 屏幕)上的客户端解析,则会请求 2x 图片。2x 图片的密度校正后固有尺寸为 320x240,因为 640x480 除以 2 等于 320x240。
外在尺寸:媒体资源在应用 CSS 和其他布局因素(例如 width 和 height 属性)后的尺寸。假设您有一个 <img> 元素,它加载的图片的密度校正固有尺寸为 320x240,但它也具有 CSS width 和 height 属性,其值分别为 256px 和 192px。在此示例中,该 <img> 元素的外在尺寸变为 256x192。
width: 256px; 和 height: 192px; 的 CSS 规则会将 320x240 的固有尺寸图片转换为 256x192 的外在尺寸图片。了解了一些术语后,我们来看看您可以使用的设备专用客户端提示列表。
Viewport-Width
Viewport-Width 是用户视口的宽度(以 CSS 像素为单位):
Viewport-Width: 320
此提示可与其他屏幕专用提示搭配使用,以提供针对特定屏幕尺寸进行了优化的不同图片处理方式(即剪裁)(即艺术指导),或省略当前屏幕宽度不需要的资源。
DPR
DPR(设备像素比的缩写)报告用户屏幕的物理像素与 CSS 像素的比率:
DPR: 2
在选择与屏幕像素密度相对应的图片来源时(例如 srcset 属性中的 x 描述符),此提示非常有用。
宽度
Width 提示会出现在由使用 sizes 属性的 <img> 或 <source> 标记触发的图片资源请求中。sizes 会告知浏览器资源的外在尺寸;Width 会使用该外在尺寸来请求具有最适合当前布局的内在尺寸的图片。
例如,假设用户使用 DPR 为 2 的 320 CSS 像素宽屏幕请求页面。设备加载一个文档,该文档包含一个 <img> 元素,该元素具有值为 85vw 的 sizes 属性值(即 85%)。如果已选择启用 Width 提示,客户端会将此 Width 提示发送到服务器,并请求 <img> 的 src:
Width: 544
在这种情况下,客户端会向服务器提示,所请求图片的最佳固有宽度应为视口宽度的 85%(272 像素)乘以屏幕的 DPR (2),即 544 像素。
此提示特别强大,因为它不仅考虑了屏幕的密度校正宽度,还使这一关键信息与布局中图片的外部尺寸保持一致。这样,服务器就有机会协商出最适合屏幕和布局的图片响应。
Content-DPR
虽然您已经知道屏幕有设备像素比,但资源也有自己的像素比。在最简单的资源选择用例中,设备和资源之间的像素比可以相同。但是!如果 DPR 和 Width 标头同时起作用,则资源的外在大小可能会导致两者不一致。这正是 Content-DPR 提示发挥作用的地方。
与其他客户端提示不同,Content-DPR 不是服务器要使用的请求标头,而是每当使用 DPR 和 Width 提示选择资源时,服务器必须发送的响应标头。Content-DPR 的值应为以下等式的结果:
Content-DPR = [所选图片资源大小] / ([Width] / [DPR])
发送 Content-DPR 请求标头后,浏览器就会知道如何根据屏幕的设备像素比和布局来缩放给定图片。否则,图片可能无法正确缩放。
Device-Memory
Device-Memory 实际上是 Device Memory API 的一部分,它会显示当前设备大致的内存量(以 GiB 为单位):
Device-Memory: 2
此提示的一个可能用例是,减少发送到内存有限的设备上的浏览器的 JavaScript 量,因为 JavaScript 是浏览器通常加载的最耗费资源的内容类型。或者,您也可以发送 DPR 较低的图片,因为它们在解码时使用的内存较少。
网络提示
Network Information API 提供了另一类客户端提示,用于描述用户网络连接的性能。在我看来,这些是最有用的一组提示。借助这些功能,我们可以通过更改向连接速度较慢的客户端提供资源的方式,为用户量身定制体验。
RTT
RTT 提示提供应用层上的大致往返时间(以毫秒为单位)。与传输层 RTT 不同,RTT 提示包含服务器处理时间。
RTT: 125
由于延迟时间在加载性能中起着重要作用,因此此提示非常有用。
使用 RTT 提示,我们可以根据网络响应能力做出决策,这有助于加快整个体验的交付速度(例如,通过省略一些请求)。
下行链路
虽然延迟时间对加载性能很重要,但带宽也很有影响力。Downlink 提示以兆位/秒 (Mbps) 为单位,显示用户连接的大致下行速度:
Downlink: 2.5
Downlink 与 RTT 结合使用,可根据网络连接质量更改向用户传送内容的方式。
厄瓜多尔时间
ECT 提示代表有效连接类型。其值是连接类型的枚举列表中的一个值,每个值都描述了RTT 和 Downlink 值在指定范围内的连接。
此标头不会说明实际连接类型,例如,它不会报告您的网关是手机基站还是 Wi-Fi 接入点。而是分析当前连接的延迟时间和带宽,并确定它与哪种网络配置文件最相似。例如,如果您通过 Wi-Fi 连接到慢速网络,则 ECT 可能会填充 2g 值,这是最接近有效连接的近似值:
ECT: 2g
ECT 的有效值包括 4g、3g、2g 和 slow-2g。此提示可用作评估连接质量的起点,随后可使用 RTT 和 Downlink 提示进行优化。
Save-Data
Save-Data 并不是描述网络状况的提示,而是表明网页应发送较少数据的用户偏好设置。
我更倾向于将 Save-Data 归类为网络提示,因为您使用它执行的许多操作都与其他网络提示类似。用户也可能会在高延迟/低带宽环境中启用此功能。此提示(如果存在)始终如下所示:
Save-Data: on
在 Google,我们已经讨论过您可以使用 Save-Data 做些什么。它对性能的影响可能非常深远。这是一种信号,表明用户实际上是在要求您减少发送给他们的内容!如果您能倾听并根据该信号采取行动,用户会非常感激。
总结与回顾
如何使用客户端提示取决于您。由于它们提供的信息非常丰富,因此您有很多选择。为了激发一些灵感,我们来看看客户端提示能为 Sconnie Timber 做些什么。这是一家虚构的木材公司,位于美国中西部偏北的农村地区。正如偏远地区常见的情况一样,网络连接可能不稳定。在这种情况下,客户端提示等技术 可以真正为用户带来不一样的体验
自适应图片
除了最简单的自适应图片用例之外,其他所有用例都可能变得复杂。如果您有多个处理方式和同一图片的不同变体,以适应不同的屏幕尺寸和不同的格式,该怎么办?这种标记会很快变得非常复杂。
很容易出错,也很容易忘记或误解重要概念(例如 sizes)。
虽然 <picture> 和 srcset 无疑是出色的工具,但对于复杂的用例,开发和维护它们可能非常耗时。我们可以自动生成标记,但这样做也很困难,因为 <picture> 和 srcset 提供的功能非常复杂,因此需要以一种能够保持其灵活性
客户端提示可以简化此过程。使用客户端提示协商图片响应可能如下所示:
- 如果适用于您的工作流程,请先查看
Viewport-Width提示,选择一种图片处理方式(即艺术指导图片)。 - 通过检查
Width提示和DPR提示,并选择适合图片布局大小和屏幕密度的来源(类似于x和w描述符在srcset中的工作方式),选择图片分辨率。 - 选择浏览器支持的最佳文件格式(
Accept可帮助我们在大多数浏览器中实现这一点)。
对于我虚构的木材公司客户,我使用 PHP 开发了一个简单的自适应图片选择例程,该例程使用客户端提示。这意味着,我们不再向所有用户发送以下标记:
<picture>
<source
srcset="
company-photo-256w.webp 256w,
company-photo-512w.webp 512w,
company-photo-768w.webp 768w,
company-photo-1024w.webp 1024w,
company-photo-1280w.webp 1280w
"
type="image/webp"
/>
<img
srcset="
company-photo-256w.jpg 256w,
company-photo-512w.jpg 512w,
company-photo-768w.jpg 768w,
company-photo-1024w.jpg 1024w,
company-photo-1280w.jpg 1280w
"
src="company-photo-256w.jpg"
sizes="(min-width: 560px) 251px, 88.43vw"
alt="The Sconnie Timber Staff!"
/>
</picture>
根据各个浏览器的支持情况,我将其缩减为以下内容:
<img
src="/image/sizes:true/company-photo.jpg"
sizes="(min-width: 560px) 251px, 88.43vw"
alt="SAY CHEESY PICKLES."
/>
在此示例中,/image 网址是一个 PHP 脚本,后跟由 mod_rewrite 重写的参数。它接受图片文件名和额外参数,以帮助后端脚本在给定条件下选择最佳图片。
我感觉您的第一个问题是“但这不只是在后端重新实现 <picture> 和 srcset 吗?”
在某种程度上,是的,但有一个重要区别:当应用使用客户端提示来制作媒体响应时,大多数(如果不是全部)工作都更容易实现自动化,其中可能包括可以代表您执行此操作的服务(例如 CDN)。而对于 HTML 解决方案,则需要编写新的标记来满足每个用例的需求。当然,您可以自动生成标记。不过,如果您的设计或要求发生变化,您很可能需要在未来重新审视自动化策略。
借助客户端提示,您可以先使用无损的高分辨率图片,然后根据屏幕和布局的任何组合动态调整图片大小,以达到最佳效果。与 srcset 不同,此方法
更灵活,因为 要求您列举一个固定的
可能图片候选列表供浏览器选择。虽然 srcset 强制您向浏览器提供一组粗略的变体(例如 256w、512w、768w 和 1024w),但由客户端提示提供支持的解决方案可以提供所有宽度,而无需大量标记。
当然,您不必自行编写图片选择逻辑。当您使用 w_auto 参数时,Cloudinary 会使用客户端提示来制作图片响应,并观察到,在使用支持客户端提示的浏览器时,中位数用户下载的字节数减少了 42%。
但要小心!桌面版 Chrome 67 中的更改移除了对跨域客户端提示的支持。幸运的是,这些限制不会影响移动版 Chrome,并且在功能政策方面的工作完成后,这些限制将在所有平台上完全解除。
帮助使用慢速网络的用户
自适应性能是指我们可以根据客户端提示提供的信息(特别是与用户网络连接的当前状态相关的信息)来调整资源传送方式。
对于 Sconnie Timber 的网站,我们会在网络速度较慢时采取措施来减轻负载,并在后端代码中检查 Save-Data、ECT、RTT 和 Downlink 标头。完成此操作后,我们会生成一个网络质量分数,用于确定是否应进行干预以提供更好的用户体验。此网络得分介于 0 和 1 之间,其中 0 表示最差的网络质量,1 表示最佳的网络质量。
首先,我们会检查 Save-Data 是否存在。如果是,则分数会设为 0,因为我们假设用户希望我们采取一切必要措施,让体验更轻快。
不过,如果 Save-Data 不存在,我们会继续评估 ECT、RTT 和 Downlink 提示的值,以计算描述网络连接质量的分数。网络评分生成源代码可在 GitHub 上找到。结论是,如果我们以某种方式使用与网络相关的提示,就可以为使用慢速网络的用户提供更好的体验。
当网站根据客户端提示提供的信息进行调整时,我们不必采用“全有或全无”的方法。我们可以智能地决定要发送哪些资源。我们可以修改自适应图片选择逻辑,以便在网络质量较差时,针对给定显示屏发送质量较低的图片,从而加快加载速度。
在此示例中,我们可以看到客户端提示在提升网站在较慢网络上的性能方面所产生的影响。以下是某个网站在慢速网络上的 WebPagetest 瀑布图,该网站不适应客户端提示:
现在,我们来看一下同一网站在同一慢速连接下的瀑布图,但这次,该网站使用客户端提示来消除非关键页面资源:
客户端提示将网页加载时间从 45 秒以上缩短到不到原来的十分之一。在这种情况下,客户端提示的好处不言而喻,对于通过慢速网络寻求关键信息的用户来说,这可能是一个巨大的福音。
此外,您还可以使用客户端提示,而不会破坏不支持客户端提示的浏览器的体验。例如,如果我们想使用 ECT 提示的值调整资源传送,同时仍为不支持的浏览器提供完整体验,我们可以设置回退到默认值,如下所示:
// Set the ECT value to "4g" by default.
$ect = isset($_SERVER["HTTP_ECT"]) ? $_SERVER["HTTP_ECT"] : "4g";
在这里,"4g" 表示 ECT 标头描述的最高质量网络连接。如果我们将 $ect 初始化为 "4g",则不支持客户端提示的浏览器不会受到影响。选择启用,赢取胜利!
注意那些藏匿点!
每当您根据 HTTP 标头更改响应时,都需要注意缓存将如何处理该资源的未来提取。Vary 标头在这里必不可少,因为它会将缓存条目与提供给它的请求标头的值相关联。简而言之,如果您根据给定的 HTTP 请求标头修改任何响应,则几乎应始终在 Vary 中包含该标头,如下所示:
Vary: DPR, Width
不过,这里有一个很大的注意事项:您绝不希望在频繁更改的标头(例如 Cookie)上 Vary 可缓存的响应,因为这些资源实际上无法缓存。了解这一点后,您可能希望避免在客户端提示标头(例如 RTT 或 Downlink)上使用 Vary,因为这些是可能经常发生变化的连接因素。如果您想修改这些标头上的响应,请考虑仅对 ECT 标头进行键控,这样可以最大限度地减少缓存未命中。
当然,这仅适用于您首先缓存响应的情况。
例如,如果 HTML 资源的内容是动态的,您就不会缓存这些资源,因为这可能会破坏用户重复访问时的体验。在这种情况下,您可以根据需要随意修改此类回答,而无需考虑 Vary。
Service Worker 中的客户端提示
内容协商不再只是服务器的专属功能!由于 Service Worker 充当客户端和服务器之间的代理,因此您可以通过 JavaScript 控制资源的传送方式。这包括客户端提示。在 Service Worker fetch 事件中,您可以使用 event 对象的 request.headers.get 方法读取资源的请求标头,如下所示:
self.addEventListener('fetch', (event) => {
let dpr = event.request.headers.get('DPR');
let viewportWidth = event.request.headers.get('Viewport-Width');
let width = event.request.headers.get('Width');
event.respondWith(
(async function () {
// Do what you will with these hints!
})(),
);
});
您选择启用的任何客户端提示标头都可以通过这种方式读取。不过,这并不是获取其中部分信息的唯一途径。您可以在 navigator 对象中的以下等效 JavaScript 属性中读取网络专用提示:
| 客户端提示 | JS 等效项 |
|---|---|
| `ECT` | `navigator.connection.effectiveType` |
| `RTT` | `navigator.connection.rtt` |
| `Save-Data` | `navigator.connection.saveData` |
| 下行链路 | `navigator.connection.downlink` |
| `Device-Memory` | `navigator.deviceMemory` |
由于这些 API 并非在所有国家/地区都可用,因此您需要使用 in 运算符进行功能检查:
if ('connection' in navigator) {
// Work with netinfo API properties in JavaScript!
}
在这里,您可以使用与在服务器上使用的逻辑类似的逻辑,但您不需要服务器来通过客户端提示协商内容。由于 Service Worker 能够在用户离线时提供内容,因此仅凭 Service Worker 就能让体验更快速、更灵活。
总结
借助客户端提示,我们能够以完全渐进的方式为用户提供更快的体验。我们可以根据用户的设备功能提供媒体,这样一来,提供自适应图片比依赖 <picture> 和 srcset 更容易,尤其是在复杂的用例中。这不仅能让我们减少开发方面的时间和精力,还能以比
更重要的是,我们可以检测到网络连接不佳的情况,并通过修改发送内容和发送方式来弥补这一差距。这对于让网络不稳定的用户更轻松地访问网站大有帮助。 结合使用 Service Worker,我们可以创建速度极快且可离线使用的网站。
虽然客户端提示仅在 Chrome 和基于 Chromium 的浏览器中可用,但您可以使用它们,而不会妨碍不支持它们的浏览器。考虑使用客户端提示来打造真正包容且适应性强的体验,这些体验能够感知每个用户的设备功能及其连接的网络。希望其他浏览器供应商能够看到它们的价值,并有意实现。
资源
感谢 Ilya Grigorik、Eric Portis、Jeff Posnick、Yoav Weiss 和 Estelle Weyl 对本文提供的宝贵反馈和编辑。