构建有效的图片组件

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

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

对于 Web 应用而言,图片是常见的性能瓶颈,也是优化的重点领域。未优化的图片会导致网页数据加载量过大,在第 90 百分位,图片占网页总页面重量的 70% 以上(以字节为单位)。th多种优化图片的方式需要一个智能的“图片组件”,其中默认内置了性能解决方案。

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

作为图片扩展的图片组件

图片优化问题和机会

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

未设置尺寸的图片会影响 CLS

如果提供的图片未指定大小,可能会导致布局不稳定,并导致累积布局偏移 (CLS) 较高。在 <img> 元素上设置 width 和 height 属性有助于防止布局偏移。例如:

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

应设置宽度和高度,使渲染后的图像的宽高比接近其自然宽高比。如果宽高比差异过大,可能会导致图片看起来变形。利用一项相对较新的属性,您可以在 CSS 中指定宽高比,从而以自适应方式调整图片大小,同时防止 CLS。

大型图片可能会影响 LCP

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

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

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

AVIF 或 WebP 等现代图片格式的压缩效果优于常用的 JPEG 和 PNG 格式。在某些情况下,更好的压缩技术可将文件大小缩减 25% 到 50%,同时保持相同的图片质量。这种缩减可减少数据消耗,从而加快下载速度。应用应向支持这些格式的浏览器提供新式图片格式。

加载不必要的图片会影响 LCP

当网页加载时,折叠下方或视口中的图片不会显示给用户。可以延迟加载它们,以免它们影响 LCP 并导致延迟。延迟加载可用于在用户滚动到此类图片时再加载它们。

优化挑战

团队可以评估上述问题造成的性能开销,并实施最佳实践解决方案来克服这些问题。不过,在实际应用中,这种情况往往不会发生,低效的图片仍会减慢网页速度。可能的原因包括:

  • 优先事项:Web 开发者通常倾向于专注于代码、JavaScript 和数据优化。因此,他们可能不知道图片存在问题,也不知道如何优化图片。由设计师创建或用户上传的图片可能不会在优先级列表中排名靠前。
  • 开箱即用型解决方案:即使开发者了解图片优化的细微差别,但如果其框架或技术堆栈没有开箱即用型的一体化解决方案,也可能会让他们望而却步。
  • 动态图片:除了作为应用一部分的静态图片之外,动态图片还包括用户上传的图片或来自外部数据库或 CMS 的图片。如果图片来源是动态的,则可能难以确定此类图片的大小。
  • 标记过载:若要包含图片大小或 srcset 的不同尺寸,需要为每张图片添加额外的标记,这可能会很繁琐。srcset 属性于 2014 年推出,但如今只有 26.5%的网站使用。使用 srcset 时,开发者必须制作各种尺寸的图片。just-gimme-an-img 等工具可以提供帮助,但必须针对每张图片手动使用。
  • 浏览器支持:AVIF 和 WebP 等现代图片格式可创建更小的图片文件,但需要在不支持这些格式的浏览器上进行特殊处理。开发者必须使用内容协商或 <picture> 元素等策略,以便向所有浏览器提供图片。
  • 延迟加载复杂性:有多种技术和库可用于实现折叠线下图片的延迟加载。选择最佳选项可能是一项挑战。开发者可能也不知道加载延迟加载图片的最佳距离。设备上不同的视口大小会使情况更加复杂。
  • 不断变化的格局:随着浏览器开始支持新的 HTML 或 CSS 功能以提升性能,开发者可能很难评估每项功能。例如,Chrome 正在以源试用的形式推出 Fetch Priority 功能。它可用于提高网页上特定图片的优先级。总而言之,如果能在组件级别评估和实现此类增强功能,开发者会觉得更轻松。

图片组件作为解决方案

优化图片的机会以及为每个应用单独实现这些优化的挑战,促使我们想到了图片组件。图片组件可以封装并强制执行最佳实践。通过将 <img> 元素替换为图片组件,开发者可以更好地解决图片性能问题。

