適用於可變像素密度的高 DPI 圖片

Boris Smus
Boris Smus

發布日期:2012 年 8 月 22 日,上次更新日期:2025 年 4 月 14 日

市面上有許多裝置,因此螢幕像素密度的範圍非常廣泛。應用程式開發人員需要支援各種像素密度,這可能相當困難。在行動版網站上,挑戰會因多項因素而加劇:

  • 多種板型規格的裝置。
  • 網路頻寬和電池續航力受限。

就圖片而言,網頁開發人員的目標是盡可能以最有效率的方式提供最佳品質的圖片。本文將介紹一些實用技巧,讓您現在和日後都能順利執行這項操作。

盡量避免使用圖片

在假設您需要加入圖片之前,請記住,網路上有許多強大的技術,大多不受解析度和 dpi 影響。具體來說,文字、SVG 和大部分 CSS 都會「正常運作」,因為網頁會透過 devicePixelRatio 自動調整像素。

不過,您不一定能避免使用點陣圖。舉例來說,您可能會獲得一些很難以純 SVG 或 CSS 複製的素材資源。您可能正在處理相片。雖然您可以自動將圖片轉換為 SVG,但將相片轉為向量圖形並無意義,因為放大的版本通常看起來不太好。

像素密度發展歷程

早期電腦螢幕的像素密度為 72 或 96 每英寸像素數 (DPI)

由於使用者通常會將手機拿得更靠近臉部,因此螢幕的像素密度會隨著行動裝置的進步而逐漸提升。到了 2008 年,150 dpi 的手機成為新標準。螢幕密度持續增加,目前的手機螢幕密度為 300dpi。

實際上,低密度圖片在舊螢幕和新螢幕上看起來應該是相同的,但相較於使用者習慣看到的高密度圖片,低密度圖片看起來會顯得粗糙且有顆粒感。以下是 1x 圖片在 2x 螢幕上的粗略模擬結果。相較之下,2x 圖片看起來相當不錯。

1x 像素 2 倍像素
Baboon 1x 像素密度。 Baboon 2x 像素密度。
不同像素密度的狒狒。

網頁上的像素

在網頁設計時,99% 的螢幕解析度為 96dpi,而且很少提供變化版本。由於螢幕大小和密度差異很大,我們需要一種標準方式,讓圖片在所有螢幕上都看起來都很棒。

HTML 規格解決這個問題的方法,是定義製造商用來判斷 CSS 像素大小的參考像素。

製造商可以使用參考像素,判斷裝置的實體像素相對於標準或理想像素的大小。這個比例稱為裝置像素比例。

計算裝置像素比例

假設某款手機的螢幕實體像素大小為每英寸 180 像素 (ppi)。計算裝置像素比例需經過三個步驟:

  1. 比較裝置實際握持距離與參考像素的距離。

    根據規格,我們知道在 28 英寸的情況下,每英寸的理想像素數為 96 像素。我們知道,使用手機時,使用者的臉會比使用筆電和電腦時更靠近裝置。在下列公式中,我們估計距離為 18 英寸。

  2. 將距離比率乘以標準密度 (96ppi),即可取得指定距離的理想像素密度。

    idealPixelDensity = (28/18) * 96 = 約每英寸 150 像素

  3. 將實體像素密度與理想像素密度的比率相乘,即可取得裝置像素比例。

    devicePixelRatio = 180/150 = 1.2

一個參考角度像素,用於說明裝置像素比例的計算方式。

因此,當瀏覽器需要瞭解如何調整圖片大小以符合螢幕時,根據理想或標準解析度,瀏覽器會參照裝置像素比例 1.2。也就是說,每個理想像素都會對應到 1.2 個實體像素。理想 (由網頁規格定義) 和實體 (裝置螢幕上的點) 像素之間的轉換公式如下:

physicalPixels = window.devicePixelRatio * idealPixels

過去,裝置供應商傾向於四捨五入 devicePixelRatios (DPR)。Apple 的 iPhone 和 iPad 會回報 1 的 DPR,而 Retina 對應裝置則會回報 2。CSS 規格建議:

像素單位是指最接近參考像素的整數裝置像素。

圓形比例較佳的原因之一,是因為這類比例可能會產生較少的子像素瑕疵

不過,實際的裝置環境變化更多,Android 手機的 DPR 通常為 1.5。Nexus 7 平板電腦的 DPR 約為 1.33,這是透過與前一個範例類似的計算方式得出的結果。未來會有更多裝置提供可變動的 DPR。因此,請勿假設客戶的 DPR 為整數。

