构建有效的图片组件

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

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

图片是 Web 应用的性能瓶颈的常见来源,也是优化的主要关注领域。未经优化的图片导致页面膨胀,其在第 90 个百分位处占页面总大小(以字节为单位)的 70% 以上。多种图片优化方法需要智能的“图片组件”并且默认内置了性能解决方案。

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

作为图片的扩展的图片组件

图片优化问题和优化建议

图片不仅会影响效果,还会影响业务。页面上的图片数量是对访问网站的用户的转化影响最大的因素。用户完成转化的会话中的图片数量比未完成转化的会话中的图片少 38%。Lighthouse 在最佳实践审核中列出了多个优化图片和改进 Web Vitals机会。下面列出了图片可能会对核心网页指标和用户体验造成影响的一些常见领域。

图片大小不合适会损害 CLS

如果投放的图片没有指定尺寸,可能会导致布局不稳定,并且会导致 Cumulative Layout Shift (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 并延迟执行。延迟加载可用于稍后在用户滚动浏览此类图片时加载它们。

优化挑战

团队可以评估上述问题导致的性能成本,并实施最佳实践解决方案来克服它们。然而,这在实践中通常不会发生,而且效率低下的图片会继续拖慢网页加载速度。可能的原因包括:

  • 优先事项:网站开发者通常侧重于代码、JavaScript 和数据优化。因此,他们可能不知道图片存在的问题,也不知道如何优化图片。由设计人员制作或由用户上传的图片在优先级列表中可能不会比较靠前。
  • 开箱即用的解决方案:即使开发者了解图片优化的细微差别,缺少适用于其框架或技术栈的一体化解决方案也可能会造成阻碍。
  • 动态图片:动态图片除了包含属于应用组成部分的静态图片外,还可以由用户上传或者来源于外部数据库或内容管理系统。在来源为动态图片的情况下,定义此类图片的尺寸可能颇具挑战性。
  • 标记过载:对于添加图片大小或针对不同尺寸添加 srcset 的解决方案,需要为每个图片添加额外的标记,这可能会很繁琐。srcset 属性于 2014 年推出,但目前只有 26.5%的网站使用。使用 srcset 时,开发者必须创建各种尺寸的图片。just-gimme-an-img 等工具可以帮助您,但您必须手动为每个映像使用这些工具。
  • 浏览器支持:AVIF 和 WebP 等现代图片格式可创建较小的图片文件,但需要在不支持此类文件的浏览器上进行特殊处理。开发者必须使用内容协商<picture> 元素等策略,以便向所有浏览器提供图片。
  • 延迟加载复杂功能:为非首屏图片实现延迟加载有多种技术和库可用于。选择最佳版本并非易事。开发者可能也不知道广告离“首屏”的最佳距离加载延迟的映像设备上视口大小不同,会让情况变得更加复杂。
  • 横向变化:随着浏览器开始支持新的 HTML 或 CSS 功能以提升性能,开发者可能很难对每项功能进行评估。例如,Chrome 将“提取优先级”功能作为源试用引入。它可用于提高网页上特定图片的优先级。总的来说,如果在组件级别评估和实施此类增强功能,开发者会发现更容易。

图片组件作为解决方案

由于可以利用优化图像的机会以及为每个应用单独实施图像而面临挑战,这促使我们形成了图像组件的理念。图片组件可以封装并强制执行最佳实践。通过将 <img> 元素替换为图片组件,开发者可以更好地解决图片性能问题。

去年,我们借助 Next.js 框架设计和实现了其图片组件。它可用于直接替换 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 组件时,开发者必须使用 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

当您在自适应填充布局模式下请求图片时,Next.js 会根据请求页面的设备尺寸识别要提供的图片,并相应地在图片中设置 srcsetsizes

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

笔记本电脑屏幕 手机屏幕
Layout = Intrinsic:在较小视口上,缩小以适应容器的宽度。在较大的视口中,放大时不会超过图片的固有尺寸。容器宽度为 100%
如下所示的山脉图片 已缩小的山脉图片
布局 = 已修复:图片无响应。宽度和高度是固定的,类似于 `` 元素,无论在什么设备上呈现。
如下所示的山脉图片 显示的山脉图片不适合屏幕
布局 = 自适应:根据容器在不同视口上的宽度缩小或放大,同时保持宽高比。
为适应屏幕而放大的山脉图片 为适应屏幕而缩小的山脉图片
Layout = Fill:拉伸宽度和高度以填充父级容器。(在本例中,父级 <div> 宽度设置为 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 自动执行图片优化,而且它们还支持现代图片格式,例如 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 Image 组件取得了成功,此外,我们还在与类似图片组件的其他技术堆栈合作。

Leboncoin 将其旧版 JavaScript 前端迁移到 Next.js 时,还升级了映像流水线以使用 Next.js Image 组件。在从 <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 Image 组件的兴趣。

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

要打造一个适用于所有情况的解决方案非常困难。设计一款对 75% 的人都适用,然后告诉另外 25% 的人“在这些情况下,这个部分并不适合您”,对此可能会很感兴趣。

但在实践中,事实证明这一策略与您作为组件设计师的目标不一致。您希望开发者采用您的组件,以便享受其性能优势。如果有用户无法迁移并且感觉自己被忽视,就很难实现这个目标。他们很可能会感到失望,从而产生负面看法,从而影响采用率。

建议您为组件制定一个涵盖所有长期用例的路线图。此外,这有助于在文档中明确说明不支持的功能和原因,让用户了解该组件将要解决的问题。

总结

映像的使用和优化十分复杂。开发者必须在图片性能和质量之间取得平衡,同时确保良好的用户体验。因此,图片优化需要投入高成本、高影响力。

我们没有让每个应用每次都重新进行重复,而是想出一个最佳实践模板,供开发者、框架和其他技术栈参考来制定自己的实现方案。随着我们在其他图像组件方面为其他框架提供支持,这项经验将非常有用。

Next.js 图片组件成功地改善了 Next.js 应用的性能,从而改善了用户体验。我们相信,这是一个很好的模型,可在更广泛的生态系统中出色地运行,并且我们希望了解希望在其项目中采用此模型的开发者的想法。