在过去一年中,我们与 Next.js 框架合作,设计并实现了其 Image 组件。它可以作为 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 Image 组件时,开发者必须使用 width 和 height 属性提供图片大小,以防止任何布局偏移。如果大小未知,开发者必须指定 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" />
}

由于开发者无法使用未设置尺寸的 Image 组件,因此该设计可确保他们花时间考虑图片尺寸并防止布局偏移。

提高响应速度

为了使图片在各种设备上都能自适应显示,开发者必须在 <img> 元素中设置 srcset 和 sizes 属性。我们希望通过 Image 组件来减少这项工作。我们设计了 Next.js Image 组件,以便每个应用仅设置一次属性值。我们会根据布局模式将这些属性应用于 Image 组件的所有实例。我们提出了一个包含三部分的解决方案:

  1. deviceSizes 属性:此属性可用于根据应用用户群共有的设备一次性配置断点。配置文件中包含断点的默认值。
  2. imageSizes 属性:这也是一个可配置的属性,用于获取与设备尺寸断点对应的图片尺寸。
  3. 每张图片中的 layout 属性:用于指明如何为每张图片使用 deviceSizes 和 imageSizes 属性。布局模式支持的值为 fixed、fill、intrinsic 和 responsive

当请求的图片采用 responsive 或 fill 布局模式时,Next.js 会根据请求网页的设备的大小来确定要提供的图片,并相应地设置图片中的 srcset 和 sizes。

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

笔记本电脑屏幕 手机屏幕
布局 = 内在:在较小的视口上缩小以适应容器的宽度。在较大的视口上不会放大到超出图片的固有大小。容器宽度为 100%
山脉图片按原样显示 山脉图片已缩小
布局 = 固定:图片不具有自适应性。宽度和高度是固定的,与呈现它的设备无关,类似于 `` 元素。
山脉图片按原样显示 按原样显示的“山脉”图片不适合屏幕
布局 = 响应式:根据不同视口上容器的宽度缩容或扩容,同时保持宽高比。
山脉图片放大到适合屏幕大小 山脉图片缩小到适合屏幕大小
布局 = 填充:宽度和高度拉伸以填充父容器。(在此示例中,父级 <div> 宽度设置为 300*500)
渲染后的山脉图片,可适应 300*500 的尺寸 渲染后的山脉图片,可适应 300*500 的尺寸
为不同布局渲染的图片

提供内置的延迟加载功能

Image 组件默认提供内置的高效延迟加载解决方案。使用 <img> 元素时,有几个用于延迟加载的选项,但它们都有缺点,因此使用起来比较棘手。开发者可以采用以下任一种延迟加载方法:

  • 指定 loading 属性:所有现代浏览器均支持此属性。
  • 使用 Intersection Observer API:构建自定义延迟加载解决方案需要付出努力,并进行周到的设计和实现。开发者可能并不总是有时间这样做。
  • 导入第三方库以延迟加载图片:可能需要额外付出精力来评估和集成合适的第三方库以实现延迟加载。

在 Next.js Image 组件中,加载默认设置为 "lazy"。延迟加载功能通过 Intersection Observer 实现,该 API 可在大多数现代浏览器中使用。开发者无需执行任何额外操作即可启用此功能,但可以在需要时将其停用。

预加载重要图片

LCP 元素通常是图片,而大图片可能会延迟 LCP。最好预加载关键图片,以便浏览器可以更快地发现该图片。使用 <img> 元素时,可以在 HTML 头部中添加预加载提示,如下所示。

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

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

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

添加 priority 属性可简化标记,使用起来更方便。图片组件开发者还可以探索应用启发式方法来自动预加载页面上符合特定条件的首页图片。

鼓励使用高性能图片托管服务

