本篇开始我们进入组件篇,为大家详细介绍 Next.js 内置的四个组件,分别是:<Image>
、Font
、<Link>
、<Script>
,它们都是 Next.js 基于原生 HTML 标签做了诸多优化而专门抽象的组件,在开发的时候尽可能的使用这些组件。
其中 <Image>
组件实现了懒加载和根据设备尺寸自动调整图片大小,<Link>
组件实现了后台预获取资源,从而让页面转换更快更平滑,<Script>
组件使得你可以控制加载和执行第三方脚本等等……具体的功能和 API 我们会在组件篇中详细介绍。
本篇将介绍 <Image>
组件,因为图片往往占据了网页大小很大一部分,图片的优化可谓是重中之重。Image
组件也提供了非常多的 prop 和配置项,了解这些 prop 以及背后的原理有助于我们更加深入的使用 Image
组件,带来更好的用户体验。
根据 Web Almanac 中的介绍,图片大小占典型网站页面大小的很大一部分。根据统计,2021 年 6 月网站的总大小中位数是 2019 KB(移动端),其中 881 KB 是图像。这比 HTML(30 KB),CSS(72 KB),JavaScript(461 KB)和字体(97 KB)的总和还要多。
在绝大多数页面上(70% 移动设备,80% 桌面),最有影响的就是图片。Largest Contentful Paint(最大内容绘制,简写:LCP) 是一种 Web 性能指标,可以标识首屏中最大的内容元素。大部分时候,该元素都有图片。
考虑到 LCP 并不算是一个常为大家熟知的概念,所以我们单独介绍下 LCP。
对于 Web 开发者而言,衡量网页主要内容的加载速度一直是一个挑战。
传统我们会使用 load、DOMContentLoaded 等方法,但它们并不表示用户在屏幕上看到的内容的时间。
而像首次内容渲染(FCP),如果页面有 loading 效果,那获取的时间也是不准确的。
当然也有首次有效绘制(FMP)等指标,但是这些指标非常复杂,往往是错误的。所以也不能用来确定主要内容的加载时间。
根据 W3C Web 性能工作组中的讨论和 Google 的研究,要衡量网页主要内容的加载时间,更为准确的方法是查看最大元素的呈现时间。这就是 LCP。
Largest Contentful Paint (LCP) 指标会报告视口内可见的最大图片或文本块的呈现时间(相对于网页首次开始加载的时间)。
为了提供良好的用户体验,网站应尽力将 Largest Contentful Paint 设置为 2.5 秒或更短。
让我们看一些 LCP 的例子:
那么问题来了,页面往往是分阶段加载的,网页中最大元素可能是在不断变化的,LCP 是怎么计算出来的呢?
首先,浏览器会将 LCP 的元素限定在一些特定的元素类型内,比如<img>
元素、包含文本节点或其他内嵌级别文本元素的子项的块级元素、为自动播放 <video>
元素而绘制的第一帧、动画图片格式(例如 GIF 动画)的第一帧等等(这是为了简化这个问题,如果什么元素都计算一遍大小,就太复杂了而且没必要)。
然后浏览器在绘制完第一帧后,就会立即分派 largest-contentful-paint 类型的 PerformanceEntry,用于标识最大的内容元素。在渲染后续帧后,只要最大内容元素发生变化,该 API 就会再分派另一个 PerformanceEntry。简单的来说,每一帧绘制的时候,浏览器都会标示出最大内容元素。
当用户与页面发生交互(通过点按、滚动或按键),浏览器就会停止报告新条目。(因为用户交互通常会改变向用户显示的内容,就比如滚动操作)。一般来说,发出的最后一个条目的 startTime 值是 LCP 值。
<Image>
讲解 LCP,只是为了帮助大家认识到图片优化的重要性(毕竟最大内容元素往往是图片)。回到 <Image>
组件上,Next.js 基于原生的 HTML <img>
元素,实现了这些优化功能:
这些功能我们会在讲解组件 API 的时候一一涉及。
这是 <Image>
组件的使用示例,看起来如同使用正常的 img 元素一样:
// app/page.js
import Image from 'next/image'
export default function Page() {
return (
<Image
src="/profile.png"
width={500}
height={500}
alt="Picture of the author"
/>
)
}
<Image>
组件支持传入这些 props:
Prop | 示例 | 类型 | 是否必须 |
---|---|---|---|
src | src="/profile.png" |
String | 是 |
width | width={500} |
Integer (px) | 是 |
height | height={500} |
Integer (px) | 是 |
alt | alt="Picture of the author" |
String | 是 |
loader | loader={imageLoader} |
Function | - |
fill | fill={true} |
Boolean | - |
sizes | sizes="(max-width: 768px) 100vw" |
String | - |
quality | quality={80} |
Integer (1-100) | - |
priority | priority={true} |
Boolean | - |
placeholder | placeholder="blur" |
String | - |
style | style={{objectFit: "contain"}} |
Object | - |
onLoadingComplete | onLoadingComplete={img => done())} |
Function | - |
onLoad | onLoad={event => done())} |
Function | - |
onError | onError(event => fail()} |
Function | - |
loading | loading="lazy" |
String | - |
blurDataURL | blurDataURL="data:image/jpeg..." |
String | - |
其中,src
、width
、height
和 alt
是必须的,其他是可选的。让我们逐一讲解。
src 支持传入一个静态导入图片文件,也支持传入一个路径字符串。
使用本地图片的时候,就可以采用静态导入图片文件的方式。通过 import
导入 .jpg
、.png
或者 .webp
格式的文件。使用示例如下:
// app/page.js
import Image from 'next/image'
import profilePic from './me.png'
export default function Page() {
return (
<Image
src={profilePic}
alt="Picture of the author"
// width={500} automatically provided
// height={500} automatically provided
// blurDataURL="data:..." automatically provided
// placeholder="blur" // Optional blur-up while loading
/>
)
}
使用静态文件导入的方式,width
和 height
不需要传入,Next.js 会自动提供。
注意:动态的 await import()
或者 require()
是不支持的。import
必须是静态的,才可以在构建的时候进行分析。
使用远程图片的时候,src 可以传入一个 URL 字符串。
由于 Next.js 在构建的时候无法获取远程文件,你需要手动提供 width
、height
和可选的 blurDataURL
props。
width
和 heigth
属性用于推断图像正确的宽高比(Aspect ratio,也称为纵横比)以及避免图片加载的时候发生布局偏移。但 width
和height
并不决定图片最终的渲染尺寸,这也很好理解,因为你也可能设置拉伸模式等。
// app/page.js
import Image from 'next/image'
export default function Page() {
return (
<Image
src="https://s3.amazonaws.com/my-bucket/profile.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
注意使用远程地址的时候,Next.js 要求在 next.config.js
文件中定义支持的远程图片地址,这是为了防止一些恶意使用。配置方法如下:
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 's3.amazonaws.com',
port: '',
pathname: '/my-bucket/**',
},
],
},
}
关于配置文件中的 remotePatterns
,本篇后面我们会详细讲解。
width
属性表示图片渲染的宽度,它以像素为单位,影响图片的显示大小。
该属性必须,除非是静态导入图片或者图片有 fill
属性。
height
属性表示图片渲染的高度,它以像素为单位,影响图片的显示大小。
属性必须,除非是静态导入图片或者图片有 fill
属性。
alt
属性用于描述图片,提供给屏幕阅读器和搜索引擎使用。如果图片被禁用或者加载图片时出现错误,它会作为降级的文本提示。
alt
属性应该使用在不改变页面含义的情况下替代图片的文本描述,不应该重复图片上方或下方标题中提供的信息。
如果图片纯粹是装饰或者不是给用户使用,那 alt
属性应该用一个空字符串表示(alt=""
)。
loader
表示解析图片地址的自定义函数。让我们看段示例代码:
'use client'
import Image from 'next/image'
const imageLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
export default function Page() {
return (
<Image
loader={imageLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
函数接受 src
、width
、quality
作为参数,返回图片的 URL 字符串。
注意:由于 loader
prop 传入的是一个函数,所以需要使用客户端组件,在这个例子中,顶部也是用的 'use client'
。
每一个图片都添加一个 loader 非常麻烦,你也可以使用 next.config.js
中的 loaderFile
配置项来配置应用里的每个 next/image
实例,而无需传递 loader
prop。这个配置项本篇后面会讲解。
fill={true} // {true} | {false}
fill
表示是否将图片填充父元素。默认值为 false
。当图片的 width
和 height
未知的时候很有用。
但是要注意:使用 fill
,父元素必须指定为 position: "relative"
或position: "fixed"
或position: "absolute"
。而 img 元素会默认自动指定为 position: "absolute"
。
如果图片没有应用其他样式,图片会被拉伸以填充容器。
当然填充容器有很多方式,CSS 属性里的 object-fit: "container"
和 object-fit: "cover"
也都可以用来填充图像。
让我们简单看下区别:
// app/page.js
import Image from 'next/image'
import profilePic from './image.png'
export default function Page() {
return (
<div style={{
width: '200px',
height: '200px',
backgroundColor: "#ccc",
position: 'relative'
}}>
<Image
src={profilePic}
alt="Picture of the author"
/>
</div>
)
}
正常显示如下:
如果添加 fill 属性后:
<Image
src={profilePic}
alt="Picture of the author"
fill={true}
/>
效果如下,图片会被拉伸以适应容器:
如果使用 object-fit: "contain"
:
<Image
src={profilePic}
alt="Picture of the author"
fill={true}
style={{objectFit: "contain"}}
/>
效果如下,图片在保持其宽高比的同时填充元素的整个内容框:
如果使用
object-fit: "cover"
:
<Image
src={profilePic}
alt="Picture of the author"
fill={true}
style={{objectFit: "cover"}}
/>
效果如下,图片在保持其宽高比的同时填充元素的整个内容框。如果对象的宽高比与内容框不相匹配,该对象将被剪裁以适应内容框:
HTML 5.1 新增加了 img 元素的 srcset
、sizes
属性,用于设置响应式图像。
当我们需要不同的设备展示不同的图片的时候,就需要用到 srcset
。这里具体又分为两种情况,一种是图片是相同的尺寸,但是不同的分辨率对应不同的图片,即高分辨率下对应高倍图。一种是相同的图片内容,但依据设备显示的更大或者更小。这分别对应着 srcset 的两种语法。
先说第一种,根据分辨率不同展示不同的图片,使用示例如下:
<img
srcset="elva-fairy-320w.jpg, elva-fairy-480w.jpg 1.5x, elva-fairy-640w.jpg 2x"
src="elva-fairy-640w.jpg"
alt="Elva dressed as a fairy" />
效果如下:
srcset 是由逗号分隔的一个或多个字符串组成,每段字符串由以下组成:
如果我们给图片应用一个 CSS 样式:
img {
width: 320px;
}
它的宽度在屏幕上就是 320 像素(CSS 像素)。浏览器会计算出正在显示的显示器的分辨率,然后显示 srcset 引用的最适合的图片。如果是普通的分辨率,一个设备像素表示一个 CSS 像素,那就会加载 elva-fairy-320w.jpg
,它的大小是 39KB,如果设备是高像素,用两个或者更多的设备像素表示一个 CSS 像素,那就会加载 elva-fairy-640w.jpg
,它的大小是 93KB。
再说第二种情况,相同的图片内容,但依据设备显示的更大或者更小。使用示例代码如下:
<img
srcset="elva-fairy-small.jpg 480w, elva-fairy-large.jpg 800w"
src="elva-fairy-large.jpg"
alt="Elva dressed as a fairy" />
srcset 的语法与刚才略有不同,它定义了浏览器可选择的图片设置以及每个图片的大小。分析它的语法,依然是由逗号分隔的一个或多个字符串组成,每段字符串由以下部分组成:
此时我们就告诉了浏览器,这张图片有两种备选图片可以显示,一张是 elva-fairy-small.jpg
,这张图片的宽度是 480px,一张是 elva-fairy-large.jpg
,它的宽度是 800px。
那浏览器怎么知道用哪张图片呢?比如当前设备视口宽度是 640px,是选择 480px 的图片还是选择 800px 的图片呢?
为了帮助浏览器判断,你就需要写 sizes 属性。sizes 属性就是一组媒体查询条件,告诉浏览器,什么样的条件使用什么样的图片。一个使用示例如下:
<img
srcset="elva-fairy-small.jpg 480w, elva-fairy-large.jpg 800w"
sizes="(max-width: 600px) 480px,
800px"
src="elva-fairy-large.jpg"
alt="Elva dressed as a fairy" />
sizes 也是由逗号分隔的一个或多个字符串组成,每段字符串由以下组成:
(max-width:600px)
,它表示当视口的宽度小于等于 600px 时有了这些属性后,浏览器会:
比如在这个例子中,如果浏览器的视口是 480px,那么 sizes 中的第一个条件 (max-width: 600px) 就为真,所以选择 480px 大小,因为 480px 与固有宽度 480w 最接近,所以加载 elva-fairy-small.jpg。通过这种方式就可以实现移动端加载小图片,从而加快移动端的加载速度。
讲完 img 元素的 srcset
和 sizes
属性,回到 <Image>
组件上,使用 Next.js,你并不需要设置 srcset
,Next.js 会自动为你生成。设置 sizes 属性会影响生成的 srcset
的值。
如果你不设置组件的 sizes 属性,Next.js 会用 1x、2x 这种像素密度描述符,而如果你设置了 sizes 属性,Next.js 会用 640w、750w 这种固有宽度描述符。
设置前:
设置后:
表示优化图片的质量,值为 1 到 100 之间的整数,100 表示最好的品质,也是最大的文件大小。默认是 75。
quality={75} // {number 1-100}
表示图片加载优先级,布尔类型,默认值为 false。当值为 true 时表示高优先级并预加载。使用 priority
的图片会自动禁用懒加载。
priority={false} // {false} | {true}
使用该属性有两个建议:
运行 next dev
的时候,如果 LCP 元素是一个图片,但没有设置 priority 属性,控制台里会有警告:
使用示例代码:
// app/page.js
import Image from 'next/image'
import profilePic from '../public/me.png'
export default function Page() {
return <Image src={profilePic} alt="Picture of the author" priority />
}
表示加载图片时的占位符。可选值为 blur
、empty
、 data:image/...
,默认值是 empty
。
placeholder = 'empty' // "empty" | "blur" | "data:image/..."
当值为 empty
的时候,加载不会有占位符,只有空白区域。
当值为 data:image/...
的时候,使用 Data URL 作为图片加载时的占位图片。Data URL,即前缀为 data:
协议的 URL,允许内容创建者向文档中嵌入小文件。比如 base64 图片就是 Data URL。
当值为blur
的时候,blurDataURL
属性的值会被用于作为占位符图片。如果图片是静态导入的,并且导入的图片为 .jpg
、.png
、.webp
或 .avif
,blurDataURL 会自动生成,但动态图片除外。如果是动态图片,必须提供 blurDataURL
属性。
这是一个默认的 blur 效果演示:
只有当你设置了 placeholder="blur"
,该属性值才会生效。
必须是 base64 编码的图片。图片会被放大并模糊,建议使用一个非常小的图片(10px 或者更小)。
可以借助 blurDataURI 实现这种色彩效果:
可以借助 https://png-pixel.com/ 快速获得一个纯色图片的 Data URL。
设置图片的样式
// components/ProfileImage.js
const imageStyle = {
borderRadius: '50%',
border: '1px solid #fff',
}
export default function ProfileImage() {
return <Image src="..." style={imageStyle} />
}
当你使用 style 修改了图片宽度的时候,注意设置 height
为 auto
以保持宽高比,否则图片会被扭曲。这是因为 Next.js 会自动为图片添加 width
和 height
属性,通过 style 样式只修改宽度,加上原本添加的 height
属性,就会导致图片变形。
'use client'
<Image onLoadingComplete={(img) => console.log(img.naturalWidth)} />
当图片加载完毕的时候,会执行该回调函数,同时占位符图片会被删除。回调函数调用的时候会传入一个参数,该参数是对底层 元素的引用。
注意:因为组件接收一个函数作为参数,需要使用客户端组件。
'use client'
<Image onLoad={(e) => console.log(e.target.naturalWidth)} />
同样是图片加载完的时候执行,该回调函数可能会在占位符被删除以及图片被完全解码前执行。所以如果你想等到图片完全加载完毕,使用 onLoadingComplete。
注意:因为组件接收一个函数作为参数,需要使用客户端组件。
'use client'
<Image onError={(e) => console.error(e.target.id)} />
图片加载失败时执行的回调函数。
注意:因为组件接收一个函数作为参数,需要使用客户端组件。
loading = 'lazy' // {lazy} | {eager}
设置图片的加载行为,默认值为 lazy
。
当值为 lazy
的时候,图片会延迟加载,直到图片接近视口。
当值为 eager
的时候,图片会被立即加载。
使用 eager
通常会损害性能。Next.js 推荐使用 priority
属性代替。
取消优化。当值为 true
的时候,使用源图片,不会更改质量、大小、格式。默认值是 false
。示例如下:
import Image from 'next/image'
const UnoptimizedImage = (props) => {
return <Image {...props} unoptimized />
}
从 Next.js 12.3.0 起,可以通过 next.config.js
设置所有的图片取消优化:
// next.config.js
module.exports = {
images: {
unoptimized: true,
},
}
其他传给 <Image />
组件的属性都会传给底层的 img 元素。但以下属性除外:
srcSet
,Next.js 会自动生成,如果你想更改,使用配置项里的 deviceSizes,下一节会讲到。decoding
,它的值总是 "async"
除了通过 props,你也可以在 next.config.js
中配置图片组件。
为保护应用远离恶意用户,当使用外部图片的时候需要配置 remotePatterns
:
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/account123/**',
},
],
},
}
这个示例的意思是,next/image
的 src
属性的值必须是以 https://example.com/account123/
为开头。其他的协议、主机名、端口或者不匹配的路径都会返回 400 错误。
这是另外一个例子,在此例子中使用了通配符:
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.example.com',
},
],
},
}
这个示例的意思是,next/image
的 src
属性的值必须是以 https://xxx.example.com
、https://xxx.xxx.example.com
、https://xxx.xxx.xxx.example.com
……开头。
pathname
和 hostname
都可以使用通配符,其中:
*
表示匹配单个路由段或者子域**
表示匹配任意数量的路由段或者子域。注意 **
语法在模式的中间是不起作用的。
自 Next.js 14 起因为使用了更为严格的 remotePatterns
而废弃。仅当所有的内容都来自你所能控制的域的时候你再使用。
与 remotePatterns
类似,domains
配置项提供了一个用于外部图片的 hostname 列表:
// next.config.js
module.exports = {
images: {
domains: ['assets.acme.com'],
},
}
但是注意 domains
不支持通配符,并且无法限制协议、端口或者路径名。所以更建议使用 remotePatterns
。
如果你不希望使用 Next.js 内置的图片优化 API,那你可以自己配置,使用 next.config.js
的 loaderFile
配置项:
// next.config.js
module.exports = {
images: {
loader: 'custom',
loaderFile: './my/image/loader.js',
},
}
loaderFile
必须指向相对于 Next.js 应用根目录的文件。该文件必须导出一个默认函数,该函数返回一个字符串。举个例子:
'use client'
export default function myImageLoader({ src, width, quality }) {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
这会应用到所有的 next/image
的实例,如果你要修改个别图片,使用 loader
prop。
如果你知道用户的设备宽度,那你可以使用 next.config.js
的 deviceSizes
来声明一系列的设备宽度断点。当 next/image
组件使用 sizes
prop 的时候,这些宽度会被用来推断正确加载的图片。
如果没有配置,默认值是:
// next.config.js
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
},
}
你可以使用 next.config.js
的 imageSize
属性声明一系列的图片宽度。
如果没有配置,默认值是:
// next.config.js
module.exports = {
images: {
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
}
imageSize
和 deviceSizes
会影响图片生成最终的 srcset
尺寸:
那么问题来了,都是用来生成最终的 srcset
,直接用一个数组不就好了吗?为什么非要用两个数组?
你可以这样理解,deviceSizes
用来处理大图片,imageSizes
用来处理小图片,而且 imageSizes
只会在图片使用了 sizes
prop 的时候生效,比如当你这样写:
import Image from 'next/image'
import profilePic from './image.png'
export default function Page() {
return (
<Image
src={profilePic}
sizes="(max-width: 600px) 160px,
320px"
alt="Picture of the author"
/>
)
}
对应生成的 srcset
就包含了deviceSizes 和 imageSizes 的所有尺寸:
当你使用了 sizes
prop 的时候,说明图片的宽度是小于全屏宽度的。imagesSizes
的中的所有值应该都小于 deviceSizes
中的最小值。
Next.js 默认的图片优化 API 会自动通过请求中的 Accept 请求头检测浏览器支持的图片格式。
如果 Accept
匹配多个配置的格式,数组中的第一个会被首先使用。因此,数组的顺序很重要,如果没有匹配到,或者源图片为动图,图片优化 API 会自动回退到原本的图片格式。
如果没有配置,默认值是:
// next.config.js
module.exports = {
images: {
formats: ['image/webp'],
},
}
你可以使用下面的配置开启 AVIF 格式支持:
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
},
}
图片会根据请求动态优化并存储在 <distDir>/cache/images
目录。优化后的图像文件会被用于后续请求,直到缓存过期。当匹配到过期的文件时,过期图片会立刻失效,同时图片会在后台重新优化并使用新的失效日期储存在缓存中。
通过读取响应头中的 x-nextjs-cache
标头确定图片的缓存状态:
MISS
:路径不在缓存中STALE
:路径缓存了但是超出了重新验证时间,它会在后台被更新HIT
:路径在缓存中,且未超过重新验证时间过期时间可以通过两种方式定义:
一种是通过 minimumCacheTTL
配置项,一种通过 Cache-Control
标头,具体而言,使用Cache-Control
标头的 max-age
字段。如果 s-maxage
和 max-age
都有,s-maxage
优先。
两种方法如果同时设置,以较大者为准。
minimumCacheTTL
配置项用来设置缓存优化图片的过期时间,它以秒为单位。使用示例如下:
// next.config.js
module.exports = {
images: {
minimumCacheTTL: 60,
},
}
如果你需要更改每张图片的缓存行为,你可以通过 headers 配置图片资源的 Cache-Control
标头。
大部分时候,建议使用静态图片导入,它会自动对文件内容进行哈希处理,并使用 immutable
的 Cache-Control
标头。
目前还没有重新验证缓存的机制,所以最好将 minimumCacheTTL
的值设低一点、否则你可能需要手动修改 src
prop 或者删除 <distDir>/cache/images
以更新缓存。
如果图片静态导入功能跟其他插件发生冲突,你希望禁用此功能:
// next.config.js
module.exports = {
images: {
disableStaticImages: true,
},
}
默认 loader 不会优化 SVG 图片。首先,SVG 是一种矢量格式,这意味着它可以无损地调整大小。其次,SVG 具有许多与 HTML/CSS 相同的功能,如果没有适当的内容安全策略,这些功能可能会导致漏洞。
如果你需要使用默认的图像优化 API 来提供 SVG 图像,设置 next.config.js
的 dangerouslyAllowSVG
值:
// next.config.js
module.exports = {
images: {
dangerouslyAllowSVG: true,
contentDispositionType: 'attachment',
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
},
}
Next.js 强烈推荐设置 contentDispositionType
强制浏览器下载图片,以及 contentSecurityPolicy
阻止执行图片中嵌入的脚本。
默认 loader 将自动跳过动态图片的优化并按原样展示。
Next.js 会自动检测动态图片,支持 GIF、APNG 和 WebP 格式。对于特定的动态图片,如果你想显式声明跳过,使用 unoptimized
属性(这样就省得 Next.js 检测判断了)。
图片默认生成的 srcset
包括 1x
、2x
图片,这是为了支持不同的设备像素比。不过有的时候,你希望渲染响应式图片,自动适配视口,这个时候,你就需要设置 sizes
以及 style
(或者 className
)。下面这些方式都可以用来渲染响应式图片:
如果源图片不是动态的,你可以通过静态导入创建一个响应式图片:
// components/author.js
import Image from 'next/image'
import me from '../photos/me.jpg'
export default function Author() {
return (
<Image
src={me}
alt="Picture of the author"
sizes="100vw"
style={{
width: '100%',
height: 'auto',
}}
/>
)
}
如果源图片是动态或者远程 URL,你需要提供 width
和 height
来设置正确的响应式图片宽高比。
// components/page.js
import Image from 'next/image'
export default function Page({ photoUrl }) {
return (
<Image
src={photoUrl}
alt="Picture of the author"
sizes="100vw"
style={{
width: '100%',
height: 'auto',
}}
width={500}
height={300}
/>
)
}
如果你不知道图片宽高比,那可以考虑使用 fill
属性,注意设置父元素为 postion:relative
当然不用这种方式。也可以使用 object-fit
,具体看你想要什么样的效果:
// app/page.js
import Image from 'next/image'
export default function Page({ photoUrl }) {
return (
<div style={{ position: 'relative', width: '500px', height: '300px' }}>
<Image
src={photoUrl}
alt="Picture of the author"
sizes="500px"
fill
style={{
objectFit: 'contain',
}}
/>
</div>
)
}
import Image from 'next/image'
import mountains from '../public/mountains.jpg'
export default function Fill() {
return (
<div
style={{
display: 'grid',
gridGap: '8px',
gridTemplateColumns: 'repeat(auto-fit, minmax(400px, auto))',
}}
>
<div style={{ position: 'relative', height: '400px' }}>
<Image
alt="Mountains"
src={mountains}
fill
sizes="(min-width: 808px) 50vw, 100vw"
style={{
objectFit: 'cover', // cover, contain, none
}}
/>
</div>
{/* And more images in the grid... */}
</div>
)
}
如果你希望实现浅色和深色模式下显示不同的图片,你可以创建一个新组件包含两个 <Image>
组件,然后通过 CSS 媒体查询显示正确的那一个:
// omponents/theme-image.module.css
.imgDark {
display: none;
}
@media (prefers-color-scheme: dark) {
.imgLight {
display: none;
}
.imgDark {
display: unset;
}
}
// components/theme-image.tsx
import styles from './theme-image.module.css'
import Image from 'next/image'
const ThemeImage = (props) => {
const { srcLight, srcDark, ...rest } = props
return (
<>
<Image {...rest} src={srcLight} className={styles.imgLight} />
<Image {...rest} src={srcDark} className={styles.imgDark} />
</>
)
}
在使用 Next.js 图片组件的时候,你会发现,Next.js 要求必须有 width 和 height 属性,哪怕使用静态导入图片的方式,也只是不用自己手写这两个属性而已,Next.js 依然会为你自动添加 width 和 height,之所以这样做,就是为了防止发生布局偏移。所谓布局偏移,顾名思义,原本内容的位置突然发生偏移,多出现在加载的时候。导致布局偏移的原因有很多,图片没有尺寸是常见的一个原因,让我们看个演示视频:
这就是没有设置图片尺寸导致的布局偏移,如果设置尺寸呢:
你会发现图片在加载的时候,浏览器为图片预留了位置。
不要小瞧布局偏移,为此专门有累计布局偏移(Cumulative Layout Shift,简称 CLS)这个 Web 性能衡量指标。这可是 Google 三大核心网页指标之一。累计布局偏移会统计视口中可见内容的移动量以及移动的距离,综合算出一个得分。
next/image
的设计就是为了防止发生布局偏移,所以如果要调整图片大小,应该使用下面三种方式之一:
width
和 height
属性