現今複雜的裝置生態中,有一個特徵是螢幕像素密度範圍非常廣泛。有些裝置的螢幕解析度相當高,但其他裝置則落後許多。應用程式開發人員需要支援多種像素密度,這可能相當困難。在行動版網站上,挑戰會因多項因素而加劇:
- 多種板型規格的裝置。
- 網路頻寬和電池續航力受限。
就圖片而言,網頁應用程式開發人員的目標是盡可能以最有效率的方式提供最佳品質的圖片。本文將介紹一些實用技巧,讓您在現今和不久的將來實現這項目標。
盡量避免使用圖片
在開啟這個罐頭之前,請記住,網際網路有許多強大的技術,大多不受解析度和 dpi 影響。具體來說,文字、SVG 和大部分 CSS 都會「正常運作」,因為網頁的自動像素縮放功能 (透過 devicePixelRatio)。
不過,您不一定能避免使用點陣圖。舉例來說,您可能會收到很難以純 SVG/CSS 複製的素材資源,或是要處理相片。雖然您可以自動將圖片轉換為 SVG,但將相片轉為向量圖並無意義,因為放大的版本通常看起來不太好。
背景
顯示密度的簡短發展史
早期電腦螢幕的像素密度為 72 或 96 dpi (每英寸點數)。
螢幕的像素密度逐漸提升,這主要是受到行動裝置用途的影響,因為使用者通常會將手機拿得更靠近臉部,因此更容易看見像素。到了 2008 年,150 dpi 的手機成為新標準。螢幕密度增加的趨勢持續延續,目前的新款手機螢幕密度達到 300dpi (Apple 稱之為「Retina」)。
當然,終極目標是打造完全看不見像素的螢幕。就手機板型規格而言,目前一代的 Retina/HiDPI 螢幕可能已接近理想狀態。不過,Project Glass 等新類型的硬體和穿戴式裝置,可能會持續提高像素密度。
實際上,低密度圖片在舊螢幕和新螢幕上看起來應該是相同的,但相較於使用者習慣看到的高密度圖片,低密度圖片看起來會顯得粗糙且有顆粒感。以下是 1x 圖片在 2x 螢幕上的粗略模擬結果。相較之下,2x 圖片看起來相當不錯。


網頁上的像素
網頁設計時,99% 的螢幕解析度為 96dpi (或假裝),而且很少提供變化版本。由於螢幕大小和密度差異甚大,我們需要一種標準做法,讓圖片在各種螢幕密度和尺寸下都能呈現良好的效果。
HTML 規格最近解決了這個問題,定義了製造商用來判斷 CSS 像素大小的參考像素。
製造商可以使用參考像素,判斷裝置實體像素相對於標準或理想像素的大小。這個比例稱為裝置像素比例。
計算裝置像素比例
假設智慧型手機的螢幕實體像素大小為每英寸 180 像素 (ppi)。計算裝置像素比例需經過三個步驟:
比較裝置實際握持距離與參考像素的距離。
根據規格,我們知道在 28 英寸的情況下,每英寸的理想像素數為 96 像素。不過,由於這是智慧型手機,使用者會將裝置拿得比筆電更靠近臉部。我們假設這個距離為 18 英寸。
將距離比率乘以標準密度 (96ppi),即可取得指定距離的理想像素密度。
idealPixelDensity = (28/18) * 96 = 約每英寸 150 像素
將實體像素密度與理想像素密度的比率相乘,即可取得裝置像素比例。
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 圖片技術總覽
為了盡快顯示最佳品質的圖片,解決這類問題的方法有很多,大致可分為以下兩類:
- 最佳化單一圖片,以及
- 在多張圖片之間最佳化選取。
單張圖片方法:使用一張圖片,但以巧妙的方式呈現。這些方法的缺點是,您必然會犧牲效能,因為即使在 DPI 較低的舊裝置上,您仍會下載 HiDPI 圖片。以下是單一圖片的幾種做法:
- 經過大量壓縮的 HiDPI 圖片
- 超讚的圖片格式
- 漸進式圖片格式
多張圖片方法:使用多張圖片,但以聰明的方式挑選要載入的圖片。這些方法會讓開發人員產生額外的負擔,因為他們必須為同一個素材資源建立多個版本,然後找出決策策略。可選擇的類型如下:
- JavaScript
- 伺服器端放送
- CSS 媒體查詢
- 內建瀏覽器功能 (
image-set()
、<img srcset>
)
經過大量壓縮的 HiDPI 圖片
下載一般網站時,圖片已佔用 60% 的頻寬。我們會為所有用戶端提供 HiDPI 圖片,以便提高這項數字。會成長多少?
我執行了一些測試,產生 1x 和 2x 圖片片段,JPEG 品質為 90、50 和 20。以下是shell 指令碼 (使用 ImageMagick) 產生這些圖片:



