教你如何玩轉(zhuǎn)Next Image
如果你用過Next.js,那么很可能邂逅過Next Image組件。此圖像優(yōu)化解決方案不僅支持webp和avif等現(xiàn)代格式,還可以針對(duì)不同屏幕尺寸生成量身定制的多個(gè)版本。
要想獲得這種魔力,只需將以下代碼添加到頁面:
import Image from 'next/image';
export default function Page() {
return (
<Image
src="/profile.png"
width={500}
height={500}
alt="Picture of the author"
/>
);
}
不過,就像其他任何魔法一樣,只有打好堅(jiān)實(shí)的基礎(chǔ),才能熟練的運(yùn)用自如。
在本文中,我們將探討Next Image的工作原理,并澄清一些常見誤解。
核心架構(gòu)
next/image的底層架構(gòu)主要由三個(gè)組件組成:
- React Next Image組件
- Image API
- Image優(yōu)化器
圖片
React組件
該組件的主要功能是根據(jù)提供的屬性生成正確的HTML圖像輸出,并構(gòu)造要在srcset和src屬性中填充的多個(gè)URL。
下面是Next Image組件的示例輸出:
<img
alt="Example"
loading="lazy"
width="500"
height="500"
decoding="async"
data-nimg="1"
style="color:transparent"
srcset="/_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 1x,
/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 2x"
src="/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75"
>
生成的URL如下:
/_next/image?url=/images/example.jpg&w=640&q=75
這樣編碼的URL接受兩個(gè)參數(shù):w(寬度)和q(質(zhì)量)。我們發(fā)現(xiàn)沒有h(高度)屬性,這個(gè)我們稍后討論。
Image API
Next Image API用作圖像代理,類似于IPX。它執(zhí)行以下任務(wù):
- 接受圖像URL、寬度和質(zhì)量
- 驗(yàn)證參數(shù)
- 確定緩存控制策略
- 處理圖像
- 以用戶瀏覽器支持的格式提供圖像
Image優(yōu)化器
Next Image根據(jù)特定條件使用不同的圖像優(yōu)化庫:Sharp或Squoosh。
Sharp是一個(gè)快速高效的圖像優(yōu)化node.js模塊,利用原生的libvips庫。
Squoosh是一個(gè)完全基于節(jié)點(diǎn)的圖像優(yōu)化解決方案。有點(diǎn)慢,但不需要在計(jì)算機(jī)上安裝任何其他庫。
因此,一般Sharp用于生產(chǎn)環(huán)境,而在本地環(huán)境中默認(rèn)使用Squoosh。
p.s.我建議在本地環(huán)境中也使用Sharp。雖然Sharp和Squoosh優(yōu)化圖像的方式非常相似,但與Squoosh相比,Sharp的壓縮算法會(huì)導(dǎo)致顏色下降。這可能會(huì)導(dǎo)致生產(chǎn)環(huán)境和本地環(huán)境之間視覺行為的差異,尤其是在嘗試將圖像的背景顏色與頁面背景匹配時(shí)。
結(jié)果
了解了next/image背后的主要架構(gòu)后,讓我們一起辨別一些常見的誤解,以便更有效地利用它。
next/image不裁剪
開發(fā)人員中有個(gè)常見的誤解是next/image可以裁剪圖像。之所以會(huì)出現(xiàn)這種混淆,是因?yàn)閷挾取⒏叨群吞畛鋵傩钥梢詡鬟f給組件,從而給人一種圖像已被裁剪的印象。
實(shí)際上,情況并非如此。
Next Image組件主要將寬度和高度用來分配給img標(biāo)記,以防止布局偏移。
圖片
正如之前所說,Image API不接受高度參數(shù),這意味著目前無法更改原始圖像的縱橫比。
如果不使用fill屬性,則在寬度-高度不匹配的情況下,圖像只會(huì)拉伸或收縮。
但是,如果你使用的是TailwindCSS,由于默認(rèn)全局CSS規(guī)則,它的行為會(huì)有所不同:
img,
video {
max-width: 100%;
height: auto;
}
這使得布局偏移問題更難檢測。
顯示的圖像寬度≠加載的圖像寬度
另一個(gè)潛在的混淆點(diǎn)是傳遞給next/image的width屬性并不表示調(diào)整圖像大小的實(shí)際寬度。
正如開頭示例中所指,將width={500}傳遞給組件將導(dǎo)致圖像大小調(diào)整為640px的寬度,如生成的URL所示:
/_next/image?url=/images/example.jpg&w=640&q=75
如果你希望x2 retina版本使用1000px或1280px的圖像寬度,那么你必將大吃一驚:實(shí)際使用的寬度為1080px。
為什么呢?
圖片
Next.js將圖像大小調(diào)整為可在next.config.js中定義的deviceSizes和imageSizes數(shù)組中最接近的大小。
默認(rèn)情況下是:
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
這里需要注意的是,使用默認(rèn)配置可能會(huì)對(duì)性能產(chǎn)生負(fù)面影響,從而導(dǎo)致Lighthouse的Page Speed Insights分?jǐn)?shù)降低。
這在你嘗試在頁面上顯示大圖像時(shí),尤其明顯。
例如,如果要渲染寬度為1250px的圖像,則實(shí)際加載的圖像寬度將為1920px。
對(duì)于x2 retina版本,所需大小與實(shí)際加載大小之間的差異變得更大,因?yàn)榇笮⒄{(diào)整為3840px。
但是,你可以通過向deviceSizes或imageSizes數(shù)組添加更多尺寸來解決這個(gè)問題。
圖片
無需next/image組件即可圖像優(yōu)化
了解核心體系結(jié)構(gòu)后,很容易看出我們可以使用Image API,而不必使用next/image。在有些場景中,這是有優(yōu)勢(shì)的。
首先,你可以在畫布內(nèi)渲染優(yōu)化的圖像。無論你是從外部源還是從本地存儲(chǔ)將圖像加載到畫布上,都可以將正確的URL傳遞給API并使其無縫工作。
此外,還可以用來優(yōu)化OG圖像或自己創(chuàng)建基于<picture>標(biāo)簽的組件以獲得更好的藝術(shù)指導(dǎo)。
Image API位于/_next/image路由下,僅接受三個(gè)附加參數(shù):URL、width(w)和quality(q)。
/_next/image?url=https://example.com/test.jpg&w=640&q=75
請(qǐng)記住,width參數(shù)由API檢查,并且只能是來自deviceSizes或imageSizes配置的數(shù)字。
對(duì)本地映像使用導(dǎo)入
對(duì)于next/image,有兩種方法可用于加載本地圖像:
import Image from 'next/image';
import profileImg from './profile.jpg';
export default function Page() {
return (
<>
{/* Using absolute path */}
<Image src="/profile.png" width={500} height={500} alt="Picture of the author" />
{/* Using imported image via relative path */}
<Image src={profileImg} alt="Picture of the author" />
</>
);
}
在處理示例、教程甚至開源項(xiàng)目中的本地圖像時(shí),使用絕對(duì)路徑很普遍。
可以看到,除了自動(dòng)寬度/高度分配之外沒有顯著差異。
但是,有區(qū)別。
當(dāng)你通過絕對(duì)路徑從公用文件夾訪問圖像時(shí),Next.js遵循目標(biāo)服務(wù)器的緩存策略,默認(rèn)情況下,是30天的緩存策略而不是public,max-age=31536000,immutable。
對(duì)圖像資源使用30天緩存策略會(huì)顯著降低Lighthouse分?jǐn)?shù)。
了解sizes和100vw技術(shù)
next/image組件接受sizes屬性,類似于html img sizes屬性。
但是,它也執(zhí)行一些獨(dú)特的操作。
sizes屬性與srcset協(xié)同工作,接受應(yīng)激活的瀏覽器條件和圖像寬度列表。
以下是圖片使用sizes的示例:
<img srcset="/img/html/vangogh-sm.jpg 120w,
/img/html/vangogh.jpg 193w,
/img/html/vangogh-lg.jpg 278w"
sizes="(max-width: 710px) 120px,
(max-width: 991px) 193px,
278px">
讓我們深入了解細(xì)節(jié)以便理解得更透徹。當(dāng)你使用Next Image而不指定sizes屬性時(shí),srcset將包含兩個(gè)URL:一個(gè)用于標(biāo)準(zhǔn)版本(x1),另一個(gè)用于Retina版本(x2)。
通過這樣的設(shè)置,在Retina設(shè)備上使用時(shí),瀏覽器將始終選擇Retina版本。這種偏好是由于在srcset中使用了1x和2x語法而產(chǎn)生的。
<img
srcset="
/_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 1x,
/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 2x
"
/>
瀏覽器將其解釋為:加載這個(gè)URL為2x像素密度,另一個(gè)URL為1x像素密度。
因此,如果桌面上的圖像版本小于移動(dòng)設(shè)備或平板電腦上的圖像版本,則瀏覽器將始終使用默認(rèn)的Next Image語法加載較大的版本。
然而,不幸的是,這可能會(huì)導(dǎo)致性能欠佳并降低Lighthouse分?jǐn)?shù)。
但是,有一種方法可以指示瀏覽器根據(jù)合適的寬度加載圖像。不是向srcset URL提供1x、2x參數(shù),而是指定圖像的寬度。
例如,瀏覽器中如果有以下說明:
<img
srcset="
/_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 640w,
/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 1080w
"
/>
在這種情況下,瀏覽器會(huì)為頁面上使用的當(dāng)前尺寸選擇最合適的圖像。
如果移動(dòng)圖片的寬度為600px(Retina為1200px),則將選擇1080w版本。
同時(shí),如果桌面圖像僅使用300px(Retina為600px),瀏覽器會(huì)選擇 640w。
此方法的優(yōu)點(diǎn)在于加載最適合當(dāng)前屏幕尺寸的圖像,通過減小圖像大小來提高性能。
下面讓我們使用100vw技巧將此策略應(yīng)用于Next Image。
雖然你不能直接指示Next Image在URL附近使用width(w)參數(shù)而非像素密度(1x)選項(xiàng),但可以應(yīng)用從Next Image的編碼方式生成的解決方法:
- 如果sizes屬性包含vw數(shù)字,則只會(huì)使這些尺寸大于最小deviceSize(默認(rèn)為640)乘以百分比(100vw = 1,50vw = 0.5)。例如指定100vw,最終將得到8個(gè)URL。
- 如果sizes屬性包含非vw數(shù)字,則srcset將包含所有大小(即deviceSizes和imageSizes的所有可能組合),總共產(chǎn)生16個(gè)URL。
為了說明這一點(diǎn),一起來看看100vw生成的代碼:
<img
sizes="100vw"
srcset="
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=640&q=75 640w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=750&q=75 750w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=828&q=75 828w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1080&q=75 1080w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1200&q=75 1200w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1920&q=75 1920w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=2048&q=75 2048w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=3840&q=75 3840w
"
src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=3840&q=75"
/>
如果sizes中包含px值(例如(max-width: 1024px) 800px, 300px),則URL列表進(jìn)一步擴(kuò)展,達(dá)到默認(rèn)配置16。
理想情況下,我更愿意為特定圖像生成4個(gè)URL,類似于其他框架,而不是使用許多不必要的選項(xiàng)來膨脹HTML,且這些選項(xiàng)中可能沒有一個(gè)適合我的需求。
關(guān)鍵點(diǎn):要用更多版本填充srcset以獲得在各種分辨率下更好的性能,你可以簡單地將sizes設(shè)置為100vw。
此技巧強(qiáng)制創(chuàng)建8個(gè)不同尺寸——從640px開始的URL。
但是,由于此方法很容易增大HTML大小 - 特別是如果添加了額外的imageSizes或deviceSizes的話,因此慎用此方法。
我個(gè)人認(rèn)為對(duì)于需要在許多不同地方使用具有不同圖像比例的大型項(xiàng)目,這種生成平均大小版本的方法很多時(shí)候是有益的。
這些版本可以滿足大多數(shù)方案,更頻繁地訪問緩存,同時(shí)保持易用性。
結(jié)論
Next Image簡化了圖像管理并有著顯著優(yōu)勢(shì),它提供類似于第三方解決方案的高級(jí)裁剪和精確調(diào)整大小等附加功能。
同時(shí)有專門的組件可進(jìn)行微調(diào)。
我特別喜歡它的自動(dòng)化方法——生成0.25x、0.5x、1x和2x寬度的四個(gè)圖像版本。