HiDPI 圖片技巧

為了盡快顯示最佳品質的圖片,我們可以使用多種技術解決這項問題,大致分為以下兩類:

  1. 最佳化單張圖片。
  2. 在多張圖片之間最佳化選取。

單張圖片方法:使用一張圖片,但以巧妙的方式呈現。這些方法的缺點是,您必須下載高 DPI 圖片,即使是在 DPI 較低的舊裝置上,也必須如此,因此您必然會犧牲效能。以下是單一圖片的幾種做法:

  • 經過大量壓縮的 HiDPI 圖片
  • 超讚的圖片格式
  • 漸進式圖片格式

多張圖片方法:使用多張圖片,但以聰明的方式挑選要載入的圖片。這些方法會讓開發人員產生額外的負擔,因為他們必須為同一個素材資源建立多個版本,然後找出決策策略。可選擇的類型如下:

  • JavaScript
  • 伺服器端放送
  • CSS 媒體查詢
  • 內建瀏覽器功能 (image-set()<img srcset>)

經過大量壓縮的 HiDPI 圖片

下載一般網站時,圖片已佔用 60% 的頻寬。我們會為所有用戶端提供 HiDPI 圖片,因此這個數字會增加。還能再擴大多少?

我執行了一些測試,產生 1x 和 2x 圖片片段,JPEG 品質為 90、50 和 20。

同一個圖片的六種版本,壓縮和像素密度各有不同。 同一個圖片的六種版本,壓縮和像素密度各有不同。 同一個圖片的六種版本,壓縮和像素密度各有不同。

從這項小型且非科學的抽樣調查來看,壓縮大型圖片似乎可提供良好的品質與大小權衡。在我看來,經過大量壓縮的 2x 圖像實際上比未壓縮的 1x 圖片看起來更好。

不過,為 2x 裝置提供低品質、經過高度壓縮的 2x 圖像,比提供較高品質的圖像更糟糕,而且這種做法會導致圖像品質受罰。如果將 quality: 90 圖片與 quality: 20 圖片進行比較,您會發現圖片的清晰度會降低,且顆粒感會增加。如果圖片品質很重要 (例如相片檢視器應用程式),或是應用程式開發人員不願妥協,則可能無法接受含有 quality:20 的構件。

這項比較是完全使用壓縮 JPEG 檔案進行。值得一提的是,廣泛實作的圖片格式 (JPEG、PNG、GIF) 之間存在許多取捨,因此我們會…

WebP:超讚的圖片格式

WebP 是一種相當吸引人的圖片格式,可在壓縮時維持高圖片保真度。

其中一種方法是使用 JavaScript 檢查是否支援 WebP。使用 data-uri 載入 1 像素的圖片,等待載入或錯誤事件觸發,然後確認大小是否正確。Modernizr 隨附這類功能偵測指令碼,可透過 Modernizr.webp 使用。

不過,更理想的做法是在 CSS 中直接使用 image() 函式。因此,如果您有 WebP 圖片和 JPEG 備用圖片,可以編寫以下內容:

#pic {
  background: image("foo.webp", "foo.jpg");
}

這種做法存在幾個問題。首先,image()並未廣泛實作。其次,雖然 WebP 壓縮功能比 JPEG 更出色,但仍有改善空間,根據這個 WebP 相片庫,其大小約縮小 30%。因此,單靠 WebP 無法解決高 DPI 問題。

漸進式圖片格式

漸進式圖片格式 (例如 JPEG 2000、漸進式 JPEG、漸進式 PNG 和 GIF) 有著 (有爭議的) 優點,可在圖片完全載入前顯示圖片。雖然這可能會產生一些大小開銷,但相關證據相互矛盾。Jeff Atwood 聲稱,漸進模式「會使 PNG 圖片的大小增加約 20%,JPEG 和 GIF 圖片的大小增加約 10%」。不過,Stoyan Stefanov 聲稱,在大多數情況下,漸進模式對於大型檔案更有效率。

乍看之下,漸進式圖片在盡可能快速提供最佳品質圖片的情況下,似乎非常有希望。這項功能的概念是,一旦瀏覽器知道額外資料不會提升圖片品質 (也就是所有畫質改善都是子像素),就會停止下載及解碼圖片。

