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

史穆斯 (Boris Smus)
Boris Smus

在現今複雜的裝置環境中,其中一項功能就是可用的多種螢幕像素密度。有些裝置配備高解析度螢幕,有些則配備超亮設計。應用程式開發人員需要支援多種像素密度,這可能會相當困難。在行動版網站上 遇到的難題又會遇到許多因素

  • 有多種板型規格的裝置供您選擇。
  • 限制網路頻寬和電池續航力。

就圖片而言,網頁應用程式開發人員的目標是盡可能以高效率提供最高畫質的圖片。本文將介紹一些實用技巧,讓您目前和即將到來都能做到這點。

盡量避免使用圖片

請記住,網路有許多強大技術,主要與解析度和 DPI 無關。 具體來說,文字、SVG 和許多 CSS 會「正常運作」,因為網路上的自動像素縮放功能 (透過 devicePixelRatio) 提供。

不過,您不一定每次都能避開光柵圖片。舉例來說,您收到的素材資源可能難以在純 SVG/CSS 中複製,或是處理相片。雖然您可以將圖片自動轉換為 SVG,但向量相片沒有意義,因為放大後的相片通常看起來並不理想。

背景

非常短的顯示密度

早期,電腦螢幕的像素密度為 72 或 96dpi (dots per inch,每英寸像素密度為 72 或 96dpi)。

螢幕在像素密度上會逐漸改善,主要取決於行動裝置用途,使用者通常會將手機靠近臉部,使像素更加顯眼。到了 2008 年,150 dpi 手機已成為新常態。螢幕密度增加也持續上升,而今日的新手機具備 300 dpi 螢幕 (Apple 推出的品牌「Retina」)。

當然,聖殿是完全看不到像素的螢幕,就手機板型規格而言,最新版 Retina/HiDPI 螢幕可能相當接近理想狀態。但是,Project Glass 等新的硬體和穿戴式裝置類別可能會繼續增加像素密度。

實際上,低密度圖片在新螢幕上顯示的外觀應該與舊螢幕相同,但相較於原本使用者看到的清晰圖像高密度圖像,低密度圖片看起來會很刺耳且像素化。以下粗略模擬 1 倍圖片在 2 倍螢幕上的外觀。相反地,2 倍大小的圖片看起來很棒。

Baboon 1x
Baboon 2 倍
Baboons!,像素密度不同。

Pixel 網頁版

網路設計時,99% 的螢幕皆具有 96 dpi (或假定),且其中一些條款針對這個前端的不同版本而設計。由於螢幕大小和密度的差異很大,因此我們需要一套標準做法,才能確保圖片在各種螢幕密度和尺寸中都能呈現良好效果。

HTML 規格日前藉由定義參照像素,讓製造商決定 CSS 像素的大小,藉此解決這個問題。

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

正在計算裝置像素比例

假設智慧型手機的畫面,實體像素大小為每英寸 180 像素 (ppi)。計算裝置像素比例需要三個步驟:

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

    根據規格,我們知道在 28 吋的理想情況下,理想解析度為每英寸 96 像素。不過,由於智慧型手機是智慧型手機,使用者手持裝置時,會盡量比手持筆記型電腦。我們估計距離是 18 英寸

  2. 將距離比例與標準密度 (96 ppi) 相乘,得出指定距離的理想像素密度。

    理想像素密度 = (28/18) * 96 = 每英寸 150 像素 (約略值)

  3. 計算實體像素密度與理想像素密度的比率,藉此取得裝置的像素比例。

    devicePixelRatio = 180/150 = 1.2

devicePixelRatio 的計算方式。
顯示一個參考角像素的圖表,說明 devicePixelRatio 的計算方式。

現在,當瀏覽器需要瞭解如何根據理想或標準解析度調整圖片大小,以便將圖片大小調整為 1.2 時,瀏覽器會將裝置像素比例設為 1.2,這表示每個理想像素而言,都會有 1.2 個實體像素。以下是理想 (依網頁規格定義) 與實體 (裝置螢幕上的點) 像素之間的計算公式:

physicalPixels = window.devicePixelRatio * idealPixels

過去,裝置供應商傾向於達到 devicePixelRatios (DPR)。Apple 的 iPhone 和 iPad 報告的 DPR 為 1,而其 Retina 等同技術的報告則是 2. CSS 規格建議

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

