Viewport 是 HTML5 針對移動端開發新增的一個 meta 屬性, 它的做用是爲同一網頁在不一樣設備的呈現,提供響應式解決方案。這篇文章嘗試經過按部就班的方式,逐層探索 Viewport 的設計原理,但願能給讀者帶來更加清晰、更加全面的技術認知。css
在PC時代,咱們用 css 設置 1px 邊框,顯示器會用1個物理像素進行渲染。而進入移動應用時代後,咱們原來設置1px邊框,在手機上可能須要用 2 個或 3 個物理像素來渲染。html
那麼,手機爲何要這麼作?解決了什麼問題?以及咱們開發過程當中須要作什麼?web
下面,咱們將帶着這些問題來一步步探索移動端 Viewport 設計原理,以及如何利用 Viewport 進行移動端適配。瀏覽器
屏幕尺寸指的是手機屏幕對角線的長度,知道屏幕的寬度(width)和高度(height),經過勾股定理就能夠算出對角線的長度:ide
diagonal 就是屏幕對角線的長度,單位是毫米(mm), 而後再把這個長度換算成 「英寸(inch)」,就是咱們平時所說的手機尺寸。佈局
1 英寸等於 25.4mm,即:字體
好比 iPhone 的尺寸 3.5寸、4寸、4.7寸、5.5寸 就是這樣計算出來的。flex
咱們在手機屏幕上看到的畫面,本質上都是由一個個發光的物理像素組成,物理像素是構成屏幕圖像的最小單元。動畫
咱們常說的屏幕分辨率,就是指這個屏幕上擁有多少個物理像素。idea
好比: iPhone4 的分辨率是 640 × 960,即屏幕在水平方向上有 640 個像素,在垂直方向上有 960 個像素。
一般,設計師給的UI設計稿上的「px」指的就是物理像素。
PPI(Pixel Per Inch by diagonal):表示對角線上每英寸所擁有的像素個數。
計算PPI,能夠先利用勾股定理計算出對角線上的像素數,而後再除以屏幕尺寸,即:
把 iPhone 4 屏幕數據代入公式,便可得出 iPhone4 的 PPI :
PPI 的值越大,每英寸屏幕上的物理像素點就越多越密集,渲染出來的畫面也更加細膩、清晰。
好比,iPhone3GS 和 iPhone4 擁有相同大小的屏幕尺寸。但前者的分辨率是 320*480,能夠算出PPI爲 163,然後者的分辨率是 640*960, PPI 是326。
這就致使 iPhone4 在畫面呈現上比 iPhone3GS 更加清晰和細膩。
咱們先看看下面的兩張圖有什麼區別?
很明細,左圖要比右圖的看着舒服。
左圖字體大小適中,圖片文字都能看的清楚,相比而言,右圖字體就過小了,讓用戶閱讀變得困難。
那麼,這個問題是怎麼形成的呢?
爲了搞清楚這個問題,咱們先來作一個對比實驗,以下圖所示:
左圖和右圖分別表明兩塊尺寸相同的屏幕,長度和寬度均爲 5cm,屏幕上的每一個方格表明一個物理像素點。
惟一不一樣的是,左邊屏幕分辨率爲5 × 5,而右邊屏幕分辨率爲 10 × 10 。
如今屏幕上放了一個按鈕,寬度爲3px,高度爲1px,css 樣式以下:
.button { width: 3px; height: 1px; }
從圖上效果能夠看出,雖然咱們爲兩個按鈕設置了相同的樣式,但右屏上的按鈕比左屏上的按鈕小了不少。
因此咱們會發現,相同尺寸的屏幕,像素點越多,每一個物理像素點看起來就越小,從而致使渲染出來的圖像就會越小。
也就是說,設置相同大小的樣式,屏幕的 PPI 越大,渲染出來的圖像就越小。
這實際上是一個問題。
在移動應用時代,手機的大小和分辨率良莠不齊,從而致使 PPI 也不盡相同。
當咱們把一個web頁面放到不一樣設備上瀏覽時,就會出現「大小各異」的效果,這違背了咱們對 css 樣式 「所見即所得」 的認知。
爲了讓同一個元素在全部設備上看起來都差很少大,設備廠商給屏幕增長了 「縮放因子」。
這裏所謂的縮放因子,並非對圖像自己進行縮放,而是使用更多的物理像素來渲染同一個元素。
以下圖所示,一樣大小的矩形元素(灰色條),在第一個屏幕上採用 8×1 個物理像素來渲染,而在第二個屏幕上採用 16×2 個物理像素來渲染,在第三個設備上則採用 24×3 個物理像素來渲染。
這樣作的目的是爲了讓這個元素在不一樣設備上看起來差很少大小。
從圖上能夠看出,屏幕的 PPI 越大,須要的物理像素就越多。若是以第一個屏幕爲基準,三個屏幕對應的物理像素數,能夠用一個倍率來表示,即 1x、2x、3x。
一般,咱們把這個倍率叫作 「縮放因子」。縮放因子是移動端響應式的關鍵因素。
而在軟件開發過程當中,咱們所說的「DPR」其實指的就是縮放因子。 DPR 是 「device pixel ratio」 的縮寫,即設備像素比。
這裏須要注意的是:
DPR 的大小並非經過固定公式計算出來的,而是廠商給屏幕設置的一個固定值,出廠時就肯定了,它的大小不會隨着程序的設置而改變。
不一樣平臺定義DPR 的基線PPI是不一樣的。
因爲第一代 iPhone 的 PPI 是163,因此蘋果把 163 做爲縮放基線。
在 iPhone 中,PPI=163 是 1x 屏;PPI=326 是 2x 屏;PPI=401 是 3x 屏;PPI=458 也是 3x 屏,對應的 DPR 分別爲 一、二、三、3。
而 Android 屏幕的縮放基線 PPI 是160,因此 PPI=160 是 1x 屏,PPI=320 是 2x 屏。
能夠看出: 在 Android 上,DPR 和 PPI 基本上呈現爲一個固定關係,但將來出現的屏幕未必會遵循這個規律。
因此,有這樣一個重要結論:
DPR 和 PPI 呈正相關,但不成正比,咱們沒法經過特定的公式來計算它的值。
對於同一個元素,DPR 越大,渲染時須要的物理像素就越多。這是咱們上面得出的結論。
那麼,在軟件開發中,元素的大小到底應該寫成多少px ?
爲了解決這個問題,咱們引入 「邏輯像素」 的概念。
平時咱們在 css 中寫的 px 指的就是邏輯像素,而不是物理像素,一個邏輯像素能夠表明一個或多個物理像素。
假設,咱們如今設置一個元素的css樣式以下:
.el { width: 8px; height: 1px; }
那麼,這個元素在不一樣屏幕上渲染方式是不一樣的:
dpr=1 時,1 個邏輯像素 對應 1個物理像素。
dpr=2 時,1個邏輯像素 對應 2個物理像素,才能保證元素大小。
dpr=3 時,1個邏輯像素 對應 3個物理像素,才能保證元素大小。
所以,咱們能夠得出一個結論:
一個邏輯像素所表明的物理像素個數與該屏幕的 DPR 成正比。
即:邏輯像素 = 物理像素 / DPR
有了這個公式,咱們就能推導出屏幕的邏輯分辨率,也就是屏幕的邏輯寬度和邏輯高度。
好比 iPhone6 的物理分辨率爲 750 × 1334,DPR = 2, 帶入公式就能夠得出其邏輯分辨率:
// 邏輯寬度 width = 750 / 2 = 375px // 邏輯高度 height = 1334 / 2 = 667px
所以,iPhone6 的邏輯分辨率爲 375 × 667 。在JavaScript中,也能夠經過 DOM API 來獲取屏幕的邏輯分辨率:
// iPhone6 window.screen.width; // 375px window.screen.height;// 667px
一般,咱們在 CSS 中設置的元素尺寸,本質上都是基於邏輯分辨率進行佈局的。
咱們在寫H5頁面的時候,一般會在 html 的 head 中加入下面這句話:
這句話就是在設置頁面的 viewport 。那 viewport 究竟是什麼?爲何要設置它?
簡單來講:viewport 是屏幕背後的一張畫布。
下面,咱們將逐個理解 viewport 中的每一個概念。
瀏覽器會先把頁面內容繪製到畫布上,而後再經過屏幕窗口呈現出來。
畫布的寬度可大可小, 當畫布的寬度大於屏幕寬度時,畫布上的內容就沒法經過屏幕所有展現出來,用戶能夠經過屏幕手勢來拖動畫布查看被遮擋的部分。
若是沒有在 html 中加 viewport 的設置,畫布其實也是存在的,瀏覽器會給畫布設置一個默認寬度 ,不一樣平臺的默認值以下:
畫布的寬度能夠經過 DOM API 來獲取:
device-width 指屏幕可視窗口在水平方向上的邏輯像素。
device-width 的大小能夠經過 window.screen.width 來獲取:
width 指的是畫布的寬度,device-width 是可視窗口寬度。
width=device-width 就是把畫布的寬度設置爲可視窗口的寬度,讓畫布上的內容徹底呈現出來。
設置了 width=device-width 以後,畫布的寬度就和屏幕的寬度同樣大了。
scale 是指畫布以 device-width 大小爲基準的縮放值。
initial-scale=1.0 也就至關於設置了 width=device-width
一般須要同時設置這兩個值,這是由於二者在不一樣平臺有兼容性問題:
在iPhone 和 iPad 上,只支持 inital-scale=1 的設置,而在 IE 只支持 width=device-width ,因此二者同時設置,能夠兼容全部的平臺。
在沒有給頁面設置 viewport 的狀況下,當畫布寬度大於可視窗口的時候,瀏覽器會自動對畫布進行縮放,以適配可視窗口大小。這樣頁面在不滾動的狀況下也能呈現所有內容。
下面這個頁面是PC端頁面,沒有作移動端適配,能夠看出網頁的內容依然能夠徹底呈現出來,這是由於沒有設置 viewport 而觸發了畫布的動態縮放機制。
經過 DOM API 能計算出瀏覽器確實對畫布進行了縮放:
須要注意的是:
當沒有設置 viewport 或者 設置了viewport 但沒有設置 scale 的時候,纔會觸發瀏覽器動態縮放機制。
給頁面添加 viewport 設置,以下所示:
因爲手動設置了 scale 的值,沒有觸發自動縮放機制,瀏覽器直接把寬度爲 980px 的畫布原封不動的展現出來了:
這種狀況下須要經過滾動才能查看畫布所有內容。
一般,咱們把畫布稱爲 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,這個問題就不難解決了。
原創發佈 @一像素 2020.01.06