雖然連線可以快速終止,但通常需要花費大量時間才能重新啟動。對於含有大量圖片的網站,最有效率的方法是讓單一 HTTP 連線保持運作,盡可能重複使用。如果連線因已下載足夠的圖片而提早終止,瀏覽器就需要建立新的連線,這在低延遲環境中可能會非常緩慢。

解決這個問題的其中一個方法是使用 HTTP Range 要求,讓瀏覽器指定要擷取的位元組範圍。智慧型瀏覽器可以發出 HEAD 要求來取得標頭、處理標頭、決定實際需要多少圖片,然後擷取。很遺憾,網路伺服器不支援 HTTP Range,因此這個方法不切實際。

最後,這種方法的明顯限制是您無法選擇要載入的圖片,只能選擇同一個圖片的不同解析度。因此,這項功能無法解決「藝術方向」用途。

使用 JavaScript 決定要載入哪張圖片

決定要載入哪張圖片的第一個也是最明顯的方法,就是在用戶端中使用 JavaScript。這種做法可讓您瞭解使用者代理程式的所有資訊,並採取正確做法。您可以使用 window.devicePixelRatio 判斷裝置像素比例、取得螢幕寬度和高度,甚至可以使用 navigator.connection 進行網路連線嗅探或發出假要求,就像 foresight.js 程式庫那樣。收集所有這類資訊後,您可以決定要載入哪個圖片。

約有 一百萬個 JavaScript 程式庫使用這項技術。很遺憾,這些都不是特別出色的選項。

其中一個重大缺點是,您必須延遲圖片載入作業,直到前瞻剖析器完成後才能進行。這基本上表示,除非 pageload 事件觸發,否則圖片不會開始下載。如要進一步瞭解這項功能,請參閱 Jason Grigsby 的文章

決定要載入哪些圖片

您可以為每個顯示的圖片編寫自訂要求處理程序,將決定權交給伺服器端。這類處理程序會根據 User-Agent (與伺服器共用的唯一資訊) 檢查 Retina 支援情形。接著,根據伺服器端邏輯是否要提供 HiDPI 素材資源,您可以載入適當的素材資源 (依據某些已知慣例命名)。

很抱歉,User-Agent 不一定會提供足夠的資訊,讓您判斷裝置應接收高品質或低品質的圖片。因此,請避免使用 User-Agent 做出樣式決策。

使用 CSS 媒體查詢

由於 CSS 媒體查詢是宣告式,因此您可以表達自己的意圖,讓瀏覽器代為執行正確的操作。除了最常見的媒體查詢用途 (比對裝置大小) 之外,您也可以比對 devicePixelRatio。相關聯的媒體查詢是 device-pixel-ratio,並具有相關聯的最小和最大變數,這點您可能已瞭解。

如果您想載入高 DPI 圖片,且裝置像素比例超過閾值,請採取下列做法:

#my-image { background: (low.png); }

@media only screen and (min-device-pixel-ratio: 1.5) {
  #my-image { background: (high.png); }
}

當所有供應商前置字元混合在一起時,情況就會變得更複雜,尤其是因為「min」和「max」前置字元的放置位置差異很大:

@media only screen and (min--moz-device-pixel-ratio: 1.5),
    (-o-min-device-pixel-ratio: 3/2),
    (-webkit-min-device-pixel-ratio: 1.5),
    (min-device-pixel-ratio: 1.5) {

  #my-image {
    background:url(high.png);
  }
}

採用這種做法,您就能再次享有前瞻剖析的優點,而這正是 JavaScript 解決方案所缺乏的。您也可以靈活選擇回應式中斷點 (例如,您可以使用低、中和高 DPI 圖片),這在伺服器端方法中無法做到。

很抱歉,這項功能仍有點難以操作,而且會導致 CSS 外觀怪異或需要預先處理。此外,這個方法僅限於 CSS 屬性,因此無法設定 <img src>,且圖片必須全部是含有背景的元素。最後,如果您嚴格依賴裝置像素比率,在EDGE 連線下,高 DPI 行動電話可能會下載巨大的 2x 圖片素材資源。這並非最佳的使用者體驗。

由於 image-set() 是 CSS 函式,因此無法解決 <img> 標記的問題。輸入 @srcset 即可解決這個問題。下一節將深入探討 image-setsrcset

支援高 DPI 的瀏覽器功能

最終,您如何處理高 DPI 支援功能取決於您的特定需求。上述所有方法都有缺點。

