使用客户端提示自动选择资源

Ilya Grigorik
Ilya Grigorik

针对网站进行构建,可为您带来无与伦比的覆盖面。只需点击一下,您的 Web 应用就能在几乎所有已连接的设备(智能手机、平板电脑、笔记本电脑、桌面设备、电视等)上使用,而无论其品牌或平台如何。为了提供最佳体验,您构建了自适应网站,以针对每种外形规格调整呈现方式和功能。现在,您要运行性能核对清单,以确保应用能够尽快加载:优化了关键渲染路径压缩和缓存了文本资源,现在查看的是图片资源,其中大部分都是传输的字节。问题是,很难优化图片

  • 确定适当的格式(矢量与光栅)
  • 确定最佳编码格式(jpeg、webp 等)
  • 确定合适的压缩设置(有损与无损)
  • 确定应保留或删除哪些元数据
  • 为每个显示屏和 DPR 分辨率制作多个变体
  • ...
  • 考虑用户的网络类型、速度和偏好设置

就个别情况而言,这些都是易于理解的问题。 它们共同构成了一个庞大的优化空间,我们(开发者)经常忽视或忽略这些空间。人在重复探索同一搜索空间方面做得很差,尤其是在涉及多个步骤时。另一方面,计算机擅长处理这些类型的任务。

针对图片及其他具有类似属性的良好且可持续的优化策略,答案很简单:自动化。如果您手动微调资源,就会犯错:您会忘记,您会懒散易上手,或者其他人会代您犯错 - 我们保证会这样做。

注重性能的开发者的传奇

图片搜索优化空间包含两个不同的阶段:构建时和运行时。

  • 有些优化是资源本身的固有优化,例如,选择合适的格式和编码类型、为每个编码器调整压缩设置、去除不必要的元数据等等。这些步骤可以在“构建时”执行。
  • 其他优化取决于请求客户端的类型和属性,并且必须在“运行时”执行:根据客户端的 DPR 和预期的显示屏宽度选择合适的资源,考虑客户端的网络速度、用户和应用偏好设置等。

构建时工具存在,但还有改进空间。例如,通过动态调整每张图片和每种图片格式的“质量”设置可以节省大量费用,但我还没发现有人在研究之外实际使用这种设置。这个领域非常适合进行创新,但就本博文而言,我就不说了。我们来重点看一下该故事的推进部分。

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

应用 intent 非常简单:在 50% 的用户视口处提取并显示图片。大多数设计师在这里都会对吧台进行清洗。与此同时,团队中注重性能的开发者彻夜难眠:

  1. 为了获得最佳压缩效果,她希望针对每个客户端使用最佳图片格式:适用于 Chrome 的 WebP、适用于 Edge 的 JPEG XR,以及其余客户端的 JPEG。
  2. 为了获得最佳视觉质量,她需要以不同的分辨率为每张图片生成多个变体:1 倍、1.5 倍、2 倍、2.5 倍、3 倍,甚至介于两者之间,甚至可能更高。
  3. 为了避免传递不必要的像素,她需要了解“用户视口 50% 的幅面实际上意味着什么”- 有很多不同的视口宽度!
  4. 理想情况下,她还希望提供一种弹性体验,使网络速度较慢的用户自动获取较低的分辨率。毕竟,是时候试水了。
  5. 该应用还公开了一些用户控件,这些控件会影响应提取哪个图片资源,因此也要将这一点考虑在内。

哦,然后设计师发现,如果视口较小,她需要以 100% 宽度显示不同的图片以提高易读性。这意味着,我们现在必须对另外一个素材资源重复相同的流程,然后根据视口大小根据视口大小设置条件提取。我有没有提过这东西很难用?好了,我们开始吧picture 元素可以相当实用

<picture>
    <!-- serve WebP to Chrome and Opera -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
        /image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
        /image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
        /image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
        /image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
    type="image/webp">
    <!-- serve JPEGXR to Edge -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
        /image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
        /image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
        /image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
        /image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <!-- serve JPEG to others -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
        /image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
        /image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
        /image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
        /image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
    <!-- fallback for browsers that don't support picture -->
    <img src="/image/thing.jpg" width="50%">