圓形比率可能會比較少,造成子像素構件的數量較少。

不過,裝置環境的實際性已更加廣泛,且 Android 手機的 DPR 通常為 1.5。Nexus 7 平板電腦的 DPR 約 1.33,計算方式與上述範例類似。我們預計日後會越來越多顯示可變動 DPR 的裝置。因此,請勿假設用戶端會有整數 DPR。

HiDPI 圖片技術總覽

有許多技巧可以解決盡快顯示最佳圖片的問題,大致分為兩類:

  1. 最佳化單張圖片
  2. 最佳化多張圖片之間的選擇。

單一圖片方法:只使用一張圖片,但請以巧妙運用單一圖片。 上述方法有缺點,您一定會大幅改善效能,因為即使在低 DPI 的舊裝置上,您也會下載 HiDPI 映像檔。以下是針對單一圖片情況的一些做法:

  • 大量壓縮 HiDPI 圖片
  • 超棒的圖片格式
  • 漸進式圖片格式

多種圖片方法:使用多張圖片,但要慎選要載入的圖片這些方法對開發人員而言是固有的負擔,用來建立同一資產的多個版本,然後再製定決策策略。可選擇的類型如下:

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

大量壓縮 HiDPI 圖片

從一般網站下載圖片以來,圖片都已佔用大量頻寬的 60%。只要向所有用戶端提供 HiDPI 映像檔,即可增加這個數量。未來會擴大多少?

我執行了一些測試,以 90、50 和 20 的 JPEG 畫質,產生 1 倍和 2 倍的圖片片段。以下是我使用 ImageMagick 產生密碼的 shell 指令碼

資訊方塊範例 1. 資訊方塊範例 2. 資訊方塊範例 3.
不同壓縮和像素密度的圖片範例。

以這種小型的取樣方式來看,壓縮大型圖片在品質與大小之間取得了不錯的取捨。對我看,大量壓縮的 2 倍圖像效果實際上比未壓縮的 1x 相片更好。

當然,相較於提供高品質圖片,高度壓縮的 2 倍圖像到 2 倍裝置效果較差,而且上述方法會對圖片品質產生懲罰。比較品質:90 張圖片與品質:20 張圖片時,會發現畫質下降及顆粒會提升。如果高畫質圖片是關鍵 (例如相片檢視器應用程式),或者不願意入侵的應用程式開發人員,系統可能無法接受這些成品。

上述比較完全使用壓縮的 JPEG 檔案。值得注意的是,在廣泛實作的圖片格式 (JPEG、PNG、GIF) 之間有許多權衡取捨,這樣讓我們...

超棒的圖片格式

WebP 是相當引人入勝的圖片格式,這種格式能充分壓縮,同時保持高圖片擬真度。當然,尚未在任何地方實作

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

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

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

這種做法有幾個問題。首先,image() 並未全面實作。其次,以這個 WebP 圖片庫為基礎,雖然 WebP 壓縮功能會將 JPEG 壓縮到水中,但改善仍相對逐步增加約 30%。因此,光是 WebP 不足以解決高 DPI 問題。

漸進式圖片格式

漸進式圖片格式 (例如 JPEG 2000、Progressive JPEG、Progressive PNG 和 GIF) 有利於在圖片完全載入前就先取得優勢 (有點辯論)。雖然這些項目可能會產生一些大小負擔,但有衝突的證據。Jeff Atwood 宣稱漸進式模式「PNG 圖片尺寸的增加約 20%,而 JPEG 和 GIF 圖片尺寸的佔比約為 10%」。然而,Stoyan Stefanov 認為,針對大型檔案,漸進式模式會更有效率 (在大多數情況下)。

乍看之下,漸進式圖片在盡可能提供最佳圖片的情況下,效果會非常出色。其原理是,瀏覽器知道額外資料不會提高圖片品質後,便可以停止下載並解碼圖片 (也就是說,所有擬真度改善項目都是小於像素)。

雖然連線很容易終止,但重新啟動通常都很高昂。對於含有許多圖片的網站,最有效的方法是讓單一 HTTP 連線保持運作,並盡可能重複使用該連線。 如果連線因為下載了足夠的映像檔而提早終止,瀏覽器就需要建立新的連線,這種環境的低延遲時間可能會很慢

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