從這項小型且非科學的抽樣調查來看,壓縮大型圖片似乎可提供良好的品質與大小權衡。在我看來,經過大量壓縮的 2x 圖像實際上比未壓縮的 1x 圖片更美觀。
當然,為 2x 裝置提供低品質、高度壓縮的 2x 圖像,比提供較高品質的圖像更糟糕,而且上述方法會導致圖像品質受罰。如果您比較品質:90 張圖片和品質:20 張圖片,您會發現清晰度會降低,顆粒感會增加。如果高品質圖片是關鍵 (例如相片檢視器應用程式),或者應用程式開發人員不願妥協,這些構件可能就無法接受。
上述比較結果完全使用壓縮 JPEG 檔案。值得一提的是,廣泛實作的圖片格式 (JPEG、PNG、GIF) 之間存在許多取捨,因此我們會…
超讚的圖片格式
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 程式庫可執行上述功能,但很遺憾,其中沒有任何一個程式庫特別出色。
這種方法的一大缺點是,使用 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);
}
}
透過這種做法,您可以重新獲得 JS 解決方案中失去的前瞻剖析功能。您也可以靈活選擇回應式中斷點 (例如,您可以使用低、中和高 DPI 圖片),這在伺服器端方法中是無法做到的。
很遺憾,這項功能仍有點難以操作,而且會產生奇怪的 CSS (或需要預先處理)。此外,這個方法僅限於 CSS 屬性,因此無法設定 <img src>
,且圖片必須全部是含有背景的元素。最後,如果您嚴格依賴裝置像素比率,在EDGE 連線下,高 DPI 智慧型手機最終會下載巨大的 2x 圖片素材資源。這並非最佳的使用者體驗。
使用新的瀏覽器功能
最近有許多討論,討論內容是關於高 DPI 圖片問題的網路平台支援。Apple 最近也加入這個領域,將 image-set() CSS 函式帶入 WebKit。因此,Safari 和 Chrome 都支援這項功能。由於這是 CSS 函式,image-set()
無法解決 <img>
標記的問題。輸入 @srcset 可解決這個問題,但 (截至本文撰寫時) 尚未有任何參考實作項目。下一節將深入探討 image-set
和 srcset
。
支援高 DPI 的瀏覽器功能
最終,您決定採用哪種做法,取決於您的特定需求。不過,請注意,上述所有方法都有缺點。不過,未來一旦 image-set
和 srcset 廣泛支援,就會是解決這個問題的適當解決方案。我們先來談談一些最佳做法,讓我們盡可能實現理想的未來。
首先,這兩者有何不同?image-set()
是 CSS 函式,適合用於背景 CSS 屬性的值。srcset 是 <img>
元素專用的屬性,語法也類似。這兩個標記都能讓您指定圖片宣告,但 srcset 屬性還可讓您根據檢視區大小設定要載入的圖片。
圖片集的最佳做法
image-set()
CSS 函式可使用 -webkit-image-set()
做為前置字串。語法相當簡單,只需使用一或多個以半形逗號分隔的圖像宣告,其中包含網址字串或 url()
函式,後面接著相關聯的解析度。例如:
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
這會告訴瀏覽器有兩張圖片可供選擇。其中一個是針對 1x 螢幕進行最佳化,另一個則是針對 2x 螢幕進行最佳化。接著,瀏覽器會根據各種因素 (如果瀏覽器夠聰明的話,甚至可能包括網路速度) 選擇要載入哪一個 (據我所知,目前尚未實作)。
除了載入正確的圖片,瀏覽器也會相應縮放圖片。換句話說,瀏覽器會假設 2x 圖片的大小是 1x 圖片的兩倍,因此會將 2x 圖片縮小 2 倍,讓圖片在頁面上顯示為相同大小。
除了指定 1x、1.5x 或 Nx,您也可以使用 dpi 指定特定裝置的像素密度。
這麼做通常都能獲得不錯的效果,但在某些不支援 image-set
屬性的瀏覽器中,系統將完全不會顯示圖片!這顯然不理想,因此您必須使用備用方案 (或一系列備用方案) 來解決這個問題:
background-image: url(icon1x.jpg);
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
/* This will be useful if image-set gets into the platform, unprefixed.
Also include other prefixed versions of this */
background-image: image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
上述程式碼會在支援 image-set 的瀏覽器中載入適當的素材資源,否則會改用 1x 素材資源。明顯的警告是,雖然 image-set()
瀏覽器支援率偏低,但大多數使用者代理程式都會取得 1x 素材資源。
這個示範使用 image-set()
載入正確的圖片,如果系統不支援這個 CSS 函式,則會改用 1x 素材資源。
此時,您可能會想知道為什麼不只為 image-set()
進行 polyfill (也就是為其建構 JavaScript shim),並將其稱為一天?事實上,為 CSS 函式實作有效的 polyfill 相當困難。(如要詳細瞭解原因,請參閱這個 www 風格討論)。
圖片 srcset
以下是 srcset 的範例:
<img alt="my awesome image"
src="banner.jpeg"
srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">
如您所見,除了 image-set
提供的 x 宣告外,srcset 元素也會採用與檢視區大小相對應的 w 和 h 值,嘗試提供最相關的版本。上述資源會將 banner-phone.jpeg 提供給檢視區寬度小於 640 像素的裝置、banner-phone-HD.jpeg 提供給小螢幕高 DPI 裝置、banner-HD.jpeg 提供給螢幕大於 640 像素的高 DPI 裝置,以及 banner.jpeg 提供給其他裝置。
為圖片元素使用 image-set
由於大多數瀏覽器都未實作 img 元素的 srcset 屬性,因此您可能會想將 img 元素替換為帶有背景的 <div>
,並使用圖片集合方法。這麼做是可行的,但有附帶條件。缺點是 <img>
標記具有長期語意值。實際上,這項做法對於網頁檢索器和無障礙功能來說特別重要。
如果您最後使用 -webkit-image-set
,可能會想使用 background CSS 屬性。這種做法的缺點是,您需要指定圖片大小,但如果使用非 1x 圖片,則無法得知圖片大小。請改用以下內容 CSS 屬性:
<div id="my-content-image"
style="content: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x);">
</div>
系統會根據 devicePixelRatio 自動調整圖片。請參閱這個範例,瞭解上述技術的實際運作方式,並為不支援 image-set
的瀏覽器額外提供 url()
備用方案。
使用 srcset 進行填充
srcset
的一項便利功能是,它會提供自然的備用方案。如果未實作 srcset 屬性,所有瀏覽器都會處理 src 屬性。此外,由於這只是 HTML 屬性,因此可以使用 JavaScript 建立 polyfill。
這個 polyfill 會附帶單元測試,確保盡可能符合規格。此外,如果已原生實作 srcset,系統會進行檢查,防止 polyfill 執行任何程式碼。
以下是 polyfill 示範。
結論
沒有萬靈丹可以解決高 DPI 圖片的問題。
最簡單的解決方案就是完全避免使用圖片,改用 SVG 和 CSS。不過,這不一定是實際可行的做法,尤其是當你在網站上使用高品質圖像時。
JS、CSS 和使用伺服器端的方法各有優缺點。不過,最有希望的方法是利用新的瀏覽器功能。雖然瀏覽器對 image-set
和 srcset
的支援仍不完整,但目前有合理的備用方案可供使用。
總結來說,我的建議如下:
- 針對背景圖片,請使用 image-set 和適當的備用方案,以便在瀏覽器不支援時使用。
- 針對內容圖片,請使用 srcset polyfill,或改用使用 image-set (請參閱上方說明)。
- 如果您願意犧牲圖片品質,建議您使用經過大量壓縮的 2x 圖片。