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

Ilya Grigorik

针对网站打造产品将为您带来无与伦比的覆盖面。只需轻轻一点,即可访问几乎所有联网设备,包括智能手机、平板电脑、笔记本电脑、台式机、电视等,无论品牌或平台为何。为了提供最佳体验,您构建了一个自适应网站,它可针对每种设备类型调整呈现方式和功能,现在您正在逐步进行性能核对清单以确保应用尽快加载:您已经优化了关键渲染路径,已经压缩和缓存了文本资源,现在您要查看的是通常传输的字节数图片资源。问题是,图片优化并非易事

  • 确定适当的格式(矢量还是光栅)
  • 确定最佳编码格式(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>

信不信由你,上面的示例足以提供与上面较长图片标记相同的所有功能,并且正如我们将看到的,它使开发者能够完全控制提取图片资源的方式、时间和时间。“Magic”位于第一行中,用于启用客户端提示报告,并告知浏览器向服务器通告资源的设备像素比 (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">

另外,还记得上面的这个人吗?借助客户端提示,不起眼的图片标记现在能够感知 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 在客户端上提供请求和响应管理功能。这就是可扩展 Web 的实际应用。

“客户提示”常见问题解答

  1. 在哪里可以使用客户端提示? 已在 Chrome 46 中提供。我们正在 FirefoxEdge 中考虑这一点。

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

  3. 既然有 ServiceWorker,为什么需要客户端提示? ServiceWorker 无权访问布局、资源和视口宽度信息。至少,这样做不会造成成本高昂的往返,并且会显著延迟图片请求 - 例如,当预加载解析器发起图片请求时。客户端提示与浏览器集成,以使该数据作为请求的一部分提供。

  4. 客户端提示是否仅适用于图片资源? DPR、Viewport-Width 和 Width 提示背后的核心用例是为图片素材资源选择资源。但是,无论类型如何,所有子资源都会提供相同的提示。例如,CSS 和 JavaScript 请求也会获得相同的信息,并可用于优化这些资源。

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

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

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