2021 年前端性能:资产优化

已发表: 2022-03-10
快速总结↬让2021年……快点! 一份年度前端性能清单,包含您在当今 Web 上创建快速体验所需了解的所有内容,从指标到工具和前端技术。 自 2016 年以来更新。

目录

  1. 准备:计划和指标
  2. 设定切合实际的目标
  3. 定义环境
  4. 资产优化
  5. 构建优化
  6. 交付优化
  7. 网络、HTTP/2、HTTP/3
  8. 测试和监控
  9. 快速获胜
  10. 一切都在一页上
  11. 下载清单(PDF、Apple Pages、MS Word)
  12. 订阅我们的电子邮件通讯不要错过下一个指南。

资产优化

  1. 使用 Brotli 进行纯文本压缩。
    2015 年,谷歌推出了 Brotli,这是一种新的开源无损数据格式,现在所有现代浏览器都支持这种格式。 为 Brotli 实现编码器和解码器的开源 Brotli 库具有 11 个预定义的编码器质量级别,更高的质量级别需要更多的 CPU 以换取更好的压缩比。 较慢的压缩最终会导致更高的压缩率,但 Brotli 仍然可以快速解压缩。 值得注意的是,压缩级别为 4 的 Brotli 比 Gzip 更小且压缩速度更快。

    在实践中,Brotli 似乎比 Gzip 更有效。 意见和经验各不相同,但如果您的网站已经使用 Gzip 进行了优化,您可能会期望在大小缩减和 FCP 时间方面至少有一位数的改进,最多也有两位数的改进。 您还可以估计您的站点的 Brotli 压缩节省。

    只有当用户通过 HTTPS 访问网站时,浏览器才会接受 Brotli。 Brotli 受到广泛支持,许多 CDN 都支持它(Akamai、Netlify Edge、AWS、KeyCDN、Fastly(目前仅作为直通)、Cloudflare、CDN77),即使在尚不支持的 CDN 上也可以启用 Brotli (与服务人员)。

    问题是,由于使用 Brotli 以高压缩级别压缩所有资产的成本很高,因此许多托管服务提供商无法完全使用它,因为它会产生巨大的成本开销。 事实上,在最高级别的压缩下,Brotli非常慢,以至于服务器在等待动态压缩资产时开始发送响应所需的时间可能会抵消文件大小的任何潜在收益。 (但如果您在构建期间有时间使用静态压缩,当然,更高的压缩设置是首选。)

    显示为须图的比较,显示了三种不同后端时间的各种压缩方法:最小、平均和第 90 个百分位数
    各种压缩方法的后端时间比较。 不出所料,Brotli 比 gzip 慢(目前)。 (大预览)

    不过,这可能正在改变。 Brotli 文件格式包括一个内置的静态字典,除了包含多种语言的各种字符串外,它还支持对这些单词应用多种转换的选项,增加了它的多功能性。 在他的研究中,Felix Hanau 发现了一种改进 5 到 9 级压缩的方法,方法是使用“比默认字典更专业的子集”并依靠Content-Type标头告诉压缩器是否应该使用HTML、JavaScript 或 CSS 的字典。 结果是“在使用有限的字典使用方法以高压缩级别压缩 Web 内容时,性能影响可以忽略不计(CPU 比正常情况下的 12% 多 1% 到 3%)。”

    一个条形图,显示在第 5 级使用 Brotli 精简字典的压缩增益
    使用改进的字典方法,我们可以在更高的压缩级别上更快地压缩资产,同时只使用 1% 到 3% 的 CPU。 通常,压缩级别 6 比 5 最多会使 CPU 使用率增加 12%。 (大预览)

    最重要的是,通过 Elena Kirilenko 的研究,我们可以使用以前的压缩工件实现快速高效的 Brotli 再压缩。 根据 Elena 的说法,“一旦我们通过 Brotli 压缩了资产,并且我们尝试动态压缩动态内容,其中的内容类似于我们提前可用的内容,我们就可以显着缩短压缩时间。 "

    这种情况多久发生一次? 例如,交付JavaScript 捆绑包子集(例如,当部分代码已经缓存在客户端或使用 WebBundles 提供动态捆绑包时)。 或者使用基于预先知道的模板的动态 HTML,或者动态子集的 WOFF2 字体。 根据 Elena 的说法,当删除 10% 的内容时,我们可以获得 5.3% 的压缩改进和 39% 的压缩速度改进,当删除 50% 的内容时,压缩率提高了 3.2%,压缩速度提高了 26%。

    Brotli 压缩越来越好,所以如果你能绕过动态压缩静态资产的成本,那绝对是值得的。 不用说,Brotli 可以用于任何纯文本负载——HTML、CSS、SVG、JavaScript、JSON 等等。

    注意:截至 2021 年初,大约 60% 的 HTTP 响应是在没有基于文本的压缩的情况下交付的,其中 30.82% 使用 Gzip 压缩,9.1% 使用 Brotli 压缩(无论是在移动设备上还是在桌面上)。 例如,23.4% 的 Angular 页面没有被压缩(通过 gzip 或 Brotli)。 然而,通常打开压缩是通过简单的开关翻转来提高性能的最简单的方法之一。

    策略? 在最高级别使用 Brotli+Gzip 预压缩静态资产,并在级别 4-6 使用 Brotli 动态压缩(动态)HTML。 确保服务器正确处理 Brotli 或 Gzip 的内容协商。

