你有沒有遇過這種情況:正準備點連結,圖片突然載入,整段文字往下一跳。手指點到旁邊的空白,只能默念:「很好,咖啡也跟著位移了。」

這不是單純的「圖片太大」問題。圖片還沒載入時,如果瀏覽器不知道它要佔多少空間,後面的內容就可能被推開,形成 CLS(Cumulative Layout Shift,累積版面配置位移)。另一方面,圖片若大到離譜,又會拖慢下載與解碼,影響 LCP。

這篇會用白話整理三件事:如何替圖片預留空間、如何讓下載尺寸符合實際顯示寬度,以及 Eleventy Image Transform 該怎麼接上工作流程。讀完後,你應該能判斷什麼時候寫 widthheight,什麼時候使用 aspect-ratio,也能避開幾個很常見的圖片效能坑。

CLS 到底在量什麼?

CLS 衡量的是頁面生命週期中,非預期的版面位移。例如文章文字先排好,圖片載入後才把文字往下推;這種跳動會讓讀者誤點按鈕,也會讓人覺得網站在偷偷喝醉。

Google 的 Core Web Vitals 建議值是:CLS 不高於 0.1、LCP 不超過 2.5 秒,都是以實際使用者資料的第 75 百分位作為判斷基準。這不代表每一台裝置都會得到同樣結果,所以本機 Lighthouse 分數很漂亮,也不等於真實訪客完全沒有問題。

先把範圍切清楚:本文專注圖片造成的位移。字型交換、廣告、Cookie 橫幅與第三方嵌入,也可能造成 CLS,但應該分開檢查,否則修到最後會像在咖啡渣裡找 bug。

第一招:在 HTML 寫上真實寬高

對一般 <img>,最穩妥的做法是提供圖片檔案的實際像素尺寸:

<img
	src="/img/product-dashboard.webp"
	width="1200"
	height="675"
	alt="產品控制台介面截圖"
	loading="lazy"
	decoding="async"
>

這裡的 widthheight 是圖片的內在尺寸,不是你希望它在畫面上永遠顯示的 CSS 寬度。瀏覽器可以先用這兩個數值算出比例,在圖片下載完成前預留大致正確的高度。

接著用 CSS 讓它能縮小,但不要被拉伸:

img {
	max-width: 100%;
	height: auto;
}

max-width: 100% 只限制圖片不要超出容器;height: auto 則依照原本比例計算高度。這兩行解決的是響應式顯示,HTML 寬高屬性解決的是載入前的尺寸提示,兩者是隊友,不是二選一。

寬高比例填錯,仍然會跳

如果圖片實際是 1600 × 900,卻寫成 1600 × 1200,瀏覽器會先預留錯誤的高度,圖片載入後再校正。這仍可能產生位移,因此請從原檔或產圖工具確認比例,不要靠目測——螢幕上的圖片很會偽裝。

Lazy loading 圖片也一樣需要寬高。它本來就可能等到接近視窗才下載;若此時才突然取得高度,讀者正在看的內容就會被推走。

第二招:需要固定框架時使用 aspect-ratio

aspect-ratio 適合用在圖片比例不固定、但設計需要統一視覺框的情境,也很適合 iframe、影片或其他沒有圖片內在比例的嵌入內容。

<figure class="screenshot">
	<img
		src="/img/compare-a.webp"
		width="1600"
		height="900"
		alt="方案 A 設定畫面"
		loading="lazy"
		decoding="async"
	>
</figure>
.screenshot {
	aspect-ratio: 16 / 9;
	width: 100%;
	overflow: hidden;
	background: #f2f2f2;
}

.screenshot img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

object-fit: cover 會讓圖片填滿框架,必要時裁掉邊緣。如果內容不能被裁切,就改用 contain,或保留 height: auto 讓圖片完整顯示。

一般內容圖片仍建議保留正確的 HTML 寬高;aspect-ratio 是補充工具,不是把每張圖的實際比例都忘掉的通行證。

第三招:圖片尺寸要配合顯示寬度

一張在手機上只顯示 360px 寬的圖片,如果每次都下載 4000px 原圖,瀏覽器不只要多下載,也要多解碼。做法不是無限降低品質,而是先產生符合版面需求的尺寸,再用 srcsetsizes 讓瀏覽器選擇候選檔案。