最後,這種方法的顯著限制是您無法選擇要載入的圖片,只會改變同一張圖片的不同擬真度。因此,這並不適用於「藝術方向」用途。

使用 JavaScript 決定要載入的圖片

決定要載入的圖片時,最顯而易見的方法就是在用戶端中使用 JavaScript。這有助於您找出 使用者代理程式的所有相關資訊,並做正確的事您可透過 window.devicePixelRatio 判斷裝置的像素比例、取得螢幕寬度和高度,甚至透過 navigator.connection 進行某些網路連線,或發出假要求 (例如 foresight.js 程式庫)。收集這些資訊後,您可以決定要載入哪張圖片。

大約有一百萬個 JavaScript 程式庫會進行類似操作,但很可惜,這些程式庫特別優異。

這種做法的一大缺點,就是使用 JavaScript 時,您必須延後載入圖片,直到預想剖析器完成之後。基本上,這表示系統甚至必須等到 pageload 事件觸發之後,才會開始下載圖片。詳情請參閱 Jason Grigsby 的文章

決定要在伺服器上載入哪些圖片

您可以為提供的每張圖片編寫自訂要求處理常式,將決定延後到伺服器端。這類處理常式會根據使用者代理程式 (唯一傳送給伺服器的資訊) 檢查 Retina 支援。然後,根據伺服器端邏輯是否提供 HiDPI 資產,您需載入適當的資產 (根據一些已知慣例命名)。

遺憾的是,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); }
}

夾帶所有廠商前置字元後,情況會稍微複雜,尤其是因為「最小值」和「最大值」前置字串的位置差異

@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>,且圖片的所有元素都必須是有背景的元素。最後,完全取決於裝置的像素比例,當高 DPI 智慧型手機透過 EDGE 連線下載大量 2 倍的圖片素材資源時,最終可能還是會受到影響。這並非最佳的使用者體驗。

使用新的瀏覽器功能

最近有許多關於網路平台支援的高 DPI 圖片問題討論。Apple 最近把 image-set() CSS 函式帶到 WebKit 了,入侵了這個空間。因此,Safari 和 Chrome 都支援這項功能。由於這是 CSS 函式,因此 image-set() 無法解決 <img> 標記的問題。輸入 @srcset 可以解決這個問題,但在本文撰寫期間,(目前) 沒有參照實作。下一節將進一步說明 image-setsrcset

支援高 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
);

這樣一來,瀏覽器就會從兩張圖片中選擇。 其中一個針對 1 倍螢幕和 2 倍螢幕進行最佳化。接著,瀏覽器會根據各種因素,選擇要載入的哪一種因素,如果瀏覽器非常聰明 (目前還沒有實作),甚至可能會考慮網路速度。

除了載入正確的圖片以外,瀏覽器也會縮放圖片。換句話說,瀏覽器會假設 2 張圖片是 1x 的兩倍,因此會將 2 倍的圖片縮小為 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
);

上述內容會在支援圖片集的瀏覽器中載入適當的素材資源,否則會改回使用 1x 素材資源。顯而易見的一點是,雖然 image-set() 瀏覽器支援很低,但大多數的使用者代理程式都會取得 1 倍的素材資源。

此示範使用 image-set() 載入正確的圖片,如果不支援此 CSS 函式,會改回使用 1 倍素材資源。

此時,您可能會好奇,為何不只是 polyfill (也就是為 image-set() 建構 JavaScript 填充碼),並每天呼叫一次?從此來看,為 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 值,嘗試提供最相關的版本。上述內容會向螢幕可視區域寬度低於 640px 的裝置,向 px-phone-HD.jpeg 放送至高螢幕高 DPI 裝置、橫幅-HD.jpeg 至螢幕大於 640px 的高 DPI 裝置,以及 banner.jpeg 提供給所有其他裝置。

為圖片元素使用圖片集

由於大部分瀏覽器並未實作 img 元素的 srcset 屬性,因此您可能會想將 img 元素替換成背景 <div>,並採用圖片設定方法。這點有效,但要注意。缺點是 <img> 標記具有長期語意值。從實務上來說,這對網頁檢索器和存取方式而言至關重要。

如果最終使用 -webkit-image-set,您可能會嘗試使用背景 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-setsrcset 的支援仍然不完整,但現在您可以使用合理的替代方案。

簡單來說,我的建議如下: