构建有效的图片组件

图片组件封装了性能最佳实践,并提供开箱即用的解决方案来优化图片。

Leena Sohoni
Leena Sohoni
Kara Erickson
Kara Erickson
Alex Castle
Alex Castle

图片是 Web 应用出现性能瓶颈的常见原因,也是需要重点关注的方面。未经优化的图片会导致网页膨胀,目前占网页总大小(以字节为单位)的 70% 以上(第 90 百分位)。有多种图片优化方法需要一个智能的“图片组件”,其中默认内置了性能解决方案。

Aurora 团队与 Next.js 合作构建了一个此类组件。我们的目标是创建一个经过优化的图片模板,供 Web 开发者进一步自定义。该组件可用作良好的模型,并为在其他框架、内容管理系统 (CMS) 和技术栈中构建图片组件设定了标准。我们已就 Nuxt.js 的类似组件开展合作,并在未来版本中与 Angular 合作优化图像。这篇博文讨论了我们如何设计 Next.js 图片组件以及在此过程中学到的经验。

作为图片扩展的图片组件

图片优化问题和优化机会

图片不仅会影响效果,还会影响业务。在访问网站的用户中,网页上的图片数量是转化次数第二大预测指标。用户发生转化的会话中的图片比未转化的会话少 38%。在最佳实践审核中,Lighthouse 列出了多个优化图片和改进 Web Vitals机会。图片会影响核心网页指标的一些常见区域和用户体验如下。

图片尺寸过大会破坏 CLS

未指定尺寸提供的图片可能会导致布局不稳定,并导致严重的累积布局偏移 (CLS)。为 img 元素设置 widthheight 属性有助于防止布局偏移。例如:

<img src="flower.jpg" width="360" height="240">

应设置宽度和高度,使所渲染图片的宽高比接近其自然宽高比。如果宽高比存在显著差异,可能会导致图片失真。使用相对较新的属性,可让您指定 CSS 中的宽高比,有助于自适应调整图片大小,同时防止出现 CLS。

大型图片可能会破坏 LCP

图片的文件大小越大,下载所需的时间就越长。大图片可以是网页的“主打”图片,也可以是视口中负责触发 Largest Contentful Paint (LCP) 的最重要元素。如果图片是关键内容的一部分并且下载时间很长,则会延迟 LCP。

在许多情况下,开发者可以通过更好地压缩和使用响应式图片来减小图片大小。<img> 元素的 srcsetsizes 属性有助于提供不同大小的图片文件。浏览器随后会根据屏幕尺寸和分辨率选择合适的图片。

图片压缩效果不佳会影响 LCP

与常用的 JPEG 和 PNG 格式相比,AVIFWebP 等现代图片格式可以提供更好的压缩效果。在某些情况下,如果图片质量相同,压缩改善后,文件大小会减少 25% 到 50%。这样可以提高下载速度和流量消耗。应用应向支持新式图片格式的浏览器提供新式图片格式

加载不必要的图片会破坏 LCP

网页加载时,非首屏或不在视口中的图片不会向用户显示。可以延迟它们,这样它们不会对 LCP 产生影响,就会延迟。当用户滚动到此类图片时,可使用延迟加载方法。

优化难题

团队可以评估前面列出的问题导致的性能成本,并实施最佳实践解决方案来解决这些问题。然而,在实践中往往不会发生这种情况,效率低下的图像会继续降低网页速度。可能的原因包括:

  • 优先事项:Web 开发者通常注重代码、JavaScript 和数据优化。因此,他们可能不知道图片存在问题或优化方法。由设计师创作或由用户上传的图片在优先级列表中可能并不是很高。
  • 开箱即用的解决方案:即使开发者知道图片优化的细微差别,缺少适用于其框架或技术栈的一体化开箱即用解决方案可能会成为阻碍。
  • 动态图片:除了应用中静态图片外,动态图片是由用户上传或从外部数据库或内容管理系统 (CMS) 中获取的动态图片。如果此类图片的来源是动态的,则定义这类图片的尺寸可能颇具挑战性。
  • 标记过载:如需添加图片尺寸或针对不同尺寸添加 srcset,解决方案需要为每张图片添加额外的标记,这可能会非常繁琐。srcset 属性于 2014 年推出,但目前只有 26.5% 的网站使用了此属性。使用 srcset 时,开发者必须创建各种尺寸的图片。just-gimme-an-img 等工具可能会有所帮助,但您必须手动处理每张图片。
  • 浏览器支持:AVIF 和 WebP 等现代图片格式创建的图片文件更小,但在不支持它们的浏览器上需要特殊处理。开发者必须使用内容协商<picture> 元素等策略,以便将图片投放到所有浏览器。
  • 延迟加载复杂功能:有多种技术和库可用于为非首屏图片实现延迟加载。挑选最佳视频可能并非易事。此外,开发者可能也不知道加载延迟图片时与“折叠边”之间的最佳距离。设备上不同的视口尺寸会使问题更复杂。
  • 不断变化的形势:随着浏览器开始支持新的 HTML 或 CSS 功能以提升性能,开发者可能很难对每项功能进行评估。例如,Chrome 将推出提取优先级功能作为一项源试用。它可用于提高网页上特定图片的优先级。总体而言,开发者会发现如果在组件级别评估和实现此类增强功能,会更容易。

作为解决方案的图片组件

由于有机会优化图像,以及在为各个应用单独实现图像方面存在的挑战,我们萌生了图像组件的想法。图片组件可以封装并强制执行最佳实践。通过将 <img> 元素替换为图片组件,开发者可以更好地解决图片性能方面的问题。

在过去的一年里,我们使用 Next.js 框架来设计和implement他们的图片组件。它可以直接替换 Next.js 应用中现有 <img> 元素,如下所示。

// Before with <img> element:
function Logo() {
  return <img src="/logo.jpg" alt="logo" height="200" width="100" />
}

// After with image component:
import Image from 'next/image'

function Logo() {
  return <Image src="/logo.jpg" alt="logo" height="200" width="100" />
}

该组件尝试通过一组丰富的功能和原则一般来解决与图片相关的问题。它还包含一些选项,让开发者能够根据各种图片要求对其进行自定义。

防止布局偏移

如前所述,未调整尺寸的图片会导致布局偏移,并影响 CLS。使用 Next.js 图片组件时,开发者必须使用 widthheight 属性提供图片尺寸,以防止任何布局偏移。如果大小未知,开发者必须指定 layout=fill,以传送位于大小容器内的未调整大小的图片。或者,您也可以使用静态映像导入在构建时检索硬盘上实际映像的大小,并将其包含在映像中。

// Image component with width and height specified
<Image src="/logo.jpg" alt="logo" height="200" width="100" />

// Image component with layout specified
<Image src="/hero.jpg" layout="fill" objectFit="cover" alt="hero" />

// Image component with image import
import Image from 'next/image'
import logo from './logo.png'

function Logo() {
  return <Image src={logo} alt="logo" />
}

由于开发者无法使用未调整大小的图片组件,因此设计可确保他们能够花时间考虑图片大小调整并防止布局偏移。

促进响应

如需让图片能够在各种设备上自适应显示,开发者必须在 <img> 元素中设置 srcsetsizes 属性。我们希望使用图片组件减少这项工作。我们将 Next.js 图片组件设计为对每个应用仅设置一次属性值。我们会根据布局模式将它们应用到图片组件的所有实例。我们提出了一个由三部分组成的解决方案:

  1. deviceSizes 属性:此属性可用于根据应用用户群常用的设备一次性配置断点。配置文件中包含断点的默认值。
  2. imageSizes 属性:这也是一个可配置的属性,用于获取与设备尺寸断点对应的图片大小。
  3. 每张图片中的 layout 属性:此属性用于指示如何为每张图片使用 deviceSizesimageSizes 属性。布局模式支持的值包括 fixedfillintrinsicresponsive

当使用 responsefill 布局模式请求图片时,Next.js 会根据请求网页的设备的尺寸识别要传送的图片,并在图片中适当设置 srcsetsizes

以下比较展示了如何使用布局模式来控制图片在不同屏幕上的大小。我们使用了 Next.js 文档中分享的演示图片,可在手机和标准笔记本电脑上查看。

笔记本电脑屏幕 手机屏幕
Layout = Intrinsic:缩小以在较小的视口上适应容器的宽度。在较大的视口上,放大后不会超出图片的固有尺寸。容器宽度为 100%
原样展示山脉图片 按比例缩小的山脉图片
布局 = 已修正:图片没有响应。宽度和高度是固定的,类似于“”元素,无论其渲染设备是什么。
原样展示山脉图片 显示的山脉图片不适合屏幕
布局 = 自适应:根据容器在不同视口上的宽度缩小或放大,并保持宽高比。
已放大以适应屏幕的山脉图片 山脉图片已按比例缩小以适应屏幕
Layout = Fill:宽度和高度将拉伸以填充父级容器。(父级 `
` 在此示例中设置为 300*500)
渲染为 300*500 尺寸的山脉图片 渲染为 300*500 尺寸的山脉图片
针对不同布局呈现的图片

提供内置的延迟加载

图片组件提供了一个内置的高性能延迟加载解决方案作为默认解决方案。使用 <img> 元素时,有一些原生选项可用于延迟加载,但它们都有缺点,使用起来很困难。开发者可以采用以下延迟加载方法之一:

  • 指定 loading 属性:该方法易于实现,但目前在某些浏览器上不受支持
  • 使用 Intersection Observer API:构建自定义延迟加载解决方案需要花费精力并经过精心的设计和实现。开发者并不一定有时间这样做。
  • 导入第三方库以延迟加载图片:为了评估和集成合适的第三方库来实现延迟加载,您可能需要执行额外的操作。

在 Next.js 图片组件中,加载默认设置为 "lazy"。延迟加载是使用 Intersection Observer 实现的,大多数现代浏览器上均有提供。开发者无需执行任何额外操作即可启用这项功能,但可以根据需要停用。

预加载重要图片

在很多情况下,LCP 元素都是图片,而大型图片可能会导致 LCP 延迟。建议您预加载关键图片,以便浏览器可以更快地发现该图片。使用 <img> 元素时,HTML 标头中可能会包含预加载提示,如下所示。

<link rel="preload" as="image" href="important.png">

无论使用何种框架,设计良好的图片组件都应提供一种方式来调整图片的加载顺序。对于 Next.js 图片组件,开发者可以使用图片组件的 priority 属性指明适合预加载的图片。

<Image src="/hero.jpg" alt="hero" height="400" width="200" priority />

添加 priority 属性可以简化标记,而且更方便使用。图片组件开发者还可以探索各种选项,运用启发法自动预加载符合特定条件的网页上的首屏图片。

鼓励高性能图片托管

建议使用图片 CDN 自动优化图片,而且此类 CDN 还支持 WebP 和 AVIF 等现代图片格式。默认情况下,Next.js 图片组件使用加载器架构使用图片 CDN。以下示例显示加载器允许在 Next.js 配置文件中配置 CDN。

module.exports = {
  images: {
    loader: 'imgix',
    path: 'https://ImgApp/imgix.net',
  },
}

借助此配置,开发者可在图片来源中使用相对网址,而框架会将相对网址与 CDN 路径串联,以生成绝对网址。支持 ImgixCloudinaryAkamai 等热门图片 CDN。该架构通过为应用实现自定义 loader 函数,支持使用自定义云服务提供商。

支持自行托管的图片

在某些情况下,网站无法使用图片 CDN。在这种情况下,图片组件必须支持自托管的图片。Next.js 图片组件将图片优化器用作内置图片服务器,提供类 CDN 的 API。如果优化器安装在服务器上,则会使用 Sharp 进行生产图片转换。对于希望构建自己的图像优化流水线的人来说,此库是一个不错的选择。

支持渐进式加载

