我們先從一個聽的最多的概念——PPI開始。
PPI
什么是PPI
PPI的復雜之處在于如果他所處的上下文環境不同,意義也會完全不一樣。 當我們在談論顯示設備的PPI時,它代指的屏幕的像素密度;當我們在談論和圖片相關時,我們談論的是打印時的分辨率或者打印機的打印精度。這里我們主要描述的前一種情況。
PPI全稱為Pixel Per Inch,譯為每英寸像素取值,更確切的說法應該是像素密度,也就是衡量單位物理面積內擁有像素值的情況。
如上圖所示,在1英寸單位內面積內擁有的像素越多,密度越大,PPI值就越高。但像素密度的實際意義是什么?它表達的是什么?或高或低對設備顯示來說有什么影響?
一般來說,我們當然希望PPI值越高越好,因為更高的PPI意味著在同一實際尺寸的物理屏幕上能容納更多的像素,能夠展現更多的畫面細節,也就意味著更平滑的畫面。原理如下:
什么是Pixel
但你有沒有仔細相關Pixel Per Inch中的pixel像素的概念究竟指的是什么樣的像素?你可能會反問我像素難道還分很多種不成?我可以很確定的告訴你,是的。
設備像素
無論是早期的CRT顯示器還是如今的LCD顯示器,都是基于點陣的。也就是說通過一些列的小點排列成一個大的矩形,不同的小點通過顯示不同的顏色來顯示成圖像。比如下圖就是LCD顯示器上一個6x6個小點排列成的矩陣:
注意每一個像素(pixel,也可以稱之為dot)又是由三個子像素(subpixel)紅綠藍組合而成。當需要顯示圖片信息時,它的工作原理可以如下圖所示:
上圖中的右側是放大之后我們能看到的像素,而左側就是對應像素在顯示器上的顯示情況了。
注意上圖代表的僅是LCD顯示器的物理像素情況,早期的CRT顯示器的物理像素同樣也是由獨立的點組成。但是不存在subpixel的概念,情況如下圖所示:
上面描述的這些顯示器上的像素我們就稱之為物理像素(physical pixel)或者設備像素(device pixel)。
CSS像素
作為web的開發者,我們接觸的更多的是用于控制元素樣式的單位像素。這里的像素我們稱之為CSS像素。
CSS像素有什么特別的地方?這里我們借用一個例子來說明:
假設我們用PC瀏覽器打開一個頁面,瀏覽器此時的寬度為800px,頁面上同時有一個400px寬的塊級元素容器。很明顯此時塊狀容器應該占頁面的一半。
但如果我們把頁面放大(通過“Ctrl鍵”加上“+號鍵”),放大為200%,也就是原來的兩倍。此時塊狀容器則橫向占滿了整個瀏覽器。
吊詭的是此時我們既沒有調整瀏覽器窗口大小,也沒有改變塊狀元素的css寬度,但是它看上去卻變大了一倍——這是因為我們把CSS像素放大為了原來的兩倍。
CSS像素與屏幕像素1:1同樣大小時:
CSS像素(黑色邊框)開始被拉伸,此時1個CSS像素大于1個屏幕像素
也就是說默認情況下一個CSS像素應該是等于一個物理像素的寬度的,但是瀏覽器的放大操作讓一個CSS像素等于了兩個設備像素寬度。在后面你會看到更復雜的情況,在高PPI的設備上,CSS像素甚至在默認狀態下就相當于多個物理像素的尺寸。
通過上面這個例子我想傳遞一個非常重要的概念,就是CSS像素從來都只是一個相對值。
正確答案
回到PPI上來,現在我們有了兩種像素,設備像素和CSS像素。那么PPI中的像素是指哪一種?
請記住,PI中的pixel指的應該是物理像素。
但是在維基百科中關于PPI的解釋,pixel被解釋為一種類似于分辨率下的像素:
The apparent PPI of a monitor depends upon the screen resolution
(that is, the number of pixels) and the size of the screen in use; a
monitor in 800×600 mode has a lower PPI than does the same monitor in a
1024×768 or 1280×960 mode.
上面這段話是在說,同一尺寸的顯示器在800x600分辨與1024x768分辨率下的像素密度明顯是不同的,明顯后者單位面積內的像素更多,當然后者的像素密度更高。
這里考慮了另一種情況,即在同一顯示器下因為分辨率調整導致顯示器的像素密度不同。這里的像素雖然不是在瀏覽器中顯示,但原理也類似于CSS像素,即由多個物理像素組成一個指定分辨率下的像素。
但問題是,這樣的比較是沒有任何意義的,我們通常在比較PPI時,一定是在跨設備比較,為了體現設備的技術優勢,也一定是拿設備的最優或者極限情況進行比較,這樣情況下分辨率下像素是與物理像素一一匹配的。 不能說一臺23寸的2k顯示器和一臺23寸的1080p顯示器因為都能調整到1440x960的分辨率,那么他們的PPI就相同了?PPI終究是體現設備某方面性能的參數。
也就是說,當我們在談論一臺設備的PPI時,它是一個定值,是一個固定的參數。
那么PPI怎么計算呢?沒錯,就和你想的一模一樣,用屏幕邊的物理像素除以物理尺寸即可,以Samsung Galaxy S4為例:
由此可見Galaxy S4的屏幕分辨率為441PPI。
The Bad and the Ugly
但PPI過高同樣也會帶來問題,相同的圖片素材,在越高的設備上會顯示的越小。以下是一個像素在不同PPI設備上的可見情況,隨著PPI的增高可視度越來越小:
那么可以預見一種很糟糕的情況是,同一尺寸的屏幕下假設PPI提高了一倍,很可能程序界面縮小了4倍(因為在屏幕尺寸不變的情況下物理像素點面積是原來的1/4)。
以Surface Pro 3為例,它的默認分辨率是2160x1440,也就是說Surface這臺設備的屏幕物理像素有2160x1440個點,同時默認分辨率情況下,一個點物理像素點對應于一個分辨率像素。 但因為屏幕只有12寸,像素密度非常高,于是就出現了上面的問題,各個文字和圖標被縮的太小了,電腦是完全不可用的。
解決方法是,Windows默認將所有的文本和素材(實際上就是分辨率像素)都放大了1.5倍(在“屏幕分辨率”-“放大或縮小文本和其他項”中進行了設置),原來是一個物理像素對應一個分辨率下的像素,現在則是1.5個物理像素對應一個分辨率下的像素,也就意味著分辨率下的像素變大了,實際分辨率降低了,已經變成了1440(2160/1.5)x900(1440/1.5)(此時如果你嘗試用window.screen.width/window.screen.height去檢測返回結果也會是1440x900)。這里留給讀者一個問題,這樣和直接將PC的分辨率調整為1440x900有什么區別呢?
但把素材和文字放大就真的一勞永逸了嗎?不,甚至還會帶來副作用。放大素材對位圖來說是非常危險的一件事。假設一款軟件中的素材圖片分辨率為32x32,但是為了配合整體界面的拉伸,它也必須被拉伸至原來的1.5倍等于為48x48。你一定有在Photoshop中把圖片強制放大為原來幾倍的效果的經驗。 這樣以來,圖片素材就變得模糊了。同時因為Window使用的字體為點陣字體而非矢量字體,所以甚至在軟件中的字體也會變得模糊。
簡單一點來說,采用這種技術需要將32x32的圖片強制拉伸為48x48,多出來的像素如何憑空生成?計算機只有猜測了,通過線性插值算法。所以圖片便會出現模糊。
但位圖可能會被拉伸的問題并非也是絕對的,假設軟件需要顯示的icon大小為32x32,但是圖片素材大小為64x64,那么即使Windows的UI界面拉伸1.5倍,icon大小為48x48,因為原圖片足夠大,圖片仍處于未拉伸的狀態。那么也不會模糊。
反過來我們可以得出結論,為了讓在低PPI上和高PPI上圖片顯示的效果一致,圖片素材應該盡可能的高清。
Apple的Retina技術使用的也是上面相同的方案。以15.4寸的Retina版Macbook Pro為例。顯示屏的物理像素點實際上有2880x1880,但其實默認的最優分辨率只有1440x900,剛好是物理像素的一半。也就是說操作系統默認使用了4:1的縮放。但這同樣也有可能會出現使用軟件虛化的問題。
我不清楚Mac軟件開發中是如何解決這個問題的,但可以參考iPhone開發中的解決方案,蘋果鼓勵開發者準備兩份素材,普通和高清素材。并且通過素材文件名后綴來區分,比如普通素材名稱為apple.png,那么高清素材名稱就為apple@2x.png。自然高清素材是普通素材面積的四倍,系統會優先使用高清素材,但自動縮小到普通素材的大小,這樣也就不存在圖片拉伸的問題了。
PPI之于Web
從上面我們得知,因為高像素密度設備下的UI會采用一定比例的縮放,所以CSS像素也會面臨同樣的問題:
正如上圖所示,左側普通屏幕中,2x2的CSS像素真的只需要2x2的物理像素。但是右側高清屏中,2x2的CSS像素卻需要4x4的物理像素。
我剛剛有說道解決高清PPI下圖片渲染問題的方法之一就是使用更高清的圖片素材。但問題是需要有多高清?
在Retina顯示屏上,根據上一節描述的原理,當我們需要渲染一張32x32的圖片,我們實際上需要準備64x64的素材。因為蘋果默認把所有素材都進行了兩倍的放大。但如果有一臺更高清的設備,進行了三倍或者四倍或者更高的倍數,我們豈不是需要準備更多尺寸或者體積更大的文件素材?在Web開發中我們正在面臨這樣的問題。
首先我們要學會如何表達和判斷這樣一種CSS像素和物理像素不平等。
DevicePixelRatio
DevicePixelRatio定義如下:
window.devicePixelRatio = physical pixels / dips
分母dips全稱為device-independent pixels,譯為與設備無關像素。 更通俗的說應為與物理像素無關的CSS像素。
以iPhone4為例,在垂直狀態下手機的物理像素寬度有640px,但是因為2:1縮放的關系,此時的dip,設備報告給我們的寬度只是320px。 此時的DevicePixelRatio就為 640 / 320 = 2;
devicePixelRatio說白了就是手機的物理像素與實際使用像素的縮放比。
注意devicePixelRatio并非是一個默認值。在默認情況下CSS像素是由手機默認的縮放決定的。但同時因為瀏覽器頁面也可以被人為的進行縮放。比如iPhon4中默認的分辨率寬度為320px。瀏覽網頁時我們完全可以自行放大兩倍為160px。這樣以來window.devicePixelRatio就變味了 640 / 160 = 4。
dppx
與divicePixelRatio幾乎等價的一個概念時dppx:dots per pixel。 表示單個CSS像素占用的物理像素個數。仔細想想,這與devicePixelRatio其實是一個意思, iPhone4的dppx為2,不就是與devicePixelRatio剛好相等嗎。devicePixelRatio是從宏觀上來說這件事。把整體寬度做運算。dppx是從微觀角度上說這件事,考慮的是單個像素之間的比較。
dpi
請記住,當我們在談論一臺顯示設備的像素密度時,dpi與ppi是等價的。dots per pixel中的dots就是代指物理像素。
但是如果你在mediaquery中使用dpi是就要注意了,Chrome會在控制臺中提示你使用dppx而非dpi:
Consider using ‘dppx’ units instead of ‘dpi’, as in CSS ‘dpi’ means
dots-per-CSS-inch, not dots-per-physical-inch, so does not
correspond to the actual ‘dpi’ of a screen. In media query
expression: only screen and (-webkit-min-device-pixel-ratio: 2), not
all, not all, only screen and (min-resolution: 192dpi), only screen
and (min-resolution: 2dppx)
上面這段話的意思是,在mediaquery中inch表示的CSS定義中的一英寸,而非生活中物理定義的一英寸。
實話實說我并沒有找到關于CSS中一英寸的定義,但是在W3C關于Resolution的定義中,我們可以看到看到它所定義的1dppx是與96dpi具有同樣含義的。那么2dppx也就是192dpi了咯。這當然脫離了我們傳統上的dpi了,Surface Pro 3的dpi(也就是ppi)能夠達到216ppi,但是在默認未放大界面時的dppx仍然可以是1。
CSS Reference Pixel
個人人為這是一個很雞肋的概念,但也正是因為了解的人太少了,還是需要值得一提。
假設我們規定了CSS像素值需要與設備像素大小相等,但當隨著手持設備距離人的遠近不同,設備像素密度的不同,都會導致我們看見的設備上的CSS像素的可見大小發生變化(類似于巨大的月亮因為離地球遙遠在人眼看來也不過像硬幣一樣大小)。為了保證CSS像素在不同設備和不同距離上觀測到的大小保持一致保持連貫性。W3C定義了一個CSS相對像素(CSS reference pixel)的概念
It is recommended that the reference pixel be the visual angle of one
pixel on a device with a pixel density of 96dpi and a distance from
the reader of an arm’s length. For a nominal arm’s length of 28
inches, the visual angle is therefore about 0.0213 degrees.
W3C規定,把人眼能夠辨別到的,距離自己一個手臂長度(約28英寸),像素密度為96dpi設備上的一個物理像素設為參考像素。所以我們可以算出眼睛看到參考像素的視野角度為0.0213度:
有了這一系列參照,通過三角函數關系,我們可以算出同樣一臺設備在不同距離下CSS像素理想的大小。 當遠離觀察者時像素應該增大,當靠近觀察者時像素應該減小:
這么做的優勢在于無論設備距離觀察者距離是多少,也無論設備的像素密度和物理像素大小是多少,觀察者看到的CSS像素是一致的,保證了用戶體驗的一致性:
但問題是如何來實踐這一標準呢?
<meta name="viewport">
我們有了物理像素,CSS像素——那么問題來了,當你再手機上使用瀏覽器打開網頁時,網頁應該按照哪一種寬度進行渲染?
首先我們需要了解一個概念:viewport,我常見到的中文譯為視口,但個人覺得這個翻譯有一些晦澀。 Viewport是用于限制Html元素——“限制”這兩個字不是那么好理解。quirksmode上有一篇文章談到這個概念時打了一個非常形象的比方:
假設body標簽內有一個塊狀元素寬度為10%: div {width:10%;},我們知道當我們縮放瀏覽器時這個塊狀元素的寬度也會跟著變化。 這是因為它的寬度占它父元素的10%。那么它的父元素,也就是body元素的寬度是由誰決定的呢?
我們知道一個塊狀元素默認寬度為它父元素的100%,也就是body元素的寬度與包裹它的html元素寬度相同。那么問題又變成了html元素的寬度是由誰決定的?
答案是瀏覽器窗口。現在我們可以歸納起來,html元素是被瀏覽器限制并且包裹起來的。html的寬度就是瀏覽器的寬度。
但事實上,html元素寬度是占據viewport的100%,而在桌面瀏覽器中,viewport與瀏覽器窗口大小剛好相等(注意,這僅僅是在桌面瀏覽器上)。
OK,在于是我們得到了一個結論,html寬度是由viewport決定的,但是 在桌面瀏覽器中,viewport大小與瀏覽器窗口大小相等。
但這一套規則在手機則是無法被執行的。大部分手機的屏幕分辨率目測只有400px,如果頁面上真的有某一個頁面元素僅占10%,也就是40px的話,肉眼幾乎是無法分辨的。實際情況應該會更糟糕,iPhone4的Safari默認是以980px來渲染網頁的。如果你在Chrome以桌面版的方式訪問stackoverflow,那么結果會是這樣的:

