要預測響應式圖片候選項,請將 sizes 解析為 CSS 寬度,乘以裝置像素比,並將該目標值與 srcset 比較。接著確認所選 URL:瀏覽器保留最終選擇的裁量權。

本指南適合在 Mac 上使用 Chrome 檢查自行編寫的網頁的人。你將計算可能的候選項、測試包含原創抽象形狀的本機頁面,並區分誤導性的尺寸宣告與瀏覽器正常的選擇行為。

sizes、srcset 和 DPR 分別向瀏覽器提供什麼資訊?

使用寬度描述符時,srcset 會列出可用資源及其固有像素寬度。像 800w 這樣的描述符,必須表示實際寬度為 800 像素的圖片。請將寬度描述符和像 2x 這樣的密度描述符分開放在不同的來源集;在同一個 srcset 中混用兩者是無效的。MDN 的圖片元素參考文件說明了這些要求。

sizes 提供資源選擇時預期的顯示寬度。瀏覽器會依序評估其中的條件,並使用第一個符合的項目。請將無條件的後備值放在最後。來源尺寸可接受 480px、50vw 和 calc(100vw - 32px) 等長度;像 50% 這樣的百分比值不是有效的版位寬度。在 HTML 中使用響應式圖片

CSS 控制顯示尺寸。請將 CSS 和明確的 sizes 清單視為兩項必須保持一致的描述:其中一項決定版面配置,另一項則提供資源選擇資訊。單獨變更 sizes 並不會實作對應的版面配置。HTML 標準:sizes 屬性

DPR 描述實體像素與 CSS 像素之間的關係。請讀取 window.devicePixelRatio,不要根據 Mac 型號或顯示器名稱猜測數值。頁面縮放可能會改變這個值,在不同顯示器之間移動視窗也可能改變它。雙指縮放不會改變 DPR。MDN:Window.devicePixelRatio

如何計算可能的候選項?

對於解析後的來源尺寸 S、資源寬度 W 和 DPR D,可使用以下兩種等效計算方式:

目標資源寬度 = S × D
候選項有效密度 = W ÷ S

HTML 處理模型會將每個候選項的寬度除以來源尺寸,藉此正規化寬度描述符。MDN 也說明了實用的預測方式:將尺寸提示乘以 DPR。HTML 標準:正規化來源密度、MDN:HTMLImageElement.sizes

實用的估算方式是選擇大於或等於目標寬度的最小候選項。如果所有候選項都小於目標值,請指出可用的最大候選項並記錄差距。以下練習將明確採用這項預測規則。HTML 標準將最終選擇留給實作決定,並未要求所有瀏覽器都遵循這項取整規則。HTML 標準:選擇圖片來源

考慮以下來源尺寸清單,以及 480w、800w、1200w 和 1440w 候選項:

(max-width: 600px) calc(100vw - 32px),
(max-width: 1200px) calc(50vw - 32px),
568px

100vw 表示視埠寬度,50vw 表示該寬度的一半。這些單位參照的是視埠,而非圖片的父容器。CSS 值與單位:視埠百分比長度

下表列出的是示意計算結果,並非記錄到的瀏覽器結果。最後一欄依照上述預測規則得出。

視埠寬度,CSS px DPR 解析後的來源尺寸,CSS px 目標資源寬度,px 預測候選項
600 1 568 568 800w
600 2 568 1,136 1200w
601 2 268.5 537 800w
1,000 1 468 468 480w
1,000 2 468 936 1200w
1,400 2 568 1,136 1200w
1,400 3 568 1,704 1440w,低於目標

從 600 到 601 CSS 像素的轉變尤其適合測試。在 600 時,第一個條件提供幾乎全寬的版位;在 601 時,第二個條件提供窄得多的版位。因此,視埠變寬反而會產生較小的目標圖片。這直接源自自行編寫的版面配置約定。

視埠寬度為 1,000 像素、DPR 為 2 時,來源尺寸是 468 CSS 像素。四個候選項的有效密度約為 1.03x、1.71x、2.56x 和 3.08x。依照我們的預測規則,1200w 候選項是第一個超過 2x 目標密度的項目。

如何建立完整的本機測試?

請使用安裝了 Chrome 和 Python 3.8 或更新版本的 Mac。在純文字編輯器中,將以下完整程式儲存為 responsive_demo.py。程式會產生四個包含相同矩形與圓形的 PNG 檔案、寫入相符的網頁,並啟動本機伺服器。所有產生的檔案都會放進新的暫存目錄,因此不會覆寫現有檔案。