<img
	src="/img/guide-800.webp"
	srcset="
		/img/guide-400.webp 400w,
		/img/guide-800.webp 800w,
		/img/guide-1200.webp 1200w
	"
	sizes="(max-width: 768px) 100vw, 720px"
	width="1200"
	height="675"
	alt="設定步驟示意"
	loading="lazy"
	decoding="async"
>

srcset 列出可用的圖片候選;sizes 描述圖片在目前版面大約會顯示多寬。瀏覽器會綜合視窗寬度與裝置像素比例,選出合適的檔案。sizes 寫成 100vw 但圖片其實只佔內容欄,可能就會下載過大的檔案。

首屏真正的主圖不要一律設定 loading="lazy"。它若是 LCP 候選,應讓瀏覽器正常優先取得;是否加上 fetchpriority="high",則要看實際載入瀑布流與頁面結構,別把「高優先」當成圖片界的萬用咖啡因。

格式與壓縮怎麼選?

沒有一個適用所有圖片的固定 KB 門檻。照片、介面截圖、透明 Logo 的可接受品質不同,應該在實際裝置與網路條件下比較畫質、檔案大小和 LCP。

可以遵循這個順序:

  1. 先裁掉不需要的畫面,並縮到合理的輸出寬度。
  2. 依瀏覽器支援與網站流程選擇 AVIF、WebP 或原格式作為 fallback。
  3. UI 截圖放大檢查文字;壓縮不是越狠越專業。
  4. 用 PageSpeed Insights 或瀏覽器 Performance 面板確認實際結果。

Squoosh 這類工具適合手動比較不同格式與品質,但工具只負責產檔,widthheight 仍要回到 HTML 補好。

Eleventy Image Transform:把重複工作交給建置流程

圖片不多時,手動處理沒有問題;部落格持續更新後,漏掉寬高或忘記產縮圖就很容易發生。Eleventy 官方的 @11ty/eleventy-img Image HTML Transform 可以在建置時產生多種尺寸與格式,並輸出含有寬高和 srcset 的標記。

概念設定如下:

import { eleventyImageTransformPlugin } from '@11ty/eleventy-img';

export default function (eleventyConfig) {
	eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
		formats: ['webp', 'auto'],
		widths: [400, 800, 1200],
	});
}

實際使用多個寬度時,請為輸出的 <img> 提供符合版面的 sizes。沒有 sizes,瀏覽器可能無法正確判斷候選圖片適合的顯示寬度,最佳化就只完成一半。

Eleventy 也可能替圖片加上 loading="lazy"。這對文章下方的圖片通常合理,但首屏主圖要個別覆寫,並在建置後檢查產出的 HTML,而不是只相信設定檔看起來很漂亮。

建議導入順序很簡單:先讓所有圖片有正確比例,再讓建置流程產生合適尺寸,最後用真實頁面檢查 CLS 與 LCP。先把地板畫好,再搬沙發;不然每次圖片載入,都像重新裝潢一次。

一次完成的檢查清單

改完圖片後,可以照這個順序驗收:

  • 搜尋所有 <img>,確認有 alt,並檢查 widthheight 是否符合原圖比例。
  • 確認響應式 CSS 沒有用兩個固定尺寸把圖片拉伸;一般圖片通常搭配 max-width: 100%height: auto
  • 圖片若需要統一框架,確認容器的 aspect-ratioobject-fit 符合內容需求。
  • 檢查 srcset 的候選寬度與 sizes 是否符合實際版面。
  • 確認首屏主圖沒有被不必要的 lazy loading 延後。
  • 用 PageSpeed Insights、Chrome DevTools Performance 或 Search Console 的 Core Web Vitals 資料觀察結果。

CLS 修好不代表整站就會飛起來;伺服器回應、字型、JavaScript 和第三方服務仍可能影響體驗。不過圖片先把尺寸說清楚,至少不會一邊載入、一邊把讀者的手指送去錯的按鈕。

結語:先預留,再瘦身

圖片最佳化可以記成兩句話:先用 HTML 寬高或 CSS 比例預留空間,再讓檔案尺寸符合實際顯示寬度。 前者守住 CLS,後者幫助下載與解碼;兩邊一起做,才不是只把問題從「會跳」換成「等很久」。

先挑一篇文章,補齊圖片寬高並檢查產出的 srcset。做完再跑一次效能檢查,然後放心喝咖啡——這次咖啡杯的位置,應該不會因為圖片載入而改變。

參考資料