深入淺出 Viewport 設計原理

威武的大白菜發表於2024-04-19

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,這個問題就不難解決了。

相關文章