</picture>

我们处理了美术指导和格式选择,并提供了每张图片的六种变体,以考虑到客户端设备的 DPR 和视口宽度的变化。非常棒!

遗憾的是,picture 元素不允许我们根据客户端的连接类型或速度定义任何规则来指示其行为方式。也就是说,在某些情况下,其处理算法确实允许用户代理调整其提取的资源(请参阅第 5 步)。我们只希望用户代理足够智能。(注意:目前没有任何实现)。同样,picture 元素中也没有任何钩子来支持处理应用或用户偏好设置的应用专属逻辑。为了获得最后两位,我们必须将上述所有逻辑移至 JavaScript,但这样会失去 picture 提供的预加载扫描器优化。嗯嗯。

不受这些限制的约束,这种机制是行之有效的。至少对于这一特定资源而言这里真正的长期挑战是,我们不能指望设计师或开发者为每个资源编写这样的代码。第一次尝试益智游戏很有意思,但之后它就会立即失去吸引力。我们需要自动化也许 IDE 或其他内容转换工具可以节省我们并自动生成上面的样板。

使用客户端提示自动选择资源

深吸一口气,暂停您的怀疑,现在请思考以下示例:

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
    <source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
    <img sizes="100vw" src="/image/thing-crop">
</picture>

信不信由你,上面的示例足以提供与上面长得多的照片标记相同的所有功能,而且正如我们将看到的,它让开发者能够完全控制获取图片资源的方式、以及提取时间。“魔法”位于第一行,用于启用客户端提示报告,并指示浏览器向服务器通告资源的设备像素比 (DPR)、布局视口宽度 (Viewport-Width) 和预期显示宽度 (Width)。

启用客户端提示后,生成的客户端标记将仅保留演示文稿要求。设计人员无需担心图片类型、客户端分辨率、减少传送字节数的最佳断点或其他资源选择标准。我们必须承认,他们从来没有这样做过,他们也应该不必这么做。更好的是,开发者也不需要重写和扩展上述标记,因为实际的资源选择是由客户端和服务器协商确定的。

Chrome 46 为 DPRWidthViewport-Width 提示提供原生支持。默认情况下,提示处于停用状态,上述 <meta http-equiv="Accept-CH" content="..."> 可用作选择启用信号,告知 Chrome 将指定的标头附加到传出请求中。找到上述内容后,我们来看看示例图片请求的请求和响应标头:

客户端提示协商图

Chrome 会通过 Accept 请求标头通告其支持 WebP 格式;新版 Edge 浏览器同样会通过 Accept 标头通告对 JPEG XR 的支持。

接下来的三个请求标头是客户端提示标头,用于通告客户端设备的设备像素比 (3x)、布局视口宽度 (460px) 以及资源的预期显示宽度 (230px)。这为服务器提供了根据自己的一组政策选择最佳图片变体所需的所有必要信息:预生成资源的可用性、重新编码或调整资源大小的费用、资源的热门程度、当前服务器负载等。在此特定情况下,服务器会使用 DPRWidth 提示并返回 WebP 资源,如 Content-TypeContent-DPRVary 标头所示。

这里没有魔法。我们已将资源选择从 HTML 标记转移到客户端与服务器之间的请求-响应协商中。因此,HTML 仅关注呈现要求,是我们可以信任任何设计人员和开发者编写的代码,而通过图片优化空间进行搜索则由计算机进行,现在可轻松地大规模自动执行。还记得我们注重性能的开发者吗?她现在的工作是编写一项图片服务,该服务可以利用提供的提示并返回适当的响应:她可以使用自己喜欢的任何语言或服务器,也可以让第三方服务或 CDN 代为执行此操作。

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