image-setsrcset 目前廣泛支援,因此是最佳解決方案。我們還有其他最佳做法,可讓我們更接近舊版瀏覽器。

這兩者有何不同?image-set() 是 CSS 函式,適合用於背景 CSS 屬性的值。srcset<img> 元素專用的屬性,語法也相似。這兩個標記都能讓您指定圖片宣告,但 srcset 屬性還可讓您根據檢視區大小設定要載入的圖片。

圖片集最佳做法

image-set() 語法會採用一或多個以半形逗號分隔的圖片宣告,這些宣告包含網址字串或 url() 函式,後面接著適當的解析度。例如:

image-set(
  url("image1.jpg") 1x,
  url("image2.jpg") 2x
);

/* You can also include image-set without `url()` */
image-set(
  "image1.jpg" 1x,
  "image2.jpg" 2x
);

這會告訴瀏覽器有兩張圖片可供選擇。一張圖片是針對 1x 螢幕進行最佳化,另一張則是針對 2x 螢幕進行最佳化。瀏覽器會根據各種因素 (如果瀏覽器夠聰明,甚至可能包括網路速度) 選擇要載入哪一個。

除了載入正確的圖片,瀏覽器也會相應調整圖片大小。換句話說,瀏覽器會假設 2x 圖片的大小是 1x 圖片的兩倍,因此會將 2x 圖片縮小 2 倍,讓圖片在網頁上顯示的大小相同。

除了指定 1x、1.5x 或 Nx,您也可以使用 dpi 指定特定裝置像素密度。

如果您擔心舊版瀏覽器不支援 image-set 屬性,可以新增備用項,確保系統會顯示圖片。例如:

/* Fallback support. */
background-image: url(icon1x.jpg);
background-image: image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

這個範例程式碼會在支援 image-set 的瀏覽器中載入適當的素材資源,並在其他情況下改用 1x 素材資源。

此時,您可能會想知道為何不只為 image-set() 進行 polyfill (也就是為其建構 JavaScript 墊片),然後就結束?事實上,為 CSS 函式實作高效的 polyfill 相當困難。(如要詳細瞭解原因,請參閱這篇 www 風格討論)。

圖片 srcset

除了 image-set 提供的宣告外,srcset 元素也會採用與檢視區大小相對應的寬度和高度值,嘗試提供最相關的版本。

<img alt="my awesome image"
  src="banner.jpeg"
  srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">

這個範例會將 banner-phone.jpeg 提供給檢視區寬度小於 640 像素的裝置、banner-phone-HD.jpeg 提供給小螢幕高 DPI 裝置、banner-HD.jpeg 提供給螢幕寬度大於 640 像素的高 DPI 裝置,以及 banner.jpeg 提供給其他裝置。

為圖片元素使用 image-set

您可能會想將 img 元素替換為帶有背景的 <div>,並使用圖片集方法。這確實可行,但有附帶條件。缺點是 <img> 標記具有長期語義值。實際上,這對無障礙功能和網頁檢索器而言十分重要。

您可以使用 content CSS 屬性,自動根據 devicePixelRation 縮放圖片。例如:

<div id="my-content-image"
  style="content: -webkit-image-set(
    url(icon1x.jpg) 1x,
    url(icon2x.jpg) 2x);">
</div>

填補 srcset

srcset 的一項便利功能是,它會提供自然的備用方案。如果未實作 srcset 屬性,所有瀏覽器都會處理 src 屬性。此外,由於這只是 HTML 屬性,因此可以使用 JavaScript 建立 polyfill

這個 polyfill 會附帶單元測試,確保盡可能符合規格。此外,如果已原生實作 srcset,系統會進行檢查,防止 polyfill 執行任何程式碼。

結論

高 DPI 圖片的最佳解決方案是選擇 SVG 和 CSS。不過,這並非總是可行的解決方案,尤其是對於圖片密集的網站。

JavaScript、CSS 和伺服器端解決方案的做法各有優缺。最可行的做法是使用 image-setsrcset

總結來說,我的建議如下:

  • 針對背景圖片,請使用 image-set 和適當的備用方案,以便在瀏覽器不支援時使用。
  • 針對內容圖片,請使用 srcset polyfill,或改用使用 image-set (請參閱上方說明)。
  • 如果您願意犧牲圖片品質,建議您使用經過大量壓縮的 2x 圖片。