建议使用图片 CDN 来自动优化图片,它们还支持 WebP 和 AVIF 等新式图片格式。Next.js Image 组件默认使用图片 CDN,采用的是 loader 架构。以下示例表明,加载器允许在 Next.js 配置文件中配置 CDN。

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

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

支持自托管映像

在某些情况下,网站可能无法使用图片 CDN。在这种情况下,图片组件必须支持自托管图片。Next.js Image 组件使用图片优化器作为内置图片服务器,可提供类似 CDN 的 API。如果优化器安装在服务器上,则会使用 Sharp 进行生产图片转换。如果您想构建自己的图片优化流水线,不妨考虑使用此库。

支持渐进式加载

渐进式加载是一种用于吸引用户兴趣的技术,它会在加载实际图片的同时显示一张质量明显较低的占位图片。这有助于提升感知性能并改善用户体验。它可以与折叠下图片或折叠上图片的延迟加载功能结合使用。

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

影响

在纳入所有这些优化后,我们已成功在生产环境中使用 Next.js Image 组件,并且还在与其他技术堆栈合作开发类似的图片组件。

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

经验教训

任何创建 Next.js 应用的人都可以通过使用 Next.js Image 组件来获益。不过,如果您想为其他框架或 CMS 构建类似的效果抽象,以下是我们在此过程中学到的一些经验,可能会对您有所帮助。

安全阀可能弊大于利

在 Next.js Image 组件的早期版本中,我们提供了一个 unsized 属性,允许开发者绕过尺寸要求,使用未指定尺寸的图片。我们认为,在无法预先知道图片的高度或宽度的情况下,此属性是必需的。不过,我们注意到,用户在 GitHub 问题中推荐使用 unsized 属性作为解决尺寸要求问题的万能解决方案,即使在他们可以通过不会加剧 CLS 的方式解决问题的情况下也是如此。随后,我们弃用了 unsized 属性并将其移除。

区分有用的摩擦和毫无意义的烦扰

调整图片大小的要求就是一个“有益摩擦”的例子。它会限制组件的使用,但作为交换,它会带来巨大的性能优势。如果用户清楚了解潜在的性能优势,就会欣然接受限制。因此,有必要在组件的相关文档和其他已发布材料中说明这种权衡取舍。

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

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

如果您的图片组件除了给用户带来“有用的摩擦”之外,没有任何其他作用,那么开发者往往不会想使用它。我们发现,虽然图片尺寸调整和自动生成 srcset 值等效果功能最为重要,面向开发者的便利功能(例如自动延迟加载和内置模糊占位符)也让开发者对 Next.js Image 组件产生了兴趣。

制定功能路线图以推动采用

构建一个在所有情况下都能完美运行的解决方案非常困难。您可能会想设计出适合 75% 用户的产品,然后告诉另外 25% 的用户“在这些情况下,此组件不适合您”。

但在实践中,此策略与您作为组件设计者的目标相悖。您希望开发者采用您的组件,以便从中获益。如果有一部分用户无法迁移,并且感觉自己被排除在对话之外,那么很难做到这一点。他们可能会表达失望之情,从而产生负面看法,影响采用率。

建议您为组件制定路线图,其中涵盖长期内的所有合理使用情形。此外,在文档中明确说明不支持哪些内容以及原因,有助于设定对组件旨在解决的问题的预期。

总结

图片使用和优化流程复杂。开发者必须在图片效果和质量之间找到平衡点,同时确保出色的用户体验。因此,图片优化是一项成本高但效果显著的工作。

我们没有让每个应用每次都重新发明轮子,而是提出了一个最佳实践模板,开发者、框架和其他技术堆栈可以将其作为自己实现的参考。随着我们支持其他框架,这种经验确实会变得非常宝贵。

Next.js Image 组件已成功提升 Next.js 应用的性能,从而改善用户体验。我们认为,这是一个非常棒的模式,可以在更广泛的生态系统中发挥良好作用,我们非常期待有意在其项目中采用此模式的开发者的反馈。