图片组件封装了性能最佳实践,并提供了开箱即用的图片优化解决方案。
图片是 Web 应用性能瓶颈的常见来源,也是优化工作的重点领域。未优化的图片会导致网页膨胀,在第 90 个百分位数th,占网页总字节数重量的 70% 以上。图片优化方法多种多样,因此需要默认内置性能解决方案的智能“图片组件”。
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
图片的文件大小越大,下载所需的时间就越长。大图片可能是网页的“主打”图片,也可能是视口中负责触发最大内容渲染 (LCP) 的最重要的元素。如果某张图片属于关键内容,并且下载时间很长,则会延迟 LCP。
在许多情况下,开发者可以通过更好的压缩和使用响应式图片来减小图片大小。<img>
元素的 srcset
和 sizes
属性有助于提供不同大小的图片文件。然后,浏览器可以根据屏幕尺寸和分辨率选择合适的图片。
不恰当的图片压缩可能会损害 LCP
与常用的 JPEG 和 PNG 格式相比,AVIF 或 WebP 等现代图片格式可以提供更好的压缩效果。在某些情况下,更好的压缩功能可将文件大小缩减 25% 到 50%,同时保持相同的图片质量。这样一来,下载速度会更快,数据流量消耗也会更少。应用应向支持这些格式的浏览器提供现代图片格式。
加载不必要的图片会影响 LCP
加载网页时,系统不会向用户显示位于首屏或不在视口中的图片。您可以延迟这些资源,使其不会对 LCP 产生影响,也不会延迟 LCP。延迟加载可用于在用户滚动到相应图片时稍后加载此类图片。
优化挑战
团队可以评估上述问题带来的性能开销,并实施最佳实践解决方案来克服这些问题。但在实际使用中,这种情况通常不会发生,低效的图片会继续拖慢网站的速度。可能的原因包括:
- 优先事项:Web 开发者通常会专注于代码、JavaScript 和数据优化。因此,他们可能不知道图片存在问题或如何优化图片。由设计师创建或由用户上传的图片在优先级列表中可能不是很高。
- 开箱即用解决方案:即使开发者了解图像优化的细微之处,但如果没有适用于其框架或技术栈的开箱即用一体化解决方案,也可能会阻碍他们优化图像。
- 动态图片:除了应用中的静态图片之外,动态图片由用户上传或从外部数据库或 CMS 获取。如果图片的来源是动态的,则很难定义此类图片的大小。
- 标记过载:对于添加图片大小或针对不同尺寸添加
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 组件时,开发者必须使用 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" />
}
由于开发者无法使用未调整大小的图片组件,因此该设计可确保开发者花时间考虑图片大小,并防止布局偏移。
提高响应速度
为了让图片在不同设备上自适应,开发者必须在 <img>
元素中设置 srcset
和 sizes
属性。我们希望通过 Image 组件来减少此工作量。我们设计了 Next.js Image 组件,以便每个应用只设置一次属性值。我们根据布局模式将这些选项应用于“图片”组件的所有实例。我们想出了一个包含三个部分的解决方案:
deviceSizes
属性:此属性可用于根据应用用户群中常见的设备,一次性配置断点。配置文件中包含断点的默认值。imageSizes
属性:这也是一个可配置的属性,用于获取与设备尺寸断点对应的图片尺寸。- 每张图片中的
layout
属性:用于指明如何为每张图片使用deviceSizes
和imageSizes
属性。布局模式支持的值为fixed
、fill
、intrinsic
和responsive
当请求采用布局模式自适应或填充的图片时,Next.js 会根据请求网页的设备的尺寸来识别要提供的图片,并相应地设置图片中的 srcset
和 sizes
。
以下对比展示了如何使用布局模式来控制不同屏幕上的图片大小。我们使用了 Next.js 文档中分享的演示图片,在手机和标准笔记本电脑上查看。
笔记本电脑屏幕 | 手机屏幕 |
---|---|
Layout = Intrinsic:在较小的视口中按比例缩小以适应容器的宽度。在较大的视口上,放大时不会超过图片的固有尺寸。容器宽度为 100% | |
布局 = 固定:图片不支持自适应。宽度和高度固定,类似于 `` 元素,无论在哪种设备上呈现都是如此。 | |
布局 = 自适应:根据容器在不同视口上的宽度缩小或放大,同时保持宽高比。 | |
Layout = Fill:拉伸宽度和高度以填充父级容器。(在此示例中,父 <div> 宽度设置为 300*500)
|
|
提供内置的延迟加载功能
默认情况下,Image 组件提供高性能的内置延迟加载解决方案。使用 <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 Image 组件默认使用加载器架构来使用图片 CDN。以下示例展示了加载器允许在 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 图片组件,并且还在与其他技术栈合作开发类似的图片组件。
当 Leboncoin 将其旧版 JavaScript 前端迁移到 Next.js 时,他们还升级了图片流水线,以使用 Next.js Image 组件。在从 <img>
迁移到 next/image 的网页上,LCP 从 2.4 秒缩短到了 1.7 秒。为该网页下载的图片总字节数从 663KB 降至 326KB(其中包含大约 100KB 的延迟加载图片字节)。
经验教训
创建 Next.js 应用的任何人都能受益于使用 Next.js 图片组件进行优化。不过,如果您想为其他框架或 CMS 构建类似的性能抽象,以下是我们在构建过程中学到的几点经验,或许对您有所帮助。
安全阀可能造成弊大于利
在早期版本的 Next.js 图片组件中,我们提供了一个 unsized
属性,让开发者能够绕过尺寸要求,并使用未指定尺寸的图片。我们认为,在无法预先知道图片的高度或宽度的情况下,这项功能非常有用。不过,我们注意到,用户在 GitHub 问题中建议使用 unsized
属性作为解决尺寸要求问题的万能解决方案,即使他们可以通过不会加剧 CLS 的方式解决问题。我们随后废弃并移除了 unsized
属性。
将有用的摩擦与无用的烦恼区分开来
调整图片大小的要求就是一种“有用摩擦”的例子。这会限制组件的使用,但可以带来巨大的性能优势。如果用户清楚了解潜在的性能优势,就会很乐意接受限制。因此,在文档和有关该组件的其他已发布材料中说明这一权衡是值得的。
不过,您可以找到解决此类问题的权宜解决方法,而不会牺牲性能。例如,在开发 Next.js 图片组件时,我们收到了投诉,指出查找本地存储图片的大小很烦人。我们添加了静态图片导入功能,该功能通过使用 Babel 插件在构建时自动检索本地图片的尺寸,从而简化了此流程。
在便捷功能和性能优化之间取得平衡
如果您的图片组件除了向用户施加“有用的摩擦”之外什么也不做,开发者通常不愿意使用它。我们发现,虽然图片大小调整和自动生成 srcset
值等效果功能最为重要,面向开发者的便捷功能(例如自动延迟加载和内置模糊占位符)也激发了对 Next.js Image 组件的兴趣。
制定功能路线图以提高采用率
要打造一个适用于所有情况的解决方案非常困难。我们可能会很想设计出对 75% 的用户都适用的产品,然后告诉另外 25% 的用户“在这种情况下,此组件不适合您”。
在实践中,这种策略与组件设计师的目标不符。您希望开发者采用您的组件,以便享受其性能优势。如果有部分用户无法迁移,并且感到自己被排除在对话之外,就很难做到这一点。他们很可能会感到失望,从而产生负面看法,从而影响采用率。
建议您为组件制定一个涵盖所有长期用例的路线图。此外,在文档中明确说明不支持的内容及其原因,也有助于用户对组件要解决的问题有合理的预期。
总结
图片使用和优化非常复杂。开发者必须在图片效果和质量之间取得平衡,同时确保提供出色的用户体验。因此,图片优化需要投入高成本、高影响力。
我们设计了一个最佳实践模板,以便开发者、框架和其他技术栈在实现自己的功能时参考。这样,每个应用就不必每次都重新发明轮子。在支持其他框架的图片组件时,这段经历确实会很有用。
Next.js Image 组件成功提升了 Next.js 应用的性能,从而改善了用户体验。我们相信,这是一个很好的模型,可在更广泛的生态系统中出色地运行,并且我们希望了解希望在其项目中采用此模型的开发者的想法。