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

Ilya Grigorik
Ilya Grigorik

面向 Web 构建应用可让您覆盖无与伦比的用户群。只需点击一下,即可在几乎所有联网设备(智能手机、平板电脑、笔记本电脑和台式机、电视等)上使用您的 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. 为了获得最佳视觉效果,她需要为每张图片生成多个不同分辨率的变体:1x、1.5x、2x、2.5x、3x,甚至可能还需要生成一些介于这些分辨率之间的变体。
  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>

我们负责了美术指导和格式选择,并为每张图片提供了 6 种变体,以应对客户设备的 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 只关注呈现要求,我们可以信任任何设计师和开发者编写 HTML,而搜索图片优化空间则交由计算机完成,现在可以轻松实现大规模自动化。还记得我们前面提到的注重性能的开发者吗?现在,她的工作是编写一个图片服务,该服务可以利用提供的提示并返回适当的响应:她可以使用自己喜欢的任何语言或服务器,也可以让第三方服务或 CDN 代表她执行此操作。

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

此外,还记得上面这位客服人员吗?借助客户端提示,普通的图片标记现在可以感知 DPR、视口和宽度,而无需任何额外的标记。如果您需要添加艺术指导,可以使用 picture 标记(如上所述),否则,您现有的所有图片标记都会变得更加智能。客户端提示可增强现有的 imgpicture 元素。

使用服务工件控制资源选择

实际上,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 资源),系统会省略宽度提示。如需接收宽度提示,请务必为图片指定尺寸值

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

  7. 在哪里可以了解详情、观看更多演示,以及其他? 请参阅说明文档。如果您有反馈或其他问题,欢迎随时在 GitHub 上提交问题