根据 Web Almanax 2020 报告显示 HTTP 请求的压缩算法的条形图
在 2020 年压缩的资源中,有 22.59% 是使用 Brotli 压缩的。 大约 77.39% 是用 gzip 压缩的。 (图片来源:Web Almanac:压缩)(大预览)
  1. 我们是否使用自适应媒体加载和客户端提示?
    它来自旧新闻的土地,但它始终是一个很好的提醒,使用带有srcsetsizes<picture>元素的响应式图像。 特别是对于媒体占用量大的网站,我们可以通过自适应媒体加载(在本例中为 React + Next.js)更进一步,为慢速网络和低内存设备提供轻量体验,为快速网络和高内存设备提供完整体验-内存设备。 在 React 的上下文中,我们可以通过服务器上的客户端提示和客户端上的 react-adaptive-hooks 来实现它。

    随着客户提示的广泛采用,响应式图像的未来可能会发生巨大变化。 客户端提示是 HTTP 请求标头字段,例如DPRViewport-WidthWidthSave-DataAccept (指定图像格式首选项)等。 他们应该通知服务器有关用户浏览器、屏幕、连接等的细节。

    因此,服务器可以决定如何用适当大小的图像填充布局,并仅以所需格式提供这些图像。 通过客户端提示,我们将资源选择从 HTML 标记移到客户端和服务器之间的请求-响应协商中。

    显示如何通过根据用户的网络能力向用户发送不同的分辨率来使用自适应媒体服务的插图
    正在使用的自适应媒体服务。 我们向离线用户发送带文本的占位符,向 2G 用户发送低分辨率图像,向 3G 用户发送高分辨率图像,向 4G 用户发送高清视频。 通过在 20 美元的功能手机上快速加载网页。 (大预览)

    正如 Ilya Grigorik 不久前指出的那样,客户提示完成了图片 - 它们不是响应式图像的替代品。 “ <picture>元素在 HTML 标记中提供了必要的艺术方向控制。客户端提示为生成的图像请求提供注释,从而实现资源选择自动化。Service Worker 在客户端提供完整的请求和响应管理功能。”

    例如,服务工作者可以将新的客户端提示标头值附加到请求中,重写 URL 并将图像请求指向 CDN,根据连接性和用户偏好调整响应等。它不仅适用于图像资产,而且适用于几乎所有其他请求也是如此。

    对于支持客户端提示的客户端,可以测量到图像节省 42% 的字节和 70th+ 百分位数的 1MB+ 更少的字节。 在 Smashing Magazine 上,我们也可以测量到 19-32% 的改进。 基于 Chromium 的浏览器支持客户端提示,但在 Firefox 中仍在考虑中。

    但是,如果您同时为客户端提示提供正常的响应式图像标记和<meta>标记,则支持的浏览器将评估响应式图像标记并使用客户端提示 HTTP 标头请求适当的图像源。

  2. 我们是否使用响应式图像作为背景图像?
    我们当然应该! 使用image-set ,现在 Safari 14 和除 Firefox 之外的大多数现代浏览器都支持,我们也可以提供响应式背景图像:

    background-image: url("fallback.jpg"); background-image: image-set( "photo-small.jpg" 1x, "photo-large.jpg" 2x, "photo-print.jpg" 600dpi);

    基本上,我们可以有条件地提供具有1x描述符的低分辨率背景图像,以及具有2x描述符的高分辨率图像,甚至是具有600dpi描述符的打印质量图像。 但请注意:浏览器不会为辅助技术提供有关背景图像的任何特殊信息,因此理想情况下,这些照片只是装饰。

  3. 我们使用 WebP 吗?
    图像压缩通常被认为是一种快速的胜利,但在实践中它仍然没有得到充分利用。 当然,图像不会阻塞渲染,但它们会严重影响 LCP 分数,而且通常它们对于正在使用它们的设备来说太重太大了。

    所以至少,我们可以探索为我们的图像使用 WebP 格式。 事实上,随着 Apple 在 Safari 14 中增加对 WebP 的支持,WebP 传奇已接近尾声。因此,经过多年的讨论和辩论,截至今天,所有现代浏览器都支持 WebP。 因此,如果需要(请参阅 Andreas Bovens 的代码片段)或使用内容协商(使用Accept标头),我们可以使用<picture>元素和 JPEG 后备来提供 WebP 图像。

    不过,WebP 并非没有缺点。 虽然 WebP 图像文件的大小与等效的 Guetzli 和 Zopfli 相比,但该格式不支持像 JPEG 这样的渐进式渲染,这就是为什么用户可以使用良好的 JPEG 更快地看到完成的图像,尽管 WebP 图像可能会通过网络变得更快。 使用 JPEG,我们可以用一半甚至四分之一的数据提供“体面”的用户体验,然后再加载其余的数据,而不是像 WebP 那样使用半空图像。

    您的决定将取决于您追求的目标:使用 WebP,您将减少有效负载,使用 JPEG,您将提高感知性能。 您可以在 Google 的 Pascal Massimino 的 WebP Rewind 演讲中了解有关 WebP 的更多信息。

    要转换为 WebP,您可以使用 WebP Converter、cwebp 或 libwebp。 Ire Aderinokun 也有一个非常详细的关于将图像转换为 WebP 的教程——Josh Comeau 在他关于拥抱现代图像格式的文章中也是如此。

    用于 Pascal Massimino 演讲的幻灯片,标题为 Image ready: webp rewind
    关于 WebP 的彻底讨论:Pascal Massimino 的 WebP Rewind。 (大预览)

    Sketch 原生支持 WebP,并且可以使用 Photoshop 的 WebP 插件从 Photoshop 中导出 WebP 图像。 但也有其他选择。

    如果您使用的是 WordPress 或 Joomla,有一些扩展可以帮助您轻松实现对 WebP 的支持,例如 Optimus 和 WordPress 的 Cache Enabler 以及 Joomla 自己支持的扩展(通过 Cody Arsenault)。 您还可以使用 React、样式化组件或 gatsby-image 抽象出<picture>元素。

    啊——不要脸的插头! — Jeremy Wagner 甚至出版了一本关于 WebP 的 Smashing 书,您可能想看看您是否对 WebP 周围的一切感兴趣。

  4. 我们使用 AVIF 吗?
    您可能已经听说了一个重大消息:AVIF 已经登陆。 它是一种源自 AV1 视频关键帧的新图像格式。 它是一种开放、免版税的格式,支持有损和无损压缩、动画、有损 Alpha 通道,并且可以处理锐利的线条和纯色(这是 JPEG 的一个问题),同时提供更好的结果。

    事实上,与 WebP 和 JPEG 相比, AVIF 的性能要好得多,在相同的 DSSIM 下(使用近似人类视觉的算法,两个或多个图像之间的(不)相似性),文件大小中值节省高达 50%。 事实上,在他关于优化图像加载的详尽文章中,Malte Ubl 指出,AVIF“在一个非常重要的方面始终优于 JPEG。这与 WebP 不同,WebP 并不总是产生比 JPEG 更小的图像,实际上可能是一个网络 -由于缺乏对渐进式加载的支持而导致的损失。”

    将 AVIF 显示为渐进增强的代码片段
    我们可以将 AVIF 用作渐进增强,将 WebP 或 JPEG 或 PNG 传递给旧版浏览器。 (大预览)。 请参阅下面的纯文本视图。

    具有讽刺意味的是,AVIF 的性能甚至比大型 SVG 还要好,尽管它当然不应被视为 SVG 的替代品。 它也是最早支持 HDR 颜色支持的图像格式之一; 提供更高的亮度、色位深度和色域。 唯一的缺点是目前 AVIF 不支持渐进式图像解码(还没有?),与 Brotli 类似,高压缩率编码目前相当慢,尽管解码速度很快。

    AVIF 目前在 Chrome、Firefox 和 Opera 中得到支持,而对 Safari 的支持预计很快就会到来(因为 Apple 是创建 AV1 的小组的成员)。

    那么,如今提供图像的最佳方式是什么? 对于插图和矢量图,(压缩的)SVG 无疑是最佳选择。 对于照片,我们使用带有picture元素的内容协商方法。 如果支持 AVIF,我们发送 AVIF 图像; 如果不是这样,我们首先回退到 WebP,如果 WebP 也不支持,我们切换到 JPEG 或 PNG 作为后备(如果需要,应用@media条件):

    <picture> <source type="image/avif"> <source type="image/webp"> <img src="image.jpg" alt="Photo" width="450" height="350"> </picture>

    坦率地说,我们更有可能在picture元素中使用一些条件:

    <picture> <source type="image/avif" /> <source type="image/webp" /> <source type="image/jpeg" /> <img src="fallback-image.jpg" alt="Photo" width="450" height="350"> </picture>
    <picture> <source type="image/avif" /> <source type="image/webp" /> <source type="image/jpeg" /> <img src="fallback-image.jpg" alt="Photo" width="450" height="350"> </picture>

    对于选择使用prefers-reduced-motion运动的客户,您可以通过将动画图像与静态图像交换更进一步:

    <picture> <source media="(prefers-reduced-motion: reduce)" type="image/avif"></source> <source media="(prefers-reduced-motion: reduce)" type="image/jpeg"></source> <source type="image/avif"></source> <img src="motion.jpg" alt="Animated AVIF"> </picture>
    <picture> <source media="(prefers-reduced-motion: reduce)" type="image/avif"></source> <source media="(prefers-reduced-motion: reduce)" type="image/jpeg"></source> <source type="image/avif"></source> <img src="motion.jpg" alt="Animated AVIF"> </picture>

    在过去的几个月里,AVIF 获得了相当大的关注:

    • 我们可以在 DevTools 的 Rendering 面板中测试 WebP/AVIF 回退。
    • 我们可以使用 Squoosh、AVIF.io 和 libavif 对 AVIF 文件进行编码、解码、压缩和转换。
    • 我们可以使用 Jake Archibald 的 AVIF Preact 组件,它在 worker 中解码 AVIF 文件并将结果显示在画布上,
    • 为了只向支持的浏览器提供 AVIF,我们可以使用 PostCSS 插件和 315B 脚本在您的 CSS 声明中使用 AVIF。
    • 我们可以使用 CSS 和 Cloudlare Workers 逐步交付新的图像格式,以动态更改返回的 HTML 文档,从accept头推断信息,然后根据需要添加webp/avif等类。
    • AVIF 已经在 Cloudinary 中可用(有使用限制),Cloudflare 在 Image Resizing 中支持 AVIF,您可以在 Netlify 中启用带有自定义 AVIF 标头的 AVIF。
    • 在动画方面,AVIF 的表现与 Safari 的<img src=mp4>一样好,总体上优于 GIF 和 WebP,但 MP4 的表现仍然更好。
    • 一般来说,对于动画,AVC1 (h264) > HVC1 > WebP > AVIF > GIF,假设基于 Chromium 的浏览器将永远支持<img src=mp4>
    • 您可以在 Netflix 的 Aditya Mavlankar 的 AVIF for Next Generation Image Coding 演讲和 Cloudflare 的 Kornel Lesinski 的 AVIF 图像格式演讲中找到有关 AVIF 的更多详细信息。
    • AVIF 的所有内容的绝佳参考:Jake Archibald 关于 AVIF 的综合帖子已登陆。

    那么未来的AVIF呢? Jon Sneyers 不同意:AVIF 的性能比 JPEG XL 差 60%,JPEG XL 是另一种由 Google 和 Cloudinary 开发的免费开放格式。 事实上,JPEG XL 似乎全面表现得更好。 但是,JPEG XL 仍处于标准化的最后阶段,还不能在任何浏览器中工作。 (不要与来自优秀的 Internet Explorer 9 次的 Microsoft JPEG-XR 混淆)。