體驗非常糟糕吧,所有的鏈接幾乎都無法準確點擊。那么如何解決這個問題?
第一個辦法,放大頁面。
我們會很習慣的用手勢去放大頁面。但是要注意我們這里做的僅僅是放大頁面,改變的是頁面的縮放(scale),效果與PC上瀏覽器的類似。但是沒有改變頁面的布局,此時用于渲染頁面布局的layout仍然是980px
第二個辦法是,改變布局。
比如下面一個頁面上有一張320px寬的圖片,如果我們以默認的980px去渲染的話,它會顯得過于窄小:
但如果我們可以將渲染它的布局設為320px的話,看上去就會好很多了,同時此時我們也未對頁面進行縮放:
當然你也可以結合上一步,同時對頁面進行縮放:
不僅僅是放大,即使是在320px的像素下,我們也可以進行縮小:
回歸到技術上,以上這些都可以通過viewport標簽來解決,比如說上面的需求,把布局設定為320px,同時進行1.5倍的縮放:
<meta name="viewport" content="width=320, initial-scale=1.5">
所見即所得,需要設置的屬性在content以逗號分割開來,width表示頁面布局寬度,initial-scale代表頁面初始狀態的縮放比例,如果你不想讓用戶進行縮放,還可以添加user-scalable=no字段來保證用戶無法進行縮放。
更重要的是,我們還可以無需指定特定寬度,通過設置width=device-width,指定布局寬度等于手機分辨率寬度(但是我們不用關心手機分辨寬度是什么)來更好的利用響應式設計。注意這里的device-width表示手機的分辨率寬度,而并非手機物理像素寬度。iPhone4在垂直狀態下物理像素寬度為640,這里的device-width代表的則應該是它的dip像素320px。
給viewport標簽添加width=device-width適用于這樣一種情況:你在為移動設備開發的響應式網頁時,你會面臨多重分辨率情況,但是你又沒有必要使用到重量級的mediaquery,同時也為了避免手機瀏覽器使用桌面分辨率寬度去渲染頁面, 同時這還能兼容在手機橫握或者豎握的情況。 這樣讓你的響應式頁面能夠適用大多數的移動設備。
寫到這里我們可以做一個總結,viewport標簽的作用是什么?它能夠讓你撇開設備的干擾,告訴設備你想用什么樣的寬度渲染網頁。讓它聽命于你,而不是你聽命于他。
上面我們談到viewport有個半專業的名詞成為layout viewport,雖然它是一個非官方的詞匯,但是非常多的文章都引用了這個概念。layout viewport專用于頁面渲染的控制。還有一種viewport稱之為visual viewport,可以譯為可視窗口。兩種viewport的區分如下:
由此可以看出visual viewport就好比是瀏覽網頁的一個窗口,網頁正是這窗外的景色。當然我們還會遇見layout viewport與visual viewport大小相等的情況。比如像下面這樣:
這也就是我上面描述的width=device-width了。
番外篇:PPI和DPI使用的更多場景
在文章的開頭我有說PPI在不同上下文中的含義是不同的,如果你仍有好奇心,可以繼續往下閱讀。接下來我們談談Web以外的PPI含義。
首先我們要重申上面的結論,就談論顯示設備的像素密度而已,PPI和DPI和一樣的概念,并且其中的像素pixel和點dots代指的都是物理像素。
如果你去查看一張JPG圖片的屬性時,你會發現有橫向或者縱向的以dpi為單位的屬性或者在Phototshop新建一份文檔時,要填寫一個以ppi為單位的屬性值:
這里也存在被混用和混淆的地方。其實他們都表示打印時的分辨率值。意為在打印時每英寸上的像素(也就是跟接近PPI,但我們更常用DPI)。這里的英寸當然不再是屏幕像素了,而是紙張尺寸了。
PPI或者DPI對于圖片來說意味著什么?準確來說什么都不意味著。 一張圖片只是存在相機或者硬盤里的數據文件而已,你能告訴我它有多少英寸長或者多少英寸寬嗎?只有當它被打印出來的時候才會涉及到打印媒介的尺寸,DPI才有意義。 如果你想讓圖片更豐富,唯一的辦法是增加圖片的像素,提升你的拍攝技巧。
當然在紙張上是沒有像素的概念。但我們可以去抽象的去想象它。假設有一張300x300像素的圖片。打印分辨率的為30DPI,那么最后打印出來尺寸為10x10英寸。假如打印時的DPI值為300DPI,那么打印出來的尺寸則為1x1英寸。所以我們可以把DPI當做調節打印尺寸大小的手段。
那么DPI值越高,圖片就越小就越清晰?當然也并非如此。如果你距離60厘米去觀看一張194DPI打印出來的圖片。你會沒法區分它到底是194DPI還是300DPI。因為人眼的分辨率是有限的。這對顯示設備同樣通用的。iPhon4的像素密度有326DPI,而New iPad的像素密度只有264DPI,New iPad的顯示效果會更差嗎?參考大多數人使用的距離和方式,其實眼睛得到的效果其實是無太大差異的。這也是為什么大型顯示器或者戶外廣告DPI都不會很高,因為我們觀看他們的時候距離很遠,效果并非太差。
最后我們可以來看另一個場景的DPI:描述打印機的打印分辨率:
當一張顯示器上的圖片打印在圖片上的時候,像素這個概念其實是我們想象出來的,更加實際的概念時是印刷設備的每一個“點”:
當你嘗試去用放大鏡去查看彩色印刷物品上的圖片時,從小到大你看到的結果應該是這樣的:
為什么會這樣?簡而言之,印刷的原理是通過半色調(halftone)技術,通過控制CMYK四種顏色點印刷時的每一個印刷點的大小,角度,間隙來模擬出一種顏色的感覺:
比如當你以600DPI打印一張150PPI的圖片時,每一個像素應該包含16個點(600dots / 150pixels = 4)。
從上面我們已經知道PPI能夠決定印刷品物理尺寸的大小,打印機的DPI參數更是能進一步決定印刷體的好壞。我們用于都在追求更高的DPI和PPI。
150dpi通常已經是被認為算的上是高質量的打印分辨率了。新聞報紙使用的分辨率通常是85dpi。戶外的廣告牌通常使用的是45dpi。但是因為距離的關系你不會覺得他們的印刷質量太差。
結尾
這篇文章我把移動開發中可能會涉及到的概念都做了一些涉及。在下篇中我將運用到這些概念,并且總結在移動設備上的圖片加載方案。
轉載自淺<a >談移動Web開發(上):深入概念</a>