Viewport 是 HTML5 針對移動端開發新增的一個 meta 屬性, 它的作用是為同一網頁在不同裝置的呈現,提供響應式解決方案。這篇文章嘗試透過循序漸進的方式,逐層探索 Viewport 的設計原理,希望能給讀者帶來更加清晰、更加全面的技術認知。
一、引言
在PC時代,我們用 css 設定 1px 邊框,顯示器會用1個物理畫素進行渲染。而進入移動應用時代後,我們原來設定1px邊框,在手機上可能需要用 2 個或 3 個物理畫素來渲染。
那麼,手機為什麼要這麼做?解決了什麼問題?以及我們開發過程中需要做什麼?
下面,我們將帶著這些問題來一步步探索移動端 Viewport 設計原理,以及如何利用 Viewport 進行移動端適配。
二、基礎概念
1、螢幕尺寸
螢幕尺寸指的是手機螢幕對角線的長度,知道螢幕的寬度(width)和高度(height),透過勾股定理就可以算出對角線的長度:
diagonal 就是螢幕對角線的長度,單位是毫米(mm), 然後再把這個長度換算成 “英寸(inch)”,就是我們平時所說的手機尺寸。
1 英寸等於 25.4mm,即:
比如 iPhone 的尺寸 3.5寸、4寸、4.7寸、5.5寸 就是這樣計算出來的。
2、物理畫素
我們在手機螢幕上看到的畫面,本質上都是由一個個發光的物理畫素組成,物理畫素是構成螢幕影像的最小單元。
我們常說的螢幕解析度,就是指這個螢幕上擁有多少個物理畫素。
比如: iPhone4 的解析度是 640 × 960,即螢幕在水平方向上有 640 個畫素,在垂直方向上有 960 個畫素。
通常,設計師給的UI設計稿上的“px”指的就是物理畫素。
3、畫素密度 - PPI
PPI(Pixel Per Inch by diagonal):表示對角線上每英寸所擁有的畫素個數。
計算PPI,可以先利用勾股定理計算出對角線上的畫素數,然後再除以螢幕尺寸,即:
把 iPhone 4 螢幕資料代入公式,即可得出 iPhone4 的 PPI :
PPI 的值越大,每英寸螢幕上的物理畫素點就越多越密集,渲染出來的畫面也更加細膩、清晰。
比如,iPhone3GS 和 iPhone4 擁有相同大小的螢幕尺寸。但前者的解析度是 320*480,可以算出PPI為 163,而後者的解析度是 640*960, PPI 是326。
這就導致 iPhone4 在畫面呈現上比 iPhone3GS 更加清晰和細膩。
4、PPI 導致的問題
我們先看看下面的兩張圖有什麼區別?
很明細,左圖要比右圖的看著舒服。
左圖字型大小適中,圖片文字都能看的清楚,相比而言,右圖字型就太小了,讓使用者閱讀變得困難。
那麼,這個問題是怎麼造成的呢?
為了搞清楚這個問題,我們先來做一個對比實驗,如下圖所示:
左圖和右圖分別代表兩塊尺寸相同的螢幕,長度和寬度均為 5cm,螢幕上的每個方格代表一個物理畫素點。
唯一不同的是,左邊螢幕解析度為5 × 5,而右邊螢幕解析度為 10 × 10 。
現在螢幕上放了一個按鈕,寬度為3px,高度為1px,css 樣式如下:
1
2
3
4
|
.button { width: 3px; height: 1px; } |
從圖上效果可以看出,雖然我們為兩個按鈕設定了相同的樣式,但右屏上的按鈕比左屏上的按鈕小了很多。
所以我們會發現,相同尺寸的螢幕,畫素點越多,每個物理畫素點看起來就越小,從而導致渲染出來的影像就會越小。
也就是說,設定相同大小的樣式,螢幕的 PPI 越大,渲染出來的影像就越小。
這其實是一個問題。
在移動應用時代,手機的大小和解析度參差不齊,從而導致 PPI 也不盡相同。
當我們把一個web頁面放到不同裝置上瀏覽時,就會出現“大小各異”的效果,這違背了我們對 css 樣式 “所見即所得” 的認知。
為了讓同一個元素在所有裝置上看起來都差不多大,裝置廠商給螢幕增加了 “縮放因子”。
5、縮放因子 - DPR
這裡所謂的縮放因子,並不是對影像本身進行縮放,而是使用更多的物理畫素來渲染同一個元素。
如下圖所示,同樣大小的矩形元素(灰色條),在第一個螢幕上採用 8×1 個物理畫素來渲染,而在第二個螢幕上採用 16×2 個物理畫素來渲染,在第三個裝置上則採用 24×3 個物理畫素來渲染。
這樣做的目的是為了讓這個元素在不同裝置上看起來差不多大小。
從圖上可以看出,螢幕的 PPI 越大,需要的物理畫素就越多。如果以第一個螢幕為基準,三個螢幕對應的物理畫素數,可以用一個倍率來表示,即 1x、2x、3x。
通常,我們把這個倍率叫做 “縮放因子”。縮放因子是移動端響應式的關鍵因素。
而在軟體開發過程中,我們所說的“DPR”其實指的就是縮放因子。 DPR 是 “device pixel ratio” 的縮寫,即裝置畫素比。
這裡需要注意的是:
DPR 的大小並不是透過固定公式計算出來的,而是廠商給螢幕設定的一個固定值,出廠時就確定了,它的大小不會隨著程式的設定而改變。
6、DPR 和 PPI 的對應關係
不同平臺定義DPR 的基線PPI是不同的。
由於第一代 iPhone 的 PPI 是163,所以蘋果把 163 作為縮放基線。
在 iPhone 中,PPI=163 是 1x 屏;PPI=326 是 2x 屏;PPI=401 是 3x 屏;PPI=458 也是 3x 屏,對應的 DPR 分別為 1、2、3、3。
而 Android 螢幕的縮放基線 PPI 是160,所以 PPI=160 是 1x 屏,PPI=320 是 2x 屏。
可以看出: 在 Android 上,DPR 和 PPI 基本上呈現為一個固定關係,但未來出現的螢幕未必會遵循這個規律。
所以,有這樣一個重要結論:
DPR 和 PPI 呈正相關,但不成正比,我們無法透過特定的公式來計算它的值。
7、邏輯畫素、邏輯解析度
對於同一個元素,DPR 越大,渲染時需要的物理畫素就越多。這是我們上面得出的結論。
那麼,在軟體開發中,元素的大小到底應該寫成多少px ?
為了解決這個問題,我們引入 “邏輯畫素” 的概念。
平時我們在 css 中寫的 px 指的就是邏輯畫素,而不是物理畫素,一個邏輯畫素可以代表一個或多個物理畫素。
假設,我們現在設定一個元素的css樣式如下:
1
2
3
4
|
.el { width: 8px; height: 1px; } |
那麼,這個元素在不同螢幕上渲染方式是不同的:
dpr=1 時,1 個邏輯畫素 對應 1個物理畫素。
dpr=2 時,1個邏輯畫素 對應 2個物理畫素,才能保證元素大小。
dpr=3 時,1個邏輯畫素 對應 3個物理畫素,才能保證元素大小。
因此,我們可以得出一個結論:
一個邏輯畫素所代表的物理畫素個數與該螢幕的 DPR 成正比。
即:邏輯畫素 = 物理畫素 / DPR
有了這個公式,我們就能推匯出螢幕的邏輯解析度,也就是螢幕的邏輯寬度和邏輯高度。
- 邏輯寬度 = 水平物理畫素 / DPR
- 邏輯高度 = 垂直物理畫素 / DPR
比如 iPhone6 的物理解析度為 750 × 1334,DPR = 2, 帶入公式就可以得出其邏輯解析度:
1
2
3
4
|
// 邏輯寬度 width = 750 / 2 = 375px // 邏輯高度 height = 1334 / 2 = 667px |
因此,iPhone6 的邏輯解析度為 375 × 667 。在JavaScript中,也可以透過 DOM API 來獲取螢幕的邏輯解析度:
1
2
3
|
// iPhone6 window.screen.width; // 375px window.screen.height; // 667px |
通常,我們在 CSS 中設定的元素尺寸,本質上都是基於邏輯解析度進行佈局的。
8、iPhone 常見的幾種規格
三、Viewport
1、Viewport 到底是什麼?
我們在寫H5頁面的時候,通常會在 html 的 head 中加入下面這句話:
這句話就是在設定頁面的 viewport 。那 viewport 到底是什麼?為什麼要設定它?
簡單來說:viewport 是螢幕背後的一張畫布。
下面,我們將逐個理解 viewport 中的每個概念。
2、Viewport 畫布
瀏覽器會先把頁面內容繪製到畫布上,然後再透過螢幕視窗呈現出來。
畫布的寬度可大可小, 當畫布的寬度大於螢幕寬度時,畫布上的內容就無法透過螢幕全部展示出來,使用者可以透過螢幕手勢來拖動畫布檢視被遮擋的部分。
如果沒有在 html 中加 viewport 的設定,畫布其實也是存在的,瀏覽器會給畫布設定一個預設寬度 ,不同平臺的預設值如下:
畫布的寬度可以透過 DOM API 來獲取:
3、device-width 指的是什麼?
device-width 指螢幕可視視窗在水平方向上的邏輯畫素。
device-width 的大小可以透過 window.screen.width 來獲取:
4、width=device-width 在設定誰的寬度?
width 指的是畫布的寬度,device-width 是可視視窗寬度。
width=device-width 就是把畫布的寬度設定為可視視窗的寬度,讓畫布上的內容完全呈現出來。
設定了 width=device-width 之後,畫布的寬度就和螢幕的寬度一樣大了。
5、畫布縮放 - scale
scale 是指畫布以 device-width 大小為基準的縮放值。
initial-scale=1.0 也就相當於設定了 width=device-width
通常需要同時設定這兩個值,這是因為兩者在不同平臺有相容性問題:
在iPhone 和 iPad 上,只支援 inital-scale=1 的設定,而在 IE 只支援 width=device-width ,所以兩者同時設定,可以相容所有的平臺。
6、動態縮放機制
在沒有給頁面設定 viewport 的情況下,當畫布寬度大於可視視窗的時候,瀏覽器會自動對畫布進行縮放,以適配可視視窗大小。這樣頁面在不滾動的情況下也能呈現全部內容。
下面這個頁面是PC端頁面,沒有做移動端適配,可以看出網頁的內容依然可以完全呈現出來,這是因為沒有設定 viewport 而觸發了畫布的動態縮放機制。
透過 DOM API 能計算出瀏覽器確實對畫布進行了縮放:
需要注意的是:
當沒有設定 viewport 或者 設定了viewport 但沒有設定 scale 的時候,才會觸發瀏覽器動態縮放機制。
7、禁止動態縮放
給頁面新增 viewport 設定,如下所示:
由於手動設定了 scale 的值,沒有觸發自動縮放機制,瀏覽器直接把寬度為 980px 的畫布原封不動的展示出來了:
這種情況下需要透過滾動才能檢視畫布全部內容。
8、三個 Viewport
通常,我們把畫布稱為 layout viewport, 把螢幕可視視窗稱為 visual viewport。
而把設定 width=device-width 的畫布稱為 ideal viewport,即“理想視口”。
我們通常在 html 中設定 viewport 就是為了得到理想視口,方便使用者閱覽。
四、響應式佈局方案
響應式佈局的目標是:用同一套程式碼適配所有的裝置。
常用的佈局方案有以下幾種:
-
百分比
-
vw
-
Css Media Query
-
rem
-
flex box
下面是手淘團隊移動端適配的協作模式:
設計師一般會把 iPhone6(750px) 作為設計稿,設計稿中的元素也都是基於750px進行標註的,當然這裡的 px 指的是物理畫素。
開發拿到設計稿後,根據iPhone6的 dpr 把標註中的元素大小換算成 css 中的大小,比如設計稿中按鈕的寬度標註為40px, 則 css 中應該寫成40/2=20px
然後再根據螢幕的邏輯寬度進行同步縮放(如:rem/vw 方案),就可以實現向上或向下適配所有裝置。
五、總結
最後,我們再回顧一下開篇提到的問題,其實不難理解,這是由於螢幕的 dpr 不同導致的。
一般情況下,PC 螢幕 dpr 是 1,即 1個邏輯畫素 = 1個物理畫素,而移動端的 dpr 通常都是 2 或 3,因此也就需要 2個或 3個物理畫素來渲染。
這也是 “移動端1px邊框” 的經典問題,理解了 viewport,這個問題就不難解決了。