开发随时随地都能快速加载的网站可能并非易事。大量的设备功能和它们所连接的网络的质量,可能会让这项任务看起来像是一件无法大规模完成的任务。虽然我们可以利用浏览器功能来提高加载性能,但我们如何知道用户设备的功能或网络连接的质量呢?解决方案是客户端提示!
客户端提示是一组自选 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。
了解一些术语后,我们开始列出可供您使用的特定于设备的客户端提示。
视口宽度
Viewport-Width
是用户视口的宽度(以 CSS 像素为单位):
Viewport-Width: 320
此提示可与其他屏幕特定的提示结合使用,以提供最适合特定屏幕尺寸(即艺术方向)的不同图片处理方式(即剪裁),或者省略对当前屏幕宽度不必要的资源。
DPR
DPR
是设备像素比的简称,用于报告用户屏幕的物理像素与 CSS 像素的比率:
DPR: 2
在选择与屏幕像素密度对应的图片来源时,此提示很有用(就像 x
描述符在 srcset
属性中所做的那样)。
宽度
对于由 <img>
或 <source>
标记使用 sizes
属性触发的图片资源请求,系统会显示 Width
提示。sizes
会告知浏览器资源的外围尺寸;Width
会使用该外侧尺寸请求具有最适合当前布局的外侧尺寸的图片。
例如,假设用户请求访问屏幕为 320 CSS 像素宽且 DPR 为 2 的网页。设备加载具有 <img>
元素且该元素包含 sizes
属性值 85vw
(即视口宽度的 85%(适用于所有屏幕尺寸)。如果已选择启用 Width
提示,客户端会将此 Width
提示随针对 <img>
的 src
的请求一起发送给服务器:
Width: 544
在本例中,客户端会提示服务器:所请求图片的最佳固有宽度是视口宽度(272 像素)的 85% 乘以屏幕的 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 API 的一部分,用于显示当前设备所具有的大致内存量(以 GiB 为单位):
Device-Memory: 2
此提示的一个可能用例是减少向内存有限的设备上的浏览器发送的 JavaScript 数量,因为 JavaScript 是浏览器通常加载的最资源密集型内容类型。或者,您可以发送更低的 DPR 图像,因为它们使用更少的内存进行解码。
网络提示
Network Information API 提供另一类客户端提示,用于描述用户网络连接的性能。我认为这是一组最实用的提示。有了它们,我们就可以通过更改在网速较慢时向客户端提供资源的方式,为用户量身定制体验。
RTT
RTT
提示在应用层提供大致的往返时间(以毫秒为单位)。与传输层 RTT 不同,RTT
提示包含服务器处理时间。
RTT: 125
此提示非常有用,因为延迟对加载性能起着重要作用。使用 RTT
提示,我们可以根据网络响应能力做出决策,这有助于加快整个体验的交付(例如,通过忽略某些请求)。
下行链路
虽然延迟时间对加载性能很重要,但带宽也有影响。Downlink
提示(以兆比特每秒 (Mbps) 为单位)显示用户连接的大致下行速度:
Downlink: 2.5
与 RTT
结合使用时,Downlink
有助于根据网络连接质量更改向用户分发内容的方式。
厄瓜多尔时间
ECT
提示表示有效连接类型。其值是枚举连接类型的列表之一,每种连接类型都描述介于 RTT
和 Downlink
值的指定范围内的连接。
此标头并未说明实际连接类型是什么。例如,它不会报告您的网关是手机基站还是 Wi-Fi 接入点。而是分析当前连接的延迟时间和带宽,并确定最相符的网络配置文件。例如,如果通过 Wi-Fi 连接到速度较慢的网络,系统可能会使用值 2g
填充 ECT
,该值是有效连接的最近似值:
ECT: 2g
ECT
的有效值包括 4g
、3g
、2g
和 slow-2g
。此提示可用作评估连接质量的起点,随后可使用 RTT
和 Downlink
提示进行优化。
保存数据
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 资源的内容是动态的,则不会缓存 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” |
“保存数据” | `navigator.connection.saveData` |
“下行链接” | “navigator.connection.downlink” |
“设备内存” | “navigator.deviceMemory” |
由于这些 API 并非在所有需要使用 in
运算符进行功能检查时都可用:
if ('connection' in navigator) {
// Work with netinfo API properties in JavaScript!
}
在这里,您可以使用与在服务器上使用的逻辑类似的逻辑,但您不需要服务器来通过客户端提示协商内容。只有 Service Worker 能够在用户离线时提供内容,因而能够提供更快、更具弹性的体验。
总结
借助客户端提示,我们能够以完全渐进式的方式为用户提供更快的体验。我们可以根据用户的设备功能来提供媒体,让提供自适应图片比依赖 <picture>
和 srcset
更容易,尤其是在复杂的用例中。这样一来,我们不仅可以减少开发方面的时间和工作量,还可以优化资源(尤其是图片),从而能够比
或许更重要的是,我们可以通过修改发送的内容及其发送方式来辨别状况不佳的网络连接并为用户弥补差距。这大大有助于让脆弱网络上的用户更轻松地访问网站。 结合 Service Worker,我们可以创建极快的离线网站。
虽然客户端提示仅在 Chrome 和基于 Chromium 的浏览器中提供,但您可以放心地使用客户端提示,以免浏览器不支持它们。考虑使用客户端提示来打造真正具有包容性和适应性的体验,了解每个用户的设备功能和他们连接到的网络。希望其他浏览器供应商能看到它们的价值,并表现出实施这些方案的意愿。
资源
感谢 Ilya Grigorik、Eric Portis、Jeff Posnick、Yoav Weiss 和 Estelle Weyl 对本文的宝贵反馈和修改。