什麼是物理畫素、虛擬畫素、邏輯畫素、裝置畫素,什麼又是 PPI, DPI, DPR 和 DIP?有關 viewport 以及蘋果安卓裝置上的頁面呈現為什麼效果不一樣,又有哪些方法去改變和統一呢?網路上有很多資源對這些知識點進行了介紹,但是檢視之後我發現大都比較零散且閱讀順序容易讓新人疑惑,在這裡我嘗試根據幾篇文章糅合了一個循序漸進的知識點整理。在正式開始介紹之前,我們先集中看看幾個基本概念。
- 裝置畫素(device pixel, dp): 又稱為物理畫素。指裝置能控制顯示的最小物理單位,意指顯示器上一個個的點。從螢幕在工廠生產出的那天起,它上面裝置畫素點就固定不變了,單位 pt。pt 在 css 單位中屬於真正的絕對單位,1pt = 1/72(inch), inch及英寸,而1英寸等於2.54釐米。所以裝置畫素的特點就是大小固定,不可變。比如 iPhone 5 的解析度為 640 x 1136px.
- CSS畫素(css pixel, px): 又稱為虛擬畫素,也可以理解為直覺畫素。CSS 畫素是 Web 程式設計的概念,指的是 CSS 樣式程式碼中使用的邏輯畫素。在 CSS 規範中,長度單位可以分為兩類,絕對(absolute)單位以及相對(relative)單位。px 是一個相對單位,相對的是前面所說的裝置畫素(device pixel)。比如 iPhone 5 的 CSS 畫素數為 320 x 568px.
畫素概念彙總
前面說到的 px 相對單位指的是影象顯示的基本單元,它既不是一個確定的物理量,也不是一個點或者小方塊,而是一個抽象概念。 剛剛提到了影象顯示的基本單元,這個東西在不同裝置上又是不一樣的,例如顯示器上的物理畫素指的是顯示器的點距,而印表機的物理畫素則指的是印表機的墨點。
作為一個抽象概念,CSS 畫素又具有兩個方面的相對性,即:
- 在同一個裝置上,每1個 CSS 畫素所代表的裝置畫素是可以變化的(即CSS畫素的第一方面的相對性);
- 在不同的裝置之間,每1個 CSS 畫素所代表的裝置畫素是可以變化的(即CSS畫素的第二方面的相對性);
所以,CSS中的1px(CSS畫素 可變)!== 裝置的1px(裝置畫素 不可變)。
CSS 畫素的真正含義
按照 CSS 規範的定義,CSS 中的 px 是一個相對長度,它相對的,是 viewing device 的解析度。這個 viewing device,通常就是電腦顯示器。典型的電腦顯示器的解析度是96DPI,也就是1畫素為1/96英寸(實際上,假設我們的顯示器解析度都與物理解析度一致,而液晶點距其實是0.25mm到0.29mm之間,所以不太可能是正好1/96英寸,而只是接近)。
一般來說,px 就是對應裝置的物理畫素,然而如果輸出裝置的解析度與電腦顯示器大不相同,輸出效果就會有問題。例如印表機輸出到紙張上,其解析度比電腦螢幕要高許多,如果不縮放,直接使用裝置的物理畫素,那電腦上的照片由 600DPI 的印表機打出來就比用顯示器看小了約6倍。
由於不同的物理裝置的物理畫素的大小是不一樣的,所以 CSS 認為瀏覽器應該對 CSS 中的畫素進行調節,使得瀏覽器中 1個 CSS 畫素的大小在不同物理裝置上看上去大小總是差不多,目的是為了保證閱讀體驗的一致。為了達到這一點,瀏覽器可以直接按照裝置的物理畫素大小進行換算,而 CSS 規範中使用參考畫素來進行換算。
一個參考畫素即為從一臂之遙看解析度為 96DPI 的裝置輸出(即1英寸96點)時,1點(即1/96英寸)的視角。它並不是1/96英寸長度,而是從一臂之遙的距離處看解析度為 96DPI 的裝置輸出一單位(即1/96英寸)時視線與水平線的夾角。通常認為常人臂長為28英寸,所以它的視角是: (1/96)in / (28in * 2 * PI / 360deg) = 0.0213度。如下圖是一個示意圖:
對於人來說,眼睛看到的大小取決於可視角度。而可視角度取決於物體的實際大小以及物體與眼睛的距離。10米遠處一個1米見方的東西,與1米遠處的10釐米見方的東西,看上去的大小差不多是一樣的,所謂一葉障目不見泰山,講的就是這個常識。
左邊的螢幕(可以認為是電腦螢幕)的典型視覺距離是71釐米即28英寸,其1px對應了0.28mm;而右邊的螢幕(可以認為是你的42寸高清電視)的典型視覺距離是3.5米即120英寸,其1px對應1.3mm。42寸的1080p電視,解析度是1920*1080,則其物理畫素只有0.5mm左右。
綜上,px 是一個相對單位,而且在特定裝置上總是一個近似值(原則是儘量接近參考畫素)。
然而,如果你把絕對單位理解為對輸出效果的絕對掌控,事情卻大相徑庭。就網頁輸出的最主要物件——電腦螢幕來說,px 可被視為一個基準單位——與桌面解析度一致,如果是液晶屏,則幾乎總是與液晶屏物理解析度一致——也就是說網頁設計者設定的1px,就是“最終開啟這個網頁的使用者在顯示器上看到的1個點距”!反倒是那些絕對單位,其實一點也不絕對。
那麼 px 都會受哪些因素的影響而變化?
- 每英寸畫素(pixel per inch, ppi/PPI): 它表示的是每英寸所擁有的畫素(pixel)數目,更確切的說法應該是畫素密度,放到顯示器上說的是每英寸多少物理畫素及顯示器裝置的點距。數值越高,代表螢幕能夠以越高的密度顯示影象。
- 裝置畫素比(device pixel ratio, dpr/DPR): 它描述的是未縮放狀態下,裝置畫素和 CSS 畫素的初始比例關係,也可以解釋為預設縮放比例。如何理解這個概念呢?通俗來說,它是指在開發中1個 CSS 畫素佔用多少裝置畫素,如
dpr=2
代表1個 CSS 畫素用2x2個裝置畫素來繪製,所以,可以理解為 1px由多少個裝置畫素組成 - DPI: 每英寸多少點。
當用於描述顯示器時,我們可以吧 ppi 和 dpi 認為是同一個概念。那麼 ppi 和 dpr 到底是什麼關係呢? ppi 用作顯示裝置的工業標準,業界人士用 ppi 的值來衡量一個螢幕是否為高清屏,然後根據得到的密度分界來獲得此時對應的 dpr 值,也即預設縮放比例。 dpr 和 ppi 相關,一般 dpr 為 ppi/160 的整數倍,如下所示:
| 項名 | ldpi | mdpi | hdpi | xhdpi | |---| ----- | -------- | ---------- | |密度分界(密度值)|120|160|240|320| |螢幕尺寸(解析度)|240×320|320×480|480×800|640×960| |預設縮放比例|0.75|1.0|1.5|2.0|
瞭解了這兩個概念後,我們可以來說說導致 CSS 中 px 變化的因素了。
- 畫素密度:從 iPhone4 開始,蘋果推出了 Retina 屏,解析度提高了一倍(640*960),而螢幕尺寸卻沒變。這時一個css畫素=2個裝置畫素(換算公式為
1px = (dpr)^2 * 1dp
, 必須讓css中的1px代表更多的裝置畫素,才能讓1px的東西在螢幕上的大小與那些低解析度的裝置差不多,否則會因太小而看不清),即 DPR=2,示意圖如下:
- 縮放操作:縮放也會引起 css 中 px 的變化。放大頁面到200%,字型大小與元素寬度的畫素值不變,只是css的1px代表裝置畫素中的4px,寬高都是200%,DPR增加了。此時,獲取
screen.width
值不變,而window.innerWidth/Height
值(visual viewport)變成原來的一半。縮放值越大,當前 viewport 寬度會越小。
如何理解上面說到的縮放呢?放大1倍,原來 1px 的東西變成 2px,但 1px 變為 2px 並不是把原來的 320px 變為 640px,而是在實際寬度不變的情況下,1px 變得跟原來的 2px 的長度(長寬)一樣了(元素會佔據更多的裝置畫素),所以放大1倍後原來需要 320px 才能填滿的寬度現在只需要 160px,也即原來 320px 的面積裡現在只能填入 160px 的東西了。
舉個例子說明 CSS 畫素的相對性,如下示意圖:
作為Web開發者,我們接觸的更多的是用於控制元素樣式的樣式單位畫素。這裡的畫素我們稱之為CSS畫素。假設我們用PC瀏覽器開啟一個頁面,瀏覽器此時的效果如左圖所示,但如果我們把頁面放大(通過“Ctrl鍵”加上“+號鍵”),此時塊狀容器則橫向擴張,如右圖所示(黑色為實際效果,灰色為原來效果)。弔詭的是此時我們既沒有調整瀏覽器視窗大小,也沒有改變塊狀元素的css寬度,但是它看上去卻變大了一倍——這是因為我們把CSS畫素放大為了原來的兩倍。
也就是說預設情況下一個CSS畫素應該是等於一個物理畫素的寬度的,但是瀏覽器的放大操作讓一個CSS畫素等於了多個裝置畫素寬度。
- 裝置獨立畫素(Device independent Pixel, DIP): 也稱為邏輯畫素,簡稱 DIP.
畫素換算與倍率
DPR = 裝置畫素 / CSS畫素 = 螢幕橫向裝置畫素 / 理想視口的寬
CSS畫素 = 裝置獨立畫素 = 邏輯畫素
有關倍率,我們用 iPhone 3gs 和 4s 來舉例。假設有個郵件列表介面,我們不妨按照PC端網頁設計的思維來想象。3gs上大概只能顯示4-5行,4s就能顯示9-10行,而且每行會變得特別寬。但兩款手機其實是一樣大的。如果照這種方式顯示,3gs上剛剛好的效果,在4s上就會小到根本看不清字。
在現實中,這兩者效果卻是一樣的。這是因為Retina螢幕把2x2個畫素當1個畫素使用。比如原本44畫素高的頂部導航欄,在Retina屏上用了88個畫素的高度來顯示。導致介面元素都變成2倍大小,反而和3gs效果一樣了。畫質卻更清晰。
在以前,iOS應用的資源圖片中,同一張圖通常有兩個尺寸。你會看到檔名有的帶@2x字樣,有的不帶。其中不帶@2x的用在普通屏上,帶@2x的用在Retina屏上。只要圖片準備好,iOS會自己判斷用哪張,Android道理也一樣。
由此可以看出,蘋果以普通屏為基準,給Retina屏定義了一個2倍的倍率(iPhone 6plus除外,它達到了3倍)。實際畫素除以倍率,就得到邏輯畫素尺寸。只要兩個螢幕邏輯畫素相同,它們的顯示效果就是相同的。
Viewport
什麼是 viewport? 通俗的講,移動裝置上的viewport就是裝置的螢幕上能用來顯示我們的網頁的那一塊區域,在具體一點,就是瀏覽器上(也可能是一個app中的webview)用來顯示網頁的那部分割槽域,但viewport又不侷限於瀏覽器可視區域的大小,它可能比瀏覽器的可視區域要大,也可能比瀏覽器的可視區域要小。在預設情況下,一般來講,移動裝置上的viewport都是要大於瀏覽器可視區域的,這是因為考慮到移動裝置的解析度相對於桌面電腦來說都比較小,所以為了能在移動裝置上正常顯示那些傳統的為桌面瀏覽器設計的網站,移動裝置上的瀏覽器都會把自己預設的viewport設為980px或1024px(也可能是其它值,這個是由裝置自己決定的),但帶來的後果就是瀏覽器會出現橫向滾動條,因為瀏覽器可視區域的寬度是比這個預設的viewport的寬度要小的。
首先,移動裝置上的瀏覽器認為自己必須能讓所有的網站都正常顯示,即使是那些不是為移動裝置設計的網站。但如果以瀏覽器的可視區域作為viewport的話,因為移動裝置的螢幕都不是很寬,所以那些為桌面瀏覽器設計的網站放到移動裝置上顯示時,必然會因為移動裝置的viewport太窄,而擠作一團,甚至佈局什麼的都會亂掉。也許有人會問,現在不是有很多手機解析度都非常大嗎,比如768x1024,或者1080x1920這樣,那這樣的手機用來顯示為桌面瀏覽器設計的網站是沒問題的吧?前面我們已經說了,css中的1px並不是代表螢幕上的1px,你解析度越大,css中1px代表的物理畫素就會越多,DPR 的值也越大,這很好理解,因為你解析度增大了,但螢幕尺寸並沒有變大多少,必須讓css中的1px代表更多的物理畫素,才能讓1px的東西在螢幕上的大小與那些低解析度的裝置差不多,不然就會因為太小而看不清。所以在1080x1920這樣的裝置上,在預設情況下,也許你只要把一個div的寬度設為300多px(視 DPR 的值而定),就是滿屏的寬度了。為了防止某些網站因為viewport太窄而顯示錯亂,所以這些瀏覽器就決定預設情況下把viewport設為一個較寬的值,比如980px,這樣的話即使是那些為桌面設計的網站也能在移動瀏覽器上正常顯示了。ppk大神 把這個瀏覽器預設的viewport叫做 layout viewport。這個 layout viewport 的寬度可以通過 document.documentElement.clientWidth 來獲取。
然而,layout viewport 的寬度是大於瀏覽器可視區域的寬度的,所以我們還需要一個viewport來代表 瀏覽器可視區域的大小,我們叫他 visual viewport。visual viewport 的寬度可以通過 window.innerWidth 來獲取,但在 Android 2, Oprea mini 和 UC 8 中無法正確獲取。下圖為兩個 viewport 的示意圖:
現在我們已經有兩個viewport了 - layout viewport 和 visual viewport。但瀏覽器覺得還不夠,因為現在越來越多的網站都會為移動裝置進行單獨的設計,所以必須還要有一個能完美適配移動裝置的 viewport。所謂的完美適配指的是,首先不需要使用者縮放和橫向滾動條就能正常的檢視網站的所有內容;第二,顯示的文字的大小是合適,比如一段14px大小的文字,不會因為在一個高密度畫素的螢幕裡顯示得太小而無法看清,理想的情況是這段14px的文字無論是在何種密度螢幕,何種解析度下,顯示出來的大小都是差不多的。當然,不只是文字,其他元素像圖片什麼的也是這個道理,這就是第三個 viewport ——移動裝置的理想 viewport (ideal viewport)。
ideal viewport 並沒有一個固定的尺寸,不同的裝置擁有有不同的 ideal viewport。所有 iPhone 的 ideal viewport 寬度都是320px,無論它的螢幕寬度是320還是640,也就是說,在 iPhone 中,css 中的 320px 就代表 iPhone 螢幕的寬度。
但是安卓裝置就比較複雜了,有320px的,有360px的,有384px的等等,關於不同的裝置ideal viewport的寬度都為多少,可以到 http://viewportsizes.com 去檢視一下,裡面收集了眾多裝置的理想寬度。
總結一下,ppk把移動裝置上的viewport分為layout viewport, visual viewport 和 ideal viewport 三類:
- ideal viewport是最適合移動裝置的viewport,ideal viewport的寬度等於移動裝置的螢幕寬度,只要在css中把某一元素的寬度設為ideal viewport的寬度(單位用px),那麼這個元素的寬度就是裝置螢幕的寬度了,也就是寬度為100%的效果。ideal viewport 的意義在於,無論在何種解析度的螢幕下,那些針對ideal viewport 而設計的網站,不需要使用者手動縮放,也不需要出現橫向滾動條,都可以完美的呈現給使用者;
- layout viewport 表示的是瀏覽器預設的viewport,一般情況下這個寬度要大於瀏覽器可視區域寬度;
- visual viewport 表示瀏覽器可視區域的大小。
利用 meta 標籤對 viewport 進行控制
viewport是專為手機瀏覽器設計的一個meta標籤,一個簡單的示例如下所示:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
其中,width=device-width
表示此寬度不依賴於原始象素(px),而依賴於螢幕的寬度。移動裝置預設的viewport是layout viewport,也就是那個比螢幕要寬的viewport,但在進行移動裝置網站的開發時,我們需要的是ideal viewport。我們在開發移動裝置的網站時,最常見的的一個動作就是把上面這個東西複製到我們的head標籤中,它的作用是讓當前的 viewport 寬度等於裝置寬度,同事不允許使用者手動縮放。也許允不允許使用者縮放不同的網站有不同的要求,但讓viewport的寬度等於裝置的寬度,這個應該是大家都想要的效果,如果你不這樣的設定的話,那就會使用那個比螢幕寬的預設viewport,也就是說會出現橫向滾動條。
這個name為viewport的meta標籤到底有哪些東西呢,又都有什麼作用呢?meta viewport 標籤首先是由蘋果公司在其safari瀏覽器中引入的,目的就是解決移動裝置的viewport問題。後來安卓以及各大瀏覽器廠商也都紛紛效仿,引入對meta viewport的支援,事實也證明這個東西還是非常有用的。在蘋果的規範中,meta viewport 有6個可以設定的內容:
內容 | 含義 |
---|---|
width | 設定layout viewport 的寬度,為一個正整數,或字串"device-width" |
initial-scale | 設定頁面的初始縮放值,為一個數字,可以帶小數 |
minimum-scale | 允許使用者的最小縮放值,為一個數字,可以帶小數 |
maximum-scale | 允許使用者的最大縮放值,為一個數字,可以帶小數 |
height | 設定layout viewport 的高度,這個屬性對我們並不重要,很少使用 |
user-scalable | 是否允許使用者進行縮放,值為"no"或"yes", no 代表不允許,yes代表允許 |
這些屬性可以同時使用,也可以單獨使用或混合使用,多個屬性同時使用時用逗號隔開就行了。此外,在安卓中還支援 target-densitydpi
這個私有屬性,它表示目標裝置的密度等級,作用是決定css中的1px代表多少物理畫素,但作為將要廢棄的屬性,所以使用中需要避免該用法。我們接下來看看具體的幾個用法:
- width=device-width: 所有瀏覽器都能把當前的viewport寬度變成ideal viewport的寬度,但要注意的是,在iPhone和iPad上,無論是豎屏還是橫屏,寬度都是豎屏時ideal viewport的寬度。
- initial-scale=1: 這句程式碼也能達到和前一句程式碼一樣的效果,也可以把當前的的viewport變為 ideal viewport。
要想清楚這件事情,首先你得弄明白這個縮放是相對於什麼來縮放的,因為這裡的縮放值是1,也就是沒縮放,但卻達到了 ideal viewport 的效果,所以,那答案就只有一個了,縮放是相對於 ideal viewport來進行縮放的。
但如果width 和 initial-scale=1同時出現,並且還出現了衝突呢?
<meta name="viewport" content="width=400, initial-scale=1">
width=400表示把當前viewport的寬度設為400px,initial-scale=1則表示把當前viewport的寬度設為ideal viewport的寬度,那麼瀏覽器到底該服從哪個命令呢?是書寫順序在後面的那個嗎?不是。當遇到這種情況時,瀏覽器會取它們兩個中較大的那個值。例如,當width=400,ideal viewport的寬度為320時,取的是400;當width=400, ideal viewport的寬度為480時,取的是ideal viewport的寬度。(ps:在uc9瀏覽器中,當initial-scale=1時,無論width屬性的值為多少,此時viewport的寬度永遠都是ideal viewport的寬度)
最後,總結一下,要把當前的viewport寬度設為ideal viewport的寬度,既可以設定 width=device-width,也可以設定 initial-scale=1,但這兩者各有一個小缺陷,就是iphone、ipad以及IE 會橫豎屏不分,通通以豎屏的ideal viewport寬度為準。所以,最完美的寫法應該是,兩者都寫上去,這樣就 initial-scale=1 解決了 iphone、ipad的毛病,width=device-width則解決了IE的毛病。關於縮放,我們可以得出以下一個式子:
當前縮放值 = ideal viewport寬度 / visual viewport寬度
大多數瀏覽器都符合這個理論,但是安卓上的原生瀏覽器以及IE有些問題。安卓自帶的webkit瀏覽器只有在 initial-scale = 1 以及沒有設定width屬性時才是表現正常的,也就相當於這理論在它身上基本沒用;而IE則根本不甩initial-scale這個屬性,無論你給他設定什麼,initial-scale表現出來的效果永遠是1。
好了,現在再來說下initial-scale的預設值問題,就是不寫這個屬性的時候,它的預設值會是多少呢?很顯然不會是1,因為當 initial-scale = 1 時,當前的layout viewport寬度會被設為 ideal viewport的寬度,但前面說了,各瀏覽器預設的 layout viewport寬度一般都是980啊,1024啊,800啊等等這些個值,沒有一開始就是 ideal viewport的寬度的,所以 initial-scale的預設值肯定不是1。安卓裝置上的initial-scale預設值好像沒有方法能夠得到,或者就是乾脆它就沒有預設值,一定要你顯示的寫出來這個東西才會起作用,我們不管它了,這裡我們重點說一下iphone和ipad上的initial-scale預設值。
根據測試,我們可以在iphone和ipad上得到一個結論,就是無論你給layout viewpor設定的寬度是多少,而又沒有指定初始的縮放值的話,那麼iphone和ipad會自動計算initial-scale這個值,以保證當前layout viewport的寬度在縮放後就是瀏覽器可視區域的寬度,也就是說不會出現橫向滾動條。比如說,在iphone上,我們不設定任何的viewport meta標籤,此時layout viewport的寬度為980px,但我們可以看到瀏覽器並沒有出現橫向滾動條,瀏覽器預設的把頁面縮小了,此時值應為 0.33 左右。
所以總結一下:在iphone和ipad上,無論你給viewport設的寬的是多少,如果沒有指定預設的縮放值,則iphone和ipad會自動計算這個縮放值,以達到當前頁面不會出現橫向滾動條(或者說viewport的寬度就是螢幕的寬度)的目的。
JavaScript 動態更改 meta viewport 標籤
- 方法一:可以使用document.write來動態輸出meta viewport標籤
document.write('<meta name="viewport" content="width=device-width,initial-scale=1">');
- 方法二:通過
setAttribute
方法改變
<meta id="testViewport" name="viewport" content="width = 380">
<script>
let mvp = document.getElementById('testViewport');
mvp.setAttribute('content','width=480');
</script>
但是要注意,在安卓2.3(或許是所有2.x版本中)的自帶瀏覽器中,對meta viewport標籤進行覆蓋或更改,會出現讓人非常迷糊的結果。
總結一下,每個移動裝置瀏覽器中都有一個理想的寬度,這個理想的寬度是指css中的寬度,跟裝置的物理寬度沒有關係,在css中,這個寬度就相當於100%的所代表的那個寬度。我們可以用meta標籤把viewport的寬度設為那個理想的寬度,如果不知道這個裝置的理想寬度是多少,那麼用device-width這個特殊值就行了,同時initial-scale=1也有把viewport的寬度設為理想寬度的作用。
為什麼需要有理想的viewport呢?比如一個解析度為320x480的手機理想viewport的寬度是320px,而另一個螢幕尺寸相同但解析度為640x960的手機的理想viewport寬度也是為320px,那為什麼解析度大的這個手機的理想寬度要跟解析度小的那個手機的理想寬度一樣呢?這是因為,只有這樣才能保證同樣的網站在不同解析度的裝置上看起來都是一樣或差不多的。