Viewport 是 HTML5 針對移動端開發新增的一個 meta 屬性, 它的做用是爲同一網頁在不一樣設備的呈現,提供響應式解決方案。這篇文章嘗試經過按部就班的方式,逐層探索 Viewport 的設計原理,但願能給讀者帶來更加清晰、更加全面的技術認知。javascript
在PC時代,咱們用 css 設置 1px 邊框,顯示器會用1個物理像素進行渲染。而進入移動應用時代後,咱們原來設置1px邊框,在手機上可能須要用 2 個或 3 個物理像素來渲染。css
那麼,手機爲何要這麼作?解決了什麼問題?以及咱們開發過程當中須要作什麼?html
下面,咱們將帶着這些問題來一步步探索移動端 Viewport 設計原理,以及如何利用 Viewport 進行移動端適配。java
屏幕尺寸指的是手機屏幕對角線的長度,知道了屏幕的寬度(width)和高度(height),對角線就能夠經過勾股定理算出:web
而後再把這個長度換算成 「英寸(inch)」,就是咱們平時所說的手機尺寸。瀏覽器
1英寸等於 25.4mm,即:iphone
好比 iPhone 常見的尺寸有 3.5寸、4寸、4.7寸、5.5寸 等等。ide
咱們在手機屏幕上看到的畫面,本質上都是由一個個發光的物理像素組成,物理像素是構成屏幕圖像的最小單元。佈局
咱們常說的屏幕分辨率,就是指這個屏幕上擁有多少個物理像素。字體
好比: 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 。
如今屏幕上放了一個按鈕,它的 css 樣式爲:
.button { width: 3px; height: 1px; }
從圖上的效果能夠看出,雖然咱們爲兩個按鈕設置了相同的大小,但右屏上的按鈕比左屏上的按鈕小了不少。
因此咱們會發現,相同尺寸的屏幕,像素點越多,每一個物理像素點「自身」大小就越小,從而致使渲染出來的圖像就會越小。
也就是說,設置相同大小的樣式,屏幕的 PPI 越大,渲染出來的圖像就越小。
這實際上是一個問題。
在移動時代,手機的大小和分辨率良莠不齊,從而致使PPI也不盡相同。當咱們把一個web頁面放到PPI不一樣的設備上瀏覽時,就會出現「大小各異」的效果,違背了咱們對 css 樣式 「所見即所得」 的認知。
爲了讓同一個元素在全部設備上看起來都差很少大,設備廠商給顯示屏幕增長了 「縮放因子」。
這裏的縮放因子並非對圖像自己進行縮放,而是使用更多的像素來渲染同一個元素。
以下圖所示,一樣大小的矩形,在第一個設備上用過了 8×1 個物理像素來渲染,而在第二個設備上用了 16×2 個物理像素來渲染,在第三個設備上則用了 24×3 個物理像素來渲染。
這樣以來,同一個元素在全部設備上的顯示效果都同樣啦。
從圖上能夠看出,屏幕的PPI越大,縮放因子就越大。若是以第一個屏幕爲基準,這三個屏幕的縮放因子分別爲 一、二、3。
一般咱們把 「縮放因子」 叫作 DPR,DPR 是 device pixel ratio 的縮寫,即設備像素比。
這裏須要注意的是:
dpr 的大小並非經過固定公式計算出來的,而是廠商給屏幕設置的一個固定值,出廠時就肯定了,它的大小不會隨着程序的設置而改變。
不一樣平臺定義 dpr 的基線 PPI 是不一樣的。
因爲第一代 iPhone 的 PPI 是163,因此蘋果把 163 做爲縮放基線。
所以,在 iPhone 中, PPI=163 是1x 屏,PPI=326 是 2x 屏。
PPI | 163 | 326 | 401 | 458 |
---|---|---|---|---|
dpr | 1 | 2 | 3 | 3 |
表明機型 | iPhone3GS | iPhone4 | iPhone6P | iPhoneX |
而 Android 的縮放基線 PPI 是160,因此 PPI=160 是 1x 屏,PPI=320 是 2x 屏。
從圖中能夠看出:
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 一一對應。
有了這個理論,咱們就能推導出屏幕的邏輯分辨率,也就是屏幕的 「邏輯寬度」 和 「邏輯高度」。
好比 iPhone6 的物理分辨率爲 750 × 1334,dpr = 2, 帶入公式就能夠得出其邏輯分辨率:
屏幕的邏輯分辨率也能夠經過 DOM API 來獲取:
// iPhone6 window.screen.width; // 375px window.screen.height; // 667px;
一般,咱們在 CSS 中設置的元素尺寸,本質上都是基於邏輯分辨率進行佈局的。
設備 | 邏輯分辨率(point) | 物理分辨率(pixel) | 屏幕尺寸 | dpr | PPI |
---|---|---|---|---|---|
iPhone 3GS | 320 × 480 | 320 × 480 | 3.5 | 1 | 163 |
iPhone 4 | 320 × 480 | 640 × 960 | 3.5 | 2 | 326 |
iPhone 5 | 320 × 568 | 640 × 1136 | 4.0 | 2 | 326 |
iPhone 6 | 375 × 667 | 750 × 1334 | 4.7 | 2 | 326 |
iPhone 6 Plus | 414 × 736 | 1080 × 1920 | 5.5 | 3 | 401 |
iPhone X | 375 × 812 | 1125 × 2436 | 5.8 | 3 | 458 |
iPhoneXR | 414 × 896 | 828 × 1792 | 6.1 | 2 | 326 |
iPhoneXS Max | 414 × 896 | 1242 × 2688 | 6.5 | 3 | 458 |
咱們在寫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 就是爲了獲得理想視口,方便用戶閱覽。
響應式佈局的目標是:用同一套代碼適配全部的設備。
經常使用的佈局方案有如下幾種:
下面是手淘團隊移動端適配的協做模式:
設計師通常會把 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