渐进式加载是一种用于在实际加载图片时,显示质量通常显著降低的占位图片,以此来保持用户兴趣。提升用户体验并改善用户体验。该功能可与非首屏图片或首屏图片的延迟加载结合使用。

Next.js 图片组件通过 placeholder 属性支持渐进式加载图片。此参数可用作 LQIP(低画质图片占位符),用于在实际图片加载时显示低质量或经过模糊处理的图片。

影响

整合上述所有优化措施后,我们已经在生产环境中使用 Next.js 图片组件取得了成功,并且正在与其他技术栈合作处理类似的图片组件。

Leboncoin 将其旧版 JavaScript 前端迁移到 Next.js 时,他们还升级了图像流水线,以使用 Next.js 图片组件。在从 <img> 迁移到下一个/图片的网页上,LCP 从 2.4 秒缩短到了 1.7 秒。为该网页下载的总图片字节数从 663 kB 降至 326 kB(约 100 kB 的延迟加载图片字节)。

经验教训

任何创建 Next.js 应用的用户都可以因使用 Next.js 图片组件进行优化而受益。不过,如果您想为其他框架或 CMS 构建类似的性能抽象,我们在此过程中学到的一些课程可能会对您有所帮助。

安全阀的危害大于安全阀

在 Next.js 图片组件的早期版本中,我们提供了一个 unsized 属性,让开发者能够绕过尺寸要求,并使用未指定尺寸的图片。我们认为,在无法提前知道图片的高度或宽度的情况下,有必要这样做。不过,我们注意到,用户建议将 GitHub 问题中的 unsized 属性用作解决大小要求问题的万能解决方案,即使他们能够以不会加深 CLS 的方式解决问题。我们随后废弃并移除了 unsized 属性。

将有用的阻力与无意义的烦恼分开

调整图片大小的要求就是一种“实用摩擦”的示例。它虽然限制了组件的使用,但提供了巨大性能优势作为交换。如果用户清楚了解潜在的性能优势,他们将很乐意接受该限制条件。因此,有必要在关于该组件的文档和其他发布材料中对此权衡进行说明。

不过,您可以在不牺牲性能的情况下找到解决办法。例如,在开发 Next.js 图片组件期间,我们收到了一些投诉,称查询本地存储的图片尺寸会让人感到厌烦。我们添加了静态图片导入功能,该功能可在构建时使用 Babel 插件自动检索本地图片的尺寸,从而简化此过程。

在便捷功能和性能优化之间取得平衡

如果您的图片组件只不过会给用户带来“不必要的摩擦”,开发者往往不会使用它。我们发现,图片大小调整和自动生成 srcset 值等性能特征是最重要的。自动延迟加载和内置模糊占位符等面向开发者的便捷功能也激发了对 Next.js 图片组件的兴趣。

制定功能路线图以提高采用率

构建一个可完美适用于所有情况的解决方案非常困难。我们很想设计一种适合 75% 人的产品,然后告诉另外 25% “在这些情况下,此组件不适合你”。

实际上,这种策略与你作为组件设计人员的目标不符。您希望开发者采用您的组件,以便从其性能优势中受益。如果存在一些用户无法迁移并感觉自己被排除在对话之外,这种情况将很难实现。他们可能会感到失望,从而导致负面看法影响产品采用率。

建议您制定一份组件路线图,涵盖所有合理的长期使用案例。此外,在文档中明确说明不受支持的资源和原因也有助于用户了解该组件旨在解决的问题。

总结

图片的使用和优化比较复杂。开发者必须在确保卓越用户体验的同时,兼顾图片的性能和质量。这使得图片优化成为一项成本高、成效显著的工作。

我们没有让每个应用每次都重复开发,而是想出一个最佳实践模板,开发者、框架和其他技术栈可以参考该模板来为自己的实现提供参考。这种体验确实很有价值,因为我们在图像组件上支持其他框架。

Next.js 图片组件已成功地改进了 Next.js 应用的性能结果,从而提升了用户体验。我们相信这是一个能在更广泛的生态系统中取得良好效果的出色模型,并且希望了解希望在其项目中采用此模型的开发者的反馈。