响应式图像断点生成器
响应式图像断点生成器自动生成图像和标记。
  1. JPEG/PNG/SVG 是否正确优化?
    当您在着陆页上工作时,英雄图像的加载速度至关重要编码器专注于感知性能,并利用 Zopfli 和 WebP 的学习成果。 唯一的缺点:处理时间慢(每百万像素 CPU 需要一分钟)。

    对于 PNG,我们可以使用 Pingo,对于 SVG,我们可以使用 SVGO 或 SVGOMG。 如果您需要从网站快速预览、复制或下载所有 SVG 资源,svg-grabber 也可以为您完成。

    每一篇图像优化文章都会说明这一点,但保持矢量资源的干净和紧凑总是值得一提的。 确保清理未使用的资产,删除不必要的元数据并减少艺术品中的路径点数量(以及 SVG 代码)。 (谢谢,杰里米!

    不过,也有一些有用的在线工具可用:

    • 使用 Squoosh 以最佳压缩级别(有损或无损)压缩、调整大小和处理图像,
    • 使用 Guetzli.it 通过 Guetzli 压缩和优化 JPEG 图像,它适用于具有锐利边缘和纯色的图像(但可能会慢一些)。
    • 使用响应式图像断点生成器或 Cloudinary 或 Imgix 等服务来自动优化图像。 此外,在许多情况下,单独使用srcsetsizes将获得显着的好处。
    • 要检查响应式标记的效率,您可以使用imaging-heap,这是一个命令行工具,可以测量视口大小和设备像素比的效率。
    • 您可以将自动图像压缩添加到您的 GitHub 工作流程中,因此没有图像可以在未压缩的情况下投入生产。 该操作使用可处理 PNG 和 JPG 的 mozjpeg 和 libvips。
    • 为了优化存储,您可以使用 Dropbox 的新 Lepton 格式将 JPEG 平均压缩 22%。
    • 如果您想尽早显示占位符图像,请使用 BlurHash。 BlurHash 拍摄一张图片,并为您提供一个短字符串(仅 20-30 个字符!),代表该图片的占位符。 该字符串足够短,可以很容易地作为字段添加到 JSON 对象中。
    左侧没有图像占位符和右侧显示占位符的界面的比较
    BlurHash 是图像占位符的小型紧凑表示。 (大预览)

    有时仅优化图像并不能解决问题。 为了缩短开始渲染关键图像所需的时间,延迟加载不太重要的图像并延迟任何脚本在关键图像已经渲染后加载。 最安全的方法是混合延迟加载,当我们使用本机延迟加载和延迟加载时,该库可以检测通过用户交互触发的任何可见性更改(使用我们稍后将探讨的 IntersectionObserver)。 此外:

    • 考虑预加载关键图像,这样浏览器就不会太晚发现它们。 对于背景图片,如果你想更加激进,可以使用<img src>将图片添加为普通图片,然后将其隐藏在屏幕之外。
    • 考虑通过根据媒体查询指定不同的图像显示尺寸来使用 Sizes 属性交换图像,例如,操纵sizes以交换放大镜组件中的源。
    • 查看图像下载不一致的情况,以防止前景和背景图像的意外下载。 注意默认加载但可能永远不会显示的图像——例如在轮播、手风琴和图像画廊中。
    • 确保始终在图像上设置widthheight 。 注意 CSS 中的aspect-ratio属性和intrinsicsize属性,这将允许我们设置图像的宽高比和尺寸,因此浏览器可以提前预留一个预定义的布局槽以避免页面加载期间的布局跳转。
    显示编辑器中使用的 padding-top 和 aspect-ratio 元素的代码截图
    现在应该只是几周或几个月的事情,纵横比登陆浏览器。 已经在 Safari Technical Preview 118 中。 目前在 Firefox 和 Chrome 中处于领先地位。 (大预览)

    如果您喜欢冒险,您可以使用 Edge 工作程序(基本上是位于 CDN 上的实时过滤器)来切分和重新排列 HTTP/2 流,以通过网络更快地发送图像。 边缘工作者使用 JavaScript 流,这些流使用您可以控制的块(基本上它们是在 CDN 边缘上运行的 JavaScript,可以修改流响应),因此您可以控制图像的交付。

    使用服务人员,为时已晚,因为您无法控制线路上的内容,但它确实适用于 Edge 工作者。 因此,您可以在为特定登录页面逐步保存的静态 JPEG 之上使用它们。

    显示具有各种视口大小和设备像素比的表格的图像堆命令行工具的屏幕截图
    成像堆的示例输出,这是一个命令行工具,用于测量视口大小和设备像素比的效率。 (图片来源)(大预览)

    还不够好? 好吧,您还可以使用多背景图像技术提高图像的感知性能。 请记住,使用对比度和模糊不必要的细节(或去除颜色)也可以减小文件大小。 啊,你需要放大一张小照片而不损失质量吗? 考虑使用 Letsenhance.io。

    到目前为止,这些优化只涵盖了基础知识。 Addy Osmani 发布了一份关于基本图像优化的非常详细的指南,该指南非常深入地介绍了图像压缩和色彩管理的细节。 例如,您可以模糊图像的不必要部分(通过对其应用高斯模糊滤镜)以减小文件大小,最终您甚至可能开始去除颜色或将图片变为黑白以进一步减小大小. 对于背景图像,从 Photoshop 中以 0 到 10% 的质量导出照片也是完全可以接受的。

    在 Smashing Magazine 上,我们使用后缀-opt作为图像名称 - 例如, brotli-compression-opt.png ; 每当图像包含该后缀时,团队中的每个人都知道该图像已被优化。

    啊,不要在网络上使用 JPEG-XR ——“在 CPU 上解码 JPEG-XRs 软件端的处理抵消甚至超过了字节大小节省的潜在积极影响,尤其是在 SPA 的上下文中”(不是与 Cloudinary/Google 的 JPEG XL 混合)。

用视频元素替换动画 GIF,节省 80% 以上
Addy Osmani 建议用循环内嵌视频替换动画 GIF。 文件大小差异很明显(节省 80%)。 (大预览)
  1. 视频是否正确优化?
    到目前为止,我们涵盖了图像,但我们避免了关于好的 ol' GIF 的对话。 尽管我们喜欢 GIF,但现在是时候彻底放弃它们了(至少在我们的网站和应用程序中)。 与其加载影响渲染性能和带宽的繁重的动画 GIF,不如切换到动画 WebP(GIF 作为后备)或完全用循环的 HTML5 视频替换它们。

    与图像不同,浏览器不会预加载<video>内容,但 HTML5 视频往往比 GIF 更轻更小。 不是一个选项? 好吧,至少我们可以使用有损 GIF、gifsicle 或 giflossy 为 GIF 添加有损压缩。

    Colin Bendell 的测试表明,Safari 技术预览中img标签内的内嵌视频显示速度至少比 GIF 快 20 倍,解码速度快 7 倍,而且文件大小只是一小部分。 但是,其他浏览器不支持它。

    在好消息的土地上,视频格式多年来一直在大规模发展。 很长一段时间以来,我们一直希望 WebM 能够成为统治一切的格式,而 WebP(基本上是 WebM 视频容器内的一张静止图像)将成为过时的图像格式的替代品。 的确,Safari 现在支持 WebP,但尽管 WebP 和 WebM 近来获得了支持,但这一突破并没有真正发生。

    尽管如此,我们仍然可以将 WebM 用于大多数现代浏览器:

    <!-- By Houssein Djirdeh. https://web.dev/replace-gifs-with-videos/ --> <!-- A common scenartio: MP4 with a WEBM fallback. --> <video autoplay loop muted playsinline> <source src="my-animation.webm" type="video/webm"> <source src="my-animation.mp4" type="video/mp4"> </video>

    但也许我们可以完全重新审视它。 2018 年,开放媒体联盟发布了一种新的有前途的视频格式,称为AV1 。 AV1 具有类似于 H.265 编解码器(H.264 的演变)的压缩,但与后者不同的是,AV1 是免费的。 H.265 许可证定价促使浏览器供应商改用性能相当的 AV1: AV1(就像 H.265)的压缩率是 WebM 的两倍

    AV1 标志 2018
    AV1 很有可能成为网络视频的终极标准。 (图片来源:Wikimedia.org)(大预览)

    事实上,苹果目前使用的是 HEIF 格式和 HEVC(H.265),而最新 iOS 上的所有照片和视频都是以这些格式保存的,而不是 JPEG。 虽然 HEIF 和 HEVC (H.265) 还没有正确地暴露在网络上(还没有?),但 AV1 是——而且它正在获得浏览器的支持。 因此,在您的<video>标签中添加AV1源代码是合理的,因为所有浏览器供应商似乎都参与其中。

    目前,最广泛使用和支持的编码是 H.264,由 MP4 文件提供,因此在提供文件之前,请确保您的 MP4 使用多通道编码进行处理,使用 frei0r iirblur 效果(如果适用)进行模糊处理,并且moov atom 元数据被移动到文件的头部,而您的服务器接受字节服务。 Boris Schapira 为 FFmpeg 提供了精确的指令以最大限度地优化视频。 当然,提供 WebM 格式作为替代方案也会有所帮助。

    需要更快地开始渲染视频但视频文件仍然太大? 例如,每当您在登录页面上有大型背景视频时? 一种常用的技术是首先将第一帧显示为静止图像,或者显示一个经过高度优化的短循环片段,可以解释为视频的一部分,然后,只要视频缓冲足够,就开始播放实际的视频。 Doug Sillars 编写了一份详细的背景视频性能指南,在这种情况下可能会有所帮助。 (谢谢,Guy Podjarny! )。

    对于上述场景,您可能需要提供响应式海报图片。 默认情况下, video元素只允许一张图片作为海报,这不一定是最佳的。 我们可以使用 Responsive Video Poster,这是一个 JavaScript 库,允许您为不同的屏幕使用不同的海报图像,同时还添加过渡叠加和视频占位符的完整样式控制。

    研究表明,视频流质量会影响观看者的行为。 事实上,如果启动延迟超过 2 秒左右,观众就会开始放弃视频。 超过这一点,延迟增加 1 秒会导致放弃率增加大约 5.8%。 因此,视频开始时间的中位数为 12.8 秒也就不足为奇了,其中 40% 的视频至少有 1 个停顿,20% 的视频至少有 2 秒的停顿视频播放。 事实上,在 3G 上,视频停顿是不可避免的,因为视频播放速度快于网络提供的内容。

    那么,解决方案是什么? 通常小屏幕设备无法处理我们为桌面服务的 720p 和 1080p。 根据 Doug Sillars 的说法,我们可以创建更小的视频版本,并使用 Javascript 来检测小屏幕的来源,以确保在这些设备上快速流畅地播放。 或者,我们可以使用流式视频。 HLS 视频流将向设备提供适当大小的视频——抽象出为不同屏幕创建不同视频的需要。 它还将协商网络速度,并根据您使用的网络速度调整视频比特率。

    为了避免带宽浪费,我们只能为实际可以播放视频的设备添加视频源。 或者,我们可以完全从video标签中删除autoplay属性,并使用 JavaScript 为更大的屏幕插入autoplay 。 此外,我们需要在video上添加preload="none"来告诉浏览器在它真正需要文件之前不要下载任何视频文件:

    <!-- Based on Doug Sillars's post. https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ --> <video preload="none" playsinline muted loop width="1920" height="1080" poster="poster.jpg"> <source src="video.webm" type="video/webm"> <source src="video.mp4" type="video/mp4"> </video>

    然后我们可以专门针对实际支持 AV1 的浏览器:

    <!-- Based on Doug Sillars's post. https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ --> <video preload="none" playsinline muted loop width="1920" height="1080" poster="poster.jpg"> <source src="video.av1.mp4" type="video/mp4; codecs=av01.0.05M.08"> <source src="video.hevc.mp4" type="video/mp4; codecs=hevc"> <source src="video.webm" type="video/webm"> <source src="video.mp4" type="video/mp4"> </video>

    然后我们可以在某个阈值(例如 1000 像素)上重新添加autoplay

    /* By Doug Sillars. https://dougsillars.com/2020/01/06/hiding-videos-on-the-mbile-web/ */ <script> window.onload = addAutoplay(); var videoLocation = document.getElementById("hero-video"); function addAutoplay() { if(window.innerWidth > 1000){ videoLocation.setAttribute("autoplay",""); }; } </script>
    条形图按设备和网络速度(包括 3G、Cable、LTE 和本机)显示 Alcatel 1X、Moto G、Moto G4、MotoE、Nexus 5 和 OnePlus 5 的小时间(毫秒)
    按设备和网络速度划分的档数。 速度更快的网络上的速度更快的设备几乎没有停顿。 根据道格·西拉斯的研究。 (大预览)

    视频播放性能本身就是一个故事,如果您想详细了解它,请查看 Doug Sillars 关于视频当前状态和视频交付最佳实践的另一个系列,其中包括有关视频交付指标的详细信息、视频预加载、压缩和流式传输。 最后,您可以使用 Stream 或 Not 检查视频流的速度或速度。

Zach Leatherman 的字体加载策略综合指南显示为思维导图
Zach Leatherman 的字体加载策略综合指南提供了十几种更好的 Web 字体交付选项。
  1. 网络字体交付是否优化?
    值得一问的第一个问题是,我们是否可以一开始就使用 UI 系统字体——我们只需要确保仔细检查它们在各种平台上的显示是否正确。 如果不是这种情况,我们提供的网络字体很可能包含字形以及未使用的额外功能和权重。 我们可以要求我们的字体铸造厂对Web 字体进行子集化,或者如果我们使用开源字体,则使用 Glyphhanger 或 Fontsquirrel 自行对它们进行子集化。 我们甚至可以使用 Peter Muller 的子字体自动化我们的整个工作流程,这是一个命令行工具,可以静态分析您的页面以生成最佳的网络字体子集,然后将它们注入我们的页面。

    WOFF2 支持很棒,我们可以使用 WOFF 作为不支持它的浏览器的后备——或者也许可以为旧版浏览器提供系统字体。 Web 字体加载有很多很多的选项,我们可以从 Zach Leatherman 的“字体加载策略综合指南”中选择一种策略(代码片段也可以作为 Web 字体加载食谱提供)。

    今天要考虑的更好的选择可能是带有preload的关键 FOFT 和“妥协”方法。 他们都使用两阶段渲染来分步交付网络字体——首先是一个小的超子集,需要使用网络字体快速准确地渲染页面,然后异步加载系列的其余部分。 不同之处在于“The Compromise”技术仅在不支持字体加载事件时才异步加载 polyfill,因此默认情况下不需要加载 polyfill。 需要快速获胜吗? Zach Leatherman 有一个 23 分钟的快速教程和案例研究,可以让您的字体井井有条。

    一般来说,使用preload资源提示来预加载字体可能是一个好主意,但在您的标记中包含指向关键 CSS 和 JavaScript 的链接之后的提示。 对于preload ,存在优先级难题,因此请考虑在外部阻塞脚本之前将rel="preload"元素注入 DOM。 根据 Andy Davies 的说法,“使用脚本注入的资源在脚本执行之前对浏览器是隐藏的,当浏览器发现preload提示时,我们可以使用这种行为来延迟。” 否则,字体加载将在第一次渲染时花费您。

    幻灯片 93 的屏幕截图显示了两个图像示例,它们旁边的标题是“指标优先级:预加载每个系列中的一个”
    当一切都很关键时,没有什么是关键的。 仅预加载每个系列的一种或最多两种字体。 (图片来源:Zach Leatherman - 幻灯片 93)(大预览)

    有选择性并选择最重要的文件是个好主意,例如那些对渲染至关重要的文件或有助于避免可见和破坏性文本重排的文件。 一般来说,Zach 建议预加载每个系列的一到两种字体——如果它们不太重要,延迟一些字体加载也是有意义的。

    @font-face规则中定义font-family时,使用local()值(按名称引用本地字体)已变得非常普遍:

     /* Warning! Not a good idea! */ @font-face { font-family: Open Sans; src: local('Open Sans Regular'), local('OpenSans-Regular'), url('opensans.woff2') format ('woff2'), url('opensans.woff') format('woff'); }

    这个想法是有道理的:一些流行的开源字体,例如Open Sans,会预装一些驱动程序或应用程序,所以如果字体在本地可用,浏览器不需要下载网络字体,可以显示本地立即字体。 正如 Bram Stein 所指出的,“虽然本地字体与网络字体的名称匹配,但它很可能不是同一种字体。许多网络字体与其“桌面”版本不同。文本可能呈现不同,某些字符可能会下降回到其他字体,OpenType 功能可能完全缺失,或者行高可能不同。”

    此外,随着字体随着时间的推移而演变,本地安装的版本可能与 Web 字体大不相同,字符看起来也大不相同。 因此,根据 Bram 的说法,最好不要在@font-face规则中混合本地安装的字体和 Web 字体。 除了 Android 对 Roboto 的请求外,Google 字体也对所有用户的 CSS 结果禁用local()

    没有人喜欢等待内容显示。 使用font-display CSS 描述符,我们可以控制字体加载行为并使内容能够立即(使用font-display: optional )或几乎立即(超时 3 秒,只要字体被成功下载——使用font-display: swap )。 (嗯,它比这更复杂一些。)

    但是,如果您想尽量减少文本重排的影响,我们可以使用字体加载 API (在所有现代浏览器中都支持)。 具体来说,这意味着对于每种字体,我们将创建一个FontFace对象,然后尝试获取所有字体,然后才将它们应用到页面上。 这样,我们通过异步加载所有字体来对所有重绘进行分组,然后恰好从备用字体切换到网络字体一次。 看看 Zach 的解释,从 32:15 开始,以及代码片段):

    /* Load two web fonts using JavaScript */ /* Zach Leatherman: https://noti.st/zachleat/KNaZEg/the-five-whys-of-web-font-loading-performance#sWkN4u4 */ // Remove existing @font-face blocks // Create two let font = new FontFace("Noto Serif", /* ... */); let fontBold = new FontFace("Noto Serif, /* ... */); // Load two fonts let fonts = await Promise.all([ font.load(), fontBold.load() ]) // Group repaints and render both fonts at the same time! fonts.forEach(font => documents.fonts.add(font));
    /* Load two web fonts using JavaScript */ /* Zach Leatherman: https://noti.st/zachleat/KNaZEg/the-five-whys-of-web-font-loading-performance#sWkN4u4 */ // Remove existing @font-face blocks // Create two let font = new FontFace("Noto Serif", /* ... */); let fontBold = new FontFace("Noto Serif, /* ... */); // Load two fonts let fonts = await Promise.all([ font.load(), fontBold.load() ]) // Group repaints and render both fonts at the same time! fonts.forEach(font => documents.fonts.add(font));

    为了在使用 Font Loading API 的情况下尽早开始获取字体,Adrian Bece 建议添加一个不间断空格nbsp;body的顶部,并使用aria-visibility: hidden.hidden类在视觉上隐藏它:

    <body class="no-js"> <!-- ... Website content ... --> <div aria-visibility="hidden" class="hidden"> <!-- There is a non-breaking space here --> </div> <script> document.getElementsByTagName("body")[0].classList.remove("no-js"); </script> </body>
    <body class="no-js"> <!-- ... Website content ... --> <div aria-visibility="hidden" class="hidden"> <!-- There is a non-breaking space here --> </div> <script> document.getElementsByTagName("body")[0].classList.remove("no-js"); </script> </body>

    这与针对不同加载状态声明不同字体系列的 CSS 一起使用,一旦字体成功加载,更改由 Font Loading API 触发:

    body:not(.wf-merriweather--loaded):not(.no-js) { font-family: [fallback-system-font]; /* Fallback font styles */ } .wf-merriweather--loaded, .no-js { font-family: "[web-font-name]"; /* Webfont styles */ } /* Accessible hiding */ .hidden { position: absolute; overflow: hidden; clip: rect(0 0 0 0); height: 1px; width: 1px; margin: -1px; padding: 0; border: 0; }
    body:not(.wf-merriweather--loaded):not(.no-js) { font-family: [fallback-system-font]; /* Fallback font styles */ } .wf-merriweather--loaded, .no-js { font-family: "[web-font-name]"; /* Webfont styles */ } /* Accessible hiding */ .hidden { position: absolute; overflow: hidden; clip: rect(0 0 0 0); height: 1px; width: 1px; margin: -1px; padding: 0; border: 0; }

    如果您想知道为什么尽管进行了所有优化,Lighthouse 仍然建议消除渲染阻塞资源(字体),在同一篇文章中,Adrian Bece 提供了一些让 Lighthouse 满意的技术,以及 Gatsby Omni Font Loader,一种高性能异步字体Gatsby 的加载和 Flash Of Unstyled Text (FOUT) 处理插件。

    现在,我们中的许多人可能正在使用 CDN 或第三方主机来加载 Web 字体。 一般来说,如果可以的话,自托管所有静态资产总是更好,因此请考虑使用 google-webfonts-helper,这是一种自托管 Google 字体的轻松方式。 如果不可能,您也许可以通过页面来源代理 Google 字体文件。

    值得注意的是,虽然 Google 做了很多开箱即用的工作,所以服务器可能需要一些调整以避免延迟(谢谢,巴里!

    这一点非常重要,尤其是自 Chrome v86(2020 年 10 月发布)以来,由于浏览器缓存分区,字体等跨站点资源无法再在同一个 CDN 上共享。 这种行为多年来一直是 Safari 的默认行为。

    但是,如果根本不可能,有一种方法可以使用 Harry Roberts 的代码段获得最快的 Google 字体:

    <!-- By Harry Roberts. https://csswizardry.com/2020/05/the-fastest-google-fonts/ - 1. Preemptively warm up the fonts' origin. - 2. Initiate a high-priority, asynchronous fetch for the CSS file. Works in - most modern browsers. - 3. Initiate a low-priority, asynchronous fetch that gets applied to the page - only after it's arrived. Works in all browsers with JavaScript enabled. - 4. In the unlikely event that a visitor has intentionally disabled - JavaScript, fall back to the original method. The good news is that, - although this is a render-blocking request, it can still make use of the - preconnect which makes it marginally faster than the default. --> <!-- [1] --> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <!-- [2] --> <link rel="preload" as="style" href="$CSS&display=swap" /> <!-- [3] --> <link rel="stylesheet" href="$CSS&display=swap" media="print" onload="this.media='all'" /> <!-- [4] --> <noscript> <link rel="stylesheet" href="$CSS&display=swap" /> </noscript>

    Harry 的策略是先发制人地预热字体的起源。 然后我们为 CSS 文件启动一个高优先级的异步获取。 之后,我们启动一个低优先级的异步获取,该获取仅在页面到达后才应用于页面(使用打印样式表技巧)。 最后,如果不支持 JavaScript,我们将退回到原始方法。

    啊,说到谷歌字体:你可以通过&text只声明你需要的字符来减少谷歌字体请求的90% 的大小。 另外,最近谷歌字体也增加了对字体显示的支持,所以我们可以直接使用它。

    不过要注意一点。 如果您使用font-display: optional ,那么使用preload可能不是最佳选择,因为它会提前触发该 Web 字体请求(如果您有其他需要获取的关键路径资源,则会导致网络拥塞)。 使用preconnect来加快跨域字体请求,但要小心preload ,因为从不同来源预加载字体会导致网络争用。 所有这些技术都包含在 Zach 的 Web 字体加载配方中。

    另一方面,如果用户在可访问性首选项中启用了减少运动或选择了数据保护模式(请参阅Save-Data标题),则选择退出网络字体(或至少第二阶段渲染)可能是个好主意,或者当用户连接速度较慢时(通过网络信息 API)。

    如果用户选择了数据保存模式(还有其他用例),我们还可以使用prefers-reduced-data CSS 媒体查询来定义字体声明。 如果来自客户端提示 HTTP 扩展的Save-Data请求标头打开/关闭以允许与 CSS 一起使用,则媒体查询基本上会公开。 目前仅支持在 Chrome 和 Edge 后面的标志。

    指标? 要衡量 Web 字体加载性能,请考虑All Text Visible指标(所有字体已加载且所有内容以 Web 字体显示的时刻)、Time to Real Italics 以及首次渲染后的Web Font Reflow Count 。 显然,这两个指标越低,性能越好。

    你可能会问,可变字体呢? 重要的是要注意可变字体可能需要重要的性能考虑。 它们为我们提供了更广泛的印刷选择设计空间,但它的代价是单个串行请求而不是多个单独的文件请求。

    虽然可变字体大大减少了字体文件的整体组合文件大小,但该单个请求可能会很慢,从而阻止页面上所有内容的呈现。 所以子集化和将字体分割成字符集仍然很重要。 不过好的一面是,使用可变字体,默认情况下我们会得到一个重排,因此不需要 JavaScript 来对重绘进行分组。

    现在,怎样才能制定防弹的网络字体加载策略呢? 子集字体并为 2-stage-render 准备它们,使用字体font-display描述符声明它们,使用 Font Loading API 对重绘进行分组并将字体存储在持久服务工作者的缓存中。 在第一次访问时,在阻塞外部脚本之前注入脚本的预加载。 如有必要,您可以使用 Bram Stein 的 Font Face Observer。 如果您对测量字体加载的性能感兴趣,Andreas Marschke 将探索使用 Font API 和 UserTiming API 进行的性能跟踪。

    最后,不要忘记包含unicode-range以将大字体分解为较小的特定语言字体,并使用 Monica Dinculescu 的字体样式匹配器来最小化布局中的不和谐变化,因为后备和网页字体。

    或者,要为备用字体模拟 Web 字体,我们可以使用 @font-face 描述符来覆盖字体指标(演示,在 Chrome 87 中启用)。 (请注意,调整会因复杂的字体堆栈而变得复杂。)

    未来看起来光明吗? 通过渐进式字体丰富,最终我们可能能够“在任何给定页面上仅下载所需的字体部分,并且对于该字体的后续请求,可以根据后续页面的要求使用额外的字形集动态地‘修补’原始下载意见”,正如 Jason Pamental 解释的那样。 增量传输演示已经可用,并且正在进行中。

目录

  1. 准备:计划和指标
  2. 设定切合实际的目标
  3. 定义环境
  4. 资产优化
  5. 构建优化
  6. 交付优化
  7. 网络、HTTP/2、HTTP/3
  8. 测试和监控
  9. 快速获胜
  10. 一切都在一页上
  11. 下载清单(PDF、Apple Pages、MS Word)
  12. 订阅我们的电子邮件通讯不要错过下一个指南。