另外,还记得前面的这个人吗?在客户提示下,Bumble 图片标记现在可感知 DPR、视口和宽度,无需任何额外的标记。如果您需要添加 Art-direction,则可以使用 picture 标记(如上图所示),否则,您现有的所有图片标记都会变得更智能。客户端提示完善了现有的 imgpicture 元素。

通过 Service Worker 控制资源选择

ServiceWorker 实际上是一个在浏览器中运行的客户端代理。它会拦截所有传出请求,并允许您检查、重写、缓存甚至合成响应。图片没有什么不同,启用客户端提示后,活跃的 ServiceWorker 可以识别图片请求,检查提供的客户端提示,以及定义自己的处理逻辑。

self.onfetch = function(event) {
    var req = event.request.clone();
    console.log("SW received request for: " + req.url)
    for (var entry of req.headers.entries()) {
    console.log("\t" + entry[0] +": " + entry[1])
    }
    ...
}
客户端提示 serviceWorker。

ServiceWorker 可让您完全在客户端控制资源选择。这一点至关重要。让我们尽情探索吧,因为可能性近乎无限:

  • 您可以重写用户代理设置的客户端提示标头值。
  • 您可以在请求中附加新的客户端提示标头值。
  • 您可以重写该网址,并将图片请求指向备用服务器(例如 CDN)。
    • 您甚至可以将提示值从标头移到网址本身,以便更轻松地在您的基础架构中进行部署。
  • 您可以缓存响应并定义自己的逻辑来提供资源。
  • 您可以根据用户的网络连接情况调整响应。
  • 您可以说明应用和用户偏好设置的覆盖设置。
  • 你可以...随心所欲地做任何事。

picture 元素在 HTML 标记中提供必要的艺术方向控件。 客户端提示可针对生成的图片请求提供注释,以实现资源选择自动化。ServiceWorker 在客户端提供请求和响应管理功能。这就是可扩展网络的实际应用。

客户端提示常见问题解答

  1. 客户端提示在哪里可用? 已在 Chrome 46 中提供。FirefoxEdge 处于考虑阶段。

  2. 为什么选择启用客户端提示? 对于不使用客户端提示的网站,我们希望尽量减少开销。如需启用客户端提示,网站应在网页标记中提供 Accept-CH 标头或等效的 <meta http-equiv> 指令。如果其中任何一项存在,用户代理都会向所有子资源请求附加适当的提示。将来,我们可能会提供其他机制来针对特定源保留此偏好设置,以便针对导航请求提供相同的提示。

  3. 如果我们有 ServiceWorker,为什么还需要客户端提示?ServiceWorker 无权访问布局、资源和视口宽度信息。至少不会避免产生高昂的往返时间并显著延迟图片请求,例如,当预加载解析器发起图片请求时。客户端提示与浏览器集成,以使此数据作为请求的一部分提供。

  4. 客户端提示是否仅针对图片资源? DPR、视口宽度和宽度提示背后的核心用例是为图片资源启用资源选择功能。不过,无论类型如何,系统都会为所有子资源提供相同的提示,例如,CSS 和 JavaScript 请求也会获得相同的信息,并且可用于优化这些资源。

  5. 有些图片请求不报告宽度,为什么? 浏览器可能不知道预期的显示宽度,因为网站依赖于图片的固有尺寸。因此,对于此类请求以及没有“显示宽度”的请求(例如 JavaScript 资源),系统会忽略 Width 提示。如需接收宽度提示,请确保在图片上指定尺寸值

  6. <insert myFavorite hint>呢?ServiceWorker 可让开发者拦截和修改所有传出请求(例如添加新标头)。例如,您可以轻松添加基于 NetInfo 的信息来指示当前连接类型,具体请参阅“使用 ServiceWorker 进行功能报告”。 Chrome 中提供的“原生”提示(DPR、宽度、资源宽度)是在浏览器中实现的,因为纯基于软件的实现会延迟所有图片请求。

  7. 在哪里可以了解更多信息和查看更多演示? 请查阅说明文档,如果您有任何反馈或其他疑问,请随时在 GitHub 上提出问题