使用 Angular Image 指令优化图片

卡拉·埃里克森
Kara Erickson
莉娜·索霍尼
Leena Sohoni

2022 年 5 月,Aurora 团队和 Angular 团队宣布将就 Angular 的图片指令展开协作。最近,该指令已作为 Angular v14.2 的一部分发布给开发者预览版。这篇博文探讨了新的图片指令 NgOptimizedImage 如何在 Angular 中支持图片优化。

背景

图片是网络用户体验中一种常见且至关重要的组成部分,其中 99.9% 的网页会生成对一张或多张图片的请求。图片也是网页重量的主要影响因素,每页图片的平均大小为 982 KB

由于图片的数量和大小在不断增加,可能会降低网页的性能并影响核心网页指标指标。在 79.4% 的桌面版网页中,图片是 2021 年 Largest Contentful Paint (LCP) 元素。因此,追求经过优化的图片成为我们许多人的持续努力。

Aurora 团队坚信,如何利用框架的强大功能提供内置解决方案来应对常见的开发者挑战。他们首次涉足图片优化领域是 Next.js 图片组件。他们认为此组件是一个测试平台,用于评估改进图像优化的开发者体验 (DX) 能否让更多使用框架的应用实现性能提升。

Next.js 用户 Leboncoin 给出的第一组结果鼓舞人心。Leboncoin 开始使用 next/image 后,LCP 显著提升(从 2.4 秒缩短到 1.7 秒)。随后,在社区中采用 next/image 对满足 LCP 阈值的 Next.js 源数量不断增加。很快,其他框架中就出现了对类似功能的请求,其中之一就是 Angular

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

Opportunity

Angular 是当今开发者使用的主要 JavaScript 框架之一。有超过 5 万个源站HTTPArchive 抓取至在移动设备上使用,并且 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% 的移动网页使用预加载提示。

    将图像标记为优先级时,图像指令将作用于两个前端。

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

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

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

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

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

    该指令鼓励使用图片 CDN,方法是提供特别吸引人的开发者体验 (DX),以便在应用中对其进行配置。该指令支持加载程序 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. 图片大小未调整:如果图片标记没有定义明确的宽度和高度,则图片指令会抛出错误。调整图片大小可能会导致布局偏移,从而影响页面的 Cumulative Layout Shift (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> 的呈现时间会太晚,无法达到任何值。

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

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

    由于 Angular 应用通常是由客户端渲染的,文件系统上的图片无法在请求时进行压缩,而是按原样提供。因此,建议使用图片 CDN 压缩图片,并按需将其转换为 WebP 或 AVIF 等现代格式

    虽然该指令并不强制使用图片 CDN,但强烈建议将其与 指令一起使用,并且它的内置加载器可确保使用正确的配置选项。

影响

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

Website One:将原生 <img> 元素与通过 Imgix CDN 提供的图片(采用默认配置选项)搭配使用。

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

幻灯影片对比:使用原生图片代码的网站 One 与使用 Angular 图片指令的网站 Two。

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

Land's End 就是其中之一。他们希望他们的网站是一个很好的测试用例,用于测试真实应用可能看到的结果。

在使用图片指令之前和之后,Lighthouse 实验室测试在其 QA 环境中执行。在桌面设备上,他们的 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/通用)的图片优化解决方案也非常重要。

  • 改善开发者体验

    NgOptimizedImage 要求为每张图片指定 widthheight 属性。但是,为每个图像指定这些标记对某些开发者来说可能很麻烦。该解决方案有望在下一次迭代中改善开发者体验,具体如下:

    1. 支持不需要定义明确宽度/高度的其他模式(类似于 Next.js 中的“fill”图片布局选项)。
    2. 借助 CLI 集成,确定图片的实际尺寸,从而自动设置本地图片的宽度和高度。

总结

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

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