此頁面刻意使用立即載入的圖片和明確的來源尺寸。其 CSS 寬度與計算表中的三個分支一致。伺服器使用 Python 文件記載的 ThreadingHTTPServer 和目錄型請求處理器。Python:http.server

from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import struct
import tempfile
import zlib


def chunk(kind, data):
    return (
        struct.pack("!I", len(data)) + kind + data
        + struct.pack("!I", zlib.crc32(kind + data))
    )


def make_png(width):
    height = width // 2
    rows = []
    for y in range(height):
        row = bytearray()
        for x in range(width):
            color = (242, 244, 248)
            if width // 8 <= x < width // 2 and height // 4 <= y < 3 * height // 4:
                color = (70, 100, 160)
            if (x - 3 * width // 4) ** 2 + (y - height // 2) ** 2 < (height // 5) ** 2:
                color = (220, 160, 60)
            row.extend(color)
        rows.append(b"\x00" + row)
    header = struct.pack("!IIBBBBB", width, height, 8, 2, 0, 0, 0)
    return (
        b"\x89PNG\r\n\x1a\n"
        + chunk(b"IHDR", header)
        + chunk(b"IDAT", zlib.compress(b"".join(rows)))
        + chunk(b"IEND", b"")
    )


page = '''<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shape A candidate test</title>
<style>
  body { margin: 0; padding: 16px; font-family: sans-serif; }
  #shape { display: block; width: calc(100vw - 32px); height: auto; }
  @media (min-width: 600.01px) {
    #shape { width: calc(50vw - 32px); }
  }
  @media (min-width: 1200.01px) {
    #shape { width: 568px; }
  }
</style>
<p>Shape A: rectangle and circle</p>
<img id="shape" src="shape-a-800.png" width="1440" height="720"
     alt="Blue rectangle and amber circle on a pale background"
     srcset="shape-a-480.png 480w, shape-a-800.png 800w,
             shape-a-1200.png 1200w, shape-a-1440.png 1440w"
     sizes="(max-width: 600px) calc(100vw - 32px),
            (max-width: 1200px) calc(50vw - 32px), 568px">
</html>
'''

root = Path(tempfile.mkdtemp(prefix="responsive-shapes-"))
for width in (480, 800, 1200, 1440):
    (root / f"shape-a-{width}.png").write_bytes(make_png(width))
(root / "index.html").write_text(page, encoding="utf-8")
handler = partial(SimpleHTTPRequestHandler, directory=str(root))
with ThreadingHTTPServer(("127.0.0.1", 0), handler) as server:
    print(f"Files: {root}", flush=True)
    print(f"Open http://127.0.0.1:{server.server_port}/", flush=True)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass

在終端機中切換到儲存程式的資料夾,然後執行:

python3 responsive_demo.py

在 Chrome 中開啟程式列出的本機網址。測試期間請保持終端機運作;完成後在終端機按下 Control+C。程式會要求一個可用的連接埠,因此請使用它列出的網址,不要假設連接埠號固定。

桌面測試的 CSS 門檻使用 600.01px 和 1200.01px,以配合整數視埠寬度。請測試表中的確切整數值。將範例套用到正式版面配置時,請讓實際 CSS 斷點與來源尺寸條件一致,並納入任何小數寬度的行為。

如何在 Mac 上的 Chrome 中確認選擇結果?

以滑鼠右鍵點按形狀並選取 Inspect,即可開啟 DevTools 並選取其圖片元素。Chrome 說明這是檢查元素及其套用樣式的直接方式。開啟 Chrome DevTools

  1. 點按 Toggle device toolbar,並在 Dimensions 下選擇 Responsive。
  2. 將視埠寬度設為 1000。
  3. 在工具列的 More options 選單中,選擇 Add device pixel ratio,接著選取 DPR 2。
  4. 如有需要,使用 More options,再選擇 Add device type,然後選取 Desktop。
  5. 開啟 Network,啟用 Disable cache,並在 DevTools 開啟時重新載入頁面。
  6. 篩選 shape-a,找出所要求的圖片。

Chrome 在裝置模式指南中說明視埠、DPR 和裝置類型控制項,並在網路功能參考文件中說明快取控制項。

頁面載入後,請在 DevTools 的 Console 中執行以下完整診斷程式碼片段:

(() => {
  const image = document.getElementById("shape");
  if (!image) throw new Error("The Shape A image was not found.");

  const viewport = window.innerWidth;
  const slot = window.matchMedia("(max-width: 600px)").matches
    ? viewport - 32
    : window.matchMedia("(max-width: 1200px)").matches
      ? viewport / 2 - 32
      : 568;

  console.table({
    viewportCSSPixels: viewport,
    devicePixelRatio: window.devicePixelRatio,
    declaredSlotCSSPixels: slot,
    renderedWidthCSSPixels: image.getBoundingClientRect().width,
    targetResourcePixels: slot * window.devicePixelRatio,
    selectedURL: image.currentSrc,
    naturalWidthCSSPixels: image.naturalWidth
  });
})();

此程式碼片段只會針對本示範計算宣告的版位;它不是通用的 sizes 剖析器。請比較宣告的版位與呈現寬度,再將所選檔名與預測表比較。

currentSrc 會回報瀏覽器選取的完整 URL。這並不能證明資源已成功載入,因此也請檢查圖片請求和顯示結果。在這個範例中,讀取 src 只會顯示後備屬性。MDN:HTMLImageElement.currentSrc

請勿將 naturalWidth 解讀為 PNG 編碼後的寬度。它是經過密度校正、以 CSS 像素表示的寬度。對於這個受控測試素材,產生檔案的尺寸和檔名可指出其資源寬度。MDN:HTMLImageElement.naturalWidth

在 600 和 601 CSS 像素處分別重複測試,並針對每種條件重新載入頁面。記錄視埠、回報的 DPR、版位、所選檔名,以及請求是否完成。這些記錄有助於區分可重複的全新載入結果,以及已載入頁面在調整尺寸時做出的一連串選擇。

預測結果不符時應檢查哪些項目?

先逐一檢查輸入值,再變更候選項清單:

  1. 檢查描述符類型。 使用 1x 和 2x 等密度描述符時,請勿透過 sizes 套用寬度描述符的計算方式。
  2. 檢查第一個符合的條件。 較早符合的條件優先於後續項目。
  3. 比較宣告寬度與呈現寬度。 100vw 宣告可能會高估受 CSS 限制而只能放在窄欄中的版位。
  4. 讀取實際 DPR。 頁面縮放或顯示器變更後,再次確認數值。
  5. 比較受控的重新載入結果。 重複測試時保持相同的視埠、DPR 和載入條件。
  6. 檢查資源是否可用。 所選 URL 與成功顯示的圖片是兩項不同的觀察結果。

缺少明確的來源尺寸,或來源尺寸無法使用時,HTML 剖析器可能會採用 100vw 作為預設值。這可能會解釋為何窄圖片的目標值出乎意料地大。請提供準確的來源尺寸清單,不要假設瀏覽器會從 CSS 推斷預期的欄寬。HTML 標準:剖析 sizes 屬性

如果頁面使用 picture 元素,請先確認適用哪個來源分支,再計算密度。來源選擇會先於該分支候選項之間的選擇;不同裁切版本屬於這種藝術指導機制。在 HTML 中使用響應式圖片

哪些支援限制會影響這個方法?

MDN 將 sizes 屬性標示為各瀏覽器普遍支援,但這不代表所有較新的關鍵字都同樣受到支援。這裡使用的明確條件提供了直接的測試方式,無須依賴自動尺寸。MDN:HTMLImageElement.sizes

sizes="auto" 對延遲載入圖片有效,能讓選擇程序使用其版面寬度。如果採用此關鍵字,請在 auto 後保留明確的後備尺寸,供不支援此關鍵字的瀏覽器使用,並提供圖片尺寸。若不調整這些前提條件,請勿將它套用到這個立即載入的範例。MDN 的圖片元素參考文件

Chrome 的裝置控制項會在模擬條件下測試 Chrome 的行為,無法證明 Safari 或 Firefox 會選擇哪些候選項。Chrome 也指出裝置模式只是一種近似,因此如果另一個瀏覽器的行為很重要,請直接在該瀏覽器中測試。Chrome DevTools:裝置模式的限制

一項實用的預測應以可供檢查的紀錄收尾:適用的來源集、解析後的來源尺寸、回報的 DPR、計算出的目標值,以及觀察到的 currentSrc。當這些輸入值與自行編寫的版面配置一致時,你就能清楚說明資源選擇結果,並判斷下一步應調整 CSS、sizes 還是可用的圖片寬度。

來源