使用 Angular Image 指令优化图片

Kara Erickson
Kara Erickson
Leena Sohoni
Leena Sohoni

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

背景

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

由于图片的数量和大小在不断增加,可能会妨碍网页性能并影响核心网页指标指标。在 2021 年,79.4% 的桌面版网页被认为是 Largest Contentful Paint (LCP) 元素。因此,我们许多人都在不懈地追求图像优化。

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

Next.js 用户 Leboncoin 的第一组结果令人鼓舞。Leboncoin 开始使用 next/image 后,LCP 有显著提升(从 2.4 秒缩短到 1.7 秒)。随后社区中对 next/image 的采用,促使 Next.js 源站不断达到 LCP 阈值。不久之后,就在其他框架中也收到了对类似功能的请求,其中一个是 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% 的移动网页使用了预加载提示。

    当图片被标记为“优先”时,image 指令会应用于两个正面。

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

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

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

  3. 针对热门图片工具优化了配置

    建议 Angular 应用使用图片 CDN,这些图片 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">
    

    映像指令为最常用的映像 CDN 提供具有最佳配置的内置加载器。这些加载程序会自动设置图片网址的格式,以确保为每个 CDN 使用建议的图片格式和压缩设置。

  4. 内置错误和警告

    除了上述内置优化外,该指令还内置了检查功能,以确保开发者遵循了图片标记中建议的最佳实践。image 指令执行以下检查。

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

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

      1. 您不小心定义了错误的尺寸(宽度或高度),或者
      2. 如果您在 CSS 中按百分比定义了一个维度,但未在 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 提供的图片搭配使用(采用默认配置选项)。

网站二:对所有图片使用映像指令。它还包含由指令引发的警告或错误直接推荐的优化。

幻灯影片对比:使用原生图片标记的网站一与使用 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(角度/通用)的图片优化解决方案也很重要。

  • 开发者体验改进

    NgOptimizedImage 要求为每个图片指定 widthheight 属性。不过,为每张图片指定这些参数可能会让某些开发者感到厌烦。在下一次迭代中,有可能会改善开发者体验,如下所示:

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

总结

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

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