使用 Angular Image 指令优化图片

Kara Erickson
Kara Erickson
Leena Sohoni
Leena Sohoni

2022 年 5 月,Aurora 和 Angular 团队宣布,他们将共同为 Angular 开发图片指令。该指令最近作为 Angular v14.2 的一部分发布了开发者预览版。本文介绍了新的图片指令 NgOptimizedImage 如何在 Angular 中支持图片优化。

背景

图片是 Web 用户体验的常见且关键组成部分,99.9% 的网页都会生成一个或多个图片的请求。图像也是导致网页重量过大的主要原因,平均每个网页的图像大小为 982 千字节

由于图像数量和大小不断增加,因此可能会影响网页的性能并影响 Core Web Vitals 指标。在 2021 年,79.4% 的桌面版网页的 Largest Contentful Paint (LCP) 元素是图片。因此,追求优化后的图片已成为许多人的不懈追求。

Aurora 团队相信,可以利用框架的强大功能,为开发者常见的挑战提供内置解决方案。他们首次涉足图片优化领域的是 Next.js 图片组件。他们将此组件视为测试平台,以便确定改进图片优化的开发者体验 (DX) 能否让更多使用框架的应用获得更出色的性能。

Next.js 用户 Leboncoin 的第一组结果令人鼓舞。在开始使用 next/image 后,Leboncoin 的 LCP 显著改善(从 2.4 秒缩短到 1.7 秒)。社区随后采用 next/image 有助于提高符合 LCP 阈值的 Next.js 源的数量。很快,其他框架中就出现了请求提供类似功能,其中之一就是 Angular

因此,Aurora 咨询了 Angular 和 Nuxt,为这些框架设计了图片组件原型。Nuxt 图片组件于去年发布。现在,Angular 图片指令 (NgOptimizedImage) 已发布,可将图片优化默认设置为 Angular。

机会

Angular 是当今开发者使用的领先 JavaScript 框架之一。HTTPArchive 在移动设备上抓取的超过 5 万个源都使用了该库,并且 NPM 上的每周下载量高达近 300 万次

过去一年内 Angular 网站的 LCP。

从 Core Web Vitals 得分来看,符合“良好”LCP 阈值的 Angular 来源所占的百分比仍有待提高。2022 年 6 月,只有 18.74% 的 Angular 网站在移动设备上的 LCP 得分较高。由于超过 70% 的移动版和桌面版网页的 LCP 元素是图片,因此未优化的 LCP 图片可能是导致 Angular 网站 LCP 较差的主要原因之一。

Angular 图片指令旨在帮助提高这些指标。

NgOptimizedImage 指令的 MVP

Angular 图片指令的 MVP 基于 Aurora 迄今为止构建的图片组件中总结出的经验,同时将设计调整为适应 Angular 的客户端渲染体验。许多标准的图片优化问题已通过以下任一方式得到解决:

  • 提供强大的默认值。
  • 抛出错误或警告,以确保符合最佳实践。

设计亮点如下:

  1. 智能延迟加载

    在网页加载时对用户不可见的图片(例如,折叠下方的图片或隐藏的轮播界面图片)最好采用延迟加载方式。延迟加载可释放浏览器资源,以加载其他关键文本、媒体或脚本。大多数图片都不是关键图片,应采用延迟加载方式,但在 2021 年,只有 7.8% 的网页使用了原生延迟加载。

    Angular 图片指令默认延迟加载非关键图片,并且仅提前加载特别标记为 priority 的图片。这样可以确保大多数图片都能以最佳方式加载。

  2. 确定关键图片的优先级

    添加资源提示(例如preloadpreconnect)来优先加载关键图片,这是推荐的最佳实践。不过,大多数应用都没有使用它们。根据《2021 年网络年鉴》,只有 12.7% 的移动网页使用预连接提示,只有 22.1% 的移动网页使用预加载提示。

    将图片标记为优先级图片后,图片指令会在两个方面发挥作用。

    • 它会将图片的 fetchpriority 设置为 "high",以便浏览器知道应以高优先级下载图片。
    • 在开发模式下,运行时检查会确认是否已包含与图片来源对应的 preconnect 资源提示。

    在开发模式下,该指令还会使用 PerformanceObserver API 来验证 LCP 图片是否已按预期标记为 priority。如果未标记为 priority,系统会抛出错误,指示开发者向 LCP 图片添加 priority 属性。

    最终,这种自动化与合规性的结合可确保 LCP 图片具有 preconnect 提示、fetchpriority 属性值为 high,并且不会延迟加载。

  3. 针对常用图片工具进行了优化的配置

    建议 Angular 应用使用图片 CDN,它们通常默认提供优化服务。

    该指令通过提供在应用中配置图片 CDN 的特别吸引人的开发者体验 (DX),鼓励使用图片 CDN。它支持加载器 API,可让您在配置中定义 CDN 提供程序和基本网址。配置完成后,您只需在标记中定义资源名称即可。例如,

    // in module providers:
    provideImgixLoader('https://mysite.net/assets/')
    
    // in markup
    <img ngSrc="image.png" >
    <img ngSrc="image2.png" >
    

    这相当于添加以下图片标记,并减少了开发者必须为每张图片添加的标记。

    <img src="https://mysite.net/assets/image.png">
    <img src="https://mysite.net/assets/image2.png">
    

    image 指令为最常用的图片 CDN 提供了内置加载器的最佳配置。这些加载器会自动设置图片网址的格式,以确保为每个 CDN 使用推荐的图片格式和压缩设置。

  4. 内置错误和警告

    除了上述内置优化之外,该指令还具有内置检查,以确保开发者在图片标记中遵循了推荐的最佳实践。image 指令会执行以下检查。

    1. 未设尺寸的图片:如果图片标记未明确定义宽度和高度,图片指令会抛出错误。未调整大小的图片可能会导致布局偏移,进而影响网页的累积布局偏移 (CLS) 指标。为防止出现这种情况,建议的最佳实践是,图片应指定 widthheight 属性。

    2. 宽高比:如果 HTML 中定义的 width:height 的宽高比与渲染的图片的实际宽高比不接近,图片指令会抛出错误,以告知开发者。这可能会导致图片在屏幕上看起来失真。以下情况可能会导致这种情况:

      1. 您不小心定义了错误的尺寸(宽度或高度),或者
      2. 如果您在 CSS 中按百分比定义了尺寸,但未定义另一个尺寸(例如,width: 100% 需要 height: auto 才能确保图片在两个尺寸上都放大)。
    3. 超大图片:如果图片未定义 srcset,并且内在图片明显大于渲染的图片,该指令将显示一条警告,建议使用 srcsetsizes 属性。

    4. 图片密度:如果您尝试在 srcset 中添加像素密度超过 3x 的图片,该指令会抛出错误。通常不建议使用高于 2x 的描述符,因为这会导致高分辨率移动设备下载巨大的图片,从而产生意外后果。此外,人眼实际上无法区分 2 倍以上的差异

挑战

在设计 NgOptimizedImage 时,将图像优化策略调整为在客户端框架中运行是一项主要挑战。Next.js 上的默认呈现体验是服务器端呈现 (SSR) 或静态网站生成 (SSG),而 Angular 上的默认呈现体验是客户端呈现 (CSR)。尽管 Angular 支持 SSR 库 angular/universal,但大多数 Angular 应用(约 60%)都使用 CSR。

image 指令完全是为 CSR 而构建的,以符合 Angular 应用中的典型用例。这增加了额外的约束条件,因此该团队不得不重新考虑如何为 CSR 应用构建特定优化。

遇到的一些挑战如下:

  1. 支持资源提示

    预加载关键素材资源有助于浏览器更早发现这些资源。不过,在 Angular 应用中添加资源提示很复杂,因为:

    手动添加:开发者很难手动添加 preload 资源提示。Angular 会为整个项目或网站中的所有路由使用一个共享的 index.html 文件。因此,每个路线的文档 <head> 都是相同的(至少在投放时是相同的)。向 <head> 添加任何 preload 提示意味着系统会为所有路由预加载资源,即使不需要该资源也是如此。因此,不建议手动添加 preload 提示。

    渲染期间自动添加:在 CSR 应用中,使用该框架在渲染期间向文档的标头添加预加载提示没有帮助。由于呈现是在下载和执行 JavaScript 后进行的,因此 <head> 的呈现时间过晚,没有任何价值。

    对于该指令的第一个版本,preconnectfetchpriority 提示的组合可用于优先显示图片,而非 preload。不过,Aurora 目前正在与 Angular CLI 团队合作,以便在构建时自动注入资源提示。敬请期待!

  2. 优化服务器上的图片大小和格式

    由于 Angular 应用通常在客户端呈现,因此文件系统中的图片无法在请求时压缩,而是按原样提供。因此,建议使用图片 CDN 来压缩图片,并根据需要将其转换为 WebP 等新型格式或 AVIF。

    虽然该指令不会强制使用图片 CDN,但我们强烈建议您将其与 CDN 搭配使用,并确保使用该指令的内置加载器来使用正确的配置选项。

影响

以下演示展示了 Angular 图片指令对图片性能的影响。它会比较以下两个网站:

网站一:使用通过 Imgix CDN 提供的图片的原生 <img> 元素(使用默认配置选项)。

网站二:对所有图片使用 image 指令。其中还包括指令抛出的警告或错误直接建议的优化。

幻灯片对比:使用原生图片标记的网站一与使用 Angular 图片指令的网站二。

该团队与合作伙伴合作,验证了图片指令对真实企业 Angular 应用的性能影响。

其中一个合作伙伴就是 Land's End。我们希望他们的网站能成为一个很好的测试用例,以便了解真实应用可能会出现的结果。

在使用图片指令之前和之后,他们对质量检查环境进行了 Lighthouse 实验室测试。在桌面设备上,其 LCP 中位数从 12.0 秒缩短到了 3.0 秒,LCP 提升了 75%。在移动设备上,LCP 中位数从 20.2 秒缩短至 12.0 秒(改善幅度为 40.6%)。

未来路线图

这只是 Angular 图片指令设计的第一部分。我们计划在未来版本中提供许多其他功能,包括:

  • 更好地支持自适应图片

    NgOptimizedImage 目前支持使用 srcset,但必须为每张图片手动提供 srcsetsizes 属性。未来,该指令可以自动生成 srcsetsizes 属性。

  • 自动注入资源提示

    您或许可以与 Angular CLI 集成,为重要的 LCP 图片生成预连接和预加载代码。

  • 支持 Angular SSR

    MVP 版本的设计充分考虑了 Angular CSR 约束条件,但探索适用于 Angular SSR (angular/universal) 的图片优化解决方案也很重要。

  • 开发者体验改进

    NgOptimizedImage 要求为每张图片指定 widthheight 属性。不过,对于某些开发者来说,为每张图片指定这些信息可能会很麻烦。在下一次迭代中,我们有望改进此处的开发者体验,具体如下:

    1. 支持一种不需要明确定义宽度/高度的额外模式(类似于 Next.js 中的“fill”图片布局选项)。
    2. 使用 CLI 集成通过确定图片的实际尺寸,自动为本地图片设置宽度和高度。

总结

Angular 图片指令将分阶段面向开发者提供,从 v14.2.0 中的开发者预览版开始。欢迎试用 NgOptimizedImage 并留下反馈!

特别感谢 Katie Hempenius 和 Alex Castle 的贡献。