移動端適配,是我們在開發中經常會遇到的,這里面可能會遇到非常多的問題:
- 1px問題
- UI圖完美適配方案
- iPhoneX適配方案
- 橫屏適配
- 高清屏圖片模糊問題
- ...
上面這些問題可能我們在開發中已經知道如何解決,但是問題產生的原理,以及解決方案的原理可能會模糊不清。在解決這些問題的過程中,我們往往會遇到非常多的概念:像素、分辨率、PPI、DPI、DP、DIP、DPR、視口等等。
本文將從移動端適配的基礎概念出發,探究移動端適配各種問題的解決方案和實現原理。
一、英寸
一般用英寸描述屏幕的物理大小,如電腦顯示器的17、22,手機顯示器的4.8、5.7等使用的單位都是英寸。

英寸和厘米的換算:1英寸 = 2.54 厘米
二、物理像素
2.1 像素
像素即一個小方塊,它具有特定的位置和顏色。
圖片、電子屏幕(手機、電腦)就是由無數個具有特定顏色和特定位置的小方塊拼接而成。
像素可以作為圖片或電子屏幕的最小組成單位。
2.2 物理像素
到電商網站購買手機,都會看一看手機的參數,以apple的官網上對手機分辨率的描述為例:

iPhone XS Max 和 iPhone SE的分辨率分別為2688 x 1242和1136 x 640。表示手機分別在垂直和水平上所具有的像素點數。這里描述的就是屏幕實際的物理像素,即一個屏幕具體由多少個像素點組成。
屏幕從工廠出來那天起,它上面的物理像素點就固定不變了,單位為pt。當然分辨率高不代表屏幕就清晰,屏幕的清晰程度還與尺寸有關。
**設備像素 = 物理像素 **
2.3 PPI(Pixel Per Inch) - 每英寸包括的像素數
PPI可以用於描述屏幕的清晰度以及一張圖片的質量。
使用PPI描述圖片時,PPI越高,圖片質量越高,使用PPI描述屏幕時,PPI越高,屏幕越清晰。
由於手機尺寸為手機對角線的長度,通常使用如下的方法計算PPI:

iPhone 6的PPI為 
,那它每英寸約含有326個物理像素點。
2.4 DPI(Dot Per Inch) - 每英寸包括的點數
這里的點是一個抽象的單位,它可以是屏幕像素點、圖片像素點也可以是打印機的墨點。
平時你可能會看到使用DPI來描述圖片和屏幕,這時的DPI應該和PPI是等價的,DPI最常用的是用於描述打印機,表示打印機每英寸可以打印的點數。
一張圖片在屏幕上顯示時,它的像素點數是規則排列的,每個像素點都有特定的位置和顏色。
當使用打印機進行打印時,打印機可能不會規則的將這些點打印出來,而是使用一個個打印點來呈現這張圖像,這些打印點之間會有一定的空隙,這就是DPI所描述的:打印點的密度。

在上面的圖像中我們可以清晰的看到,打印機是如何使用墨點來打印一張圖像。
所以,打印機的DPI越高,打印圖像的精細程度就越高,同時這也會消耗更多的墨點和時間。
三、設備獨立像素
實際上,上面我們描述的像素都是物理像素,即設備上真實的物理單元。
設備獨立像素是如何產生的??
智能手機發展非常之快,在幾年之前,我們還用着分辨率非常低的手機,比如下面左側的白色手機,它的分辨率是320x480,我們可以在上面瀏覽正常的文字、圖片等等。
但是,隨着科技的發展,低分辨率的手機已經不能滿足我們的需求了。很快,更高分辨率的屏幕誕生了,比如下面的黑色手機,它的分辨率是640x940,正好是白色手機的兩倍。
理論上來講,在白色手機上相同大小的圖片和文字,在黑色手機上會被縮放一倍,因為它的分辨率提高了一倍。這樣,豈不是后面出現更高分辨率的手機,頁面元素會變得越來越小嗎?

然而,事實並不是這樣的,我們現在使用的智能手機,不管分辨率多高,他們所展示的界面比例都是基本類似的。喬布斯在iPhone4的發布會上首次提出了Retina Display(視網膜屏幕)的概念,它正是解決了上面的問題,這也使它成為一款跨時代的手機。
在iPhone4使用的視網膜屏幕中,把2x2個像素當1個像素使用,這樣讓屏幕看起來更精致,但是元素的大小卻不會改變。

如果黑色手機使用了視網膜屏幕的技術,那么顯示結果應該是下面的情況,比如列表的寬度為300個像素,那么在一條水平線上,白色手機會用300個物理像素去渲染它,而黑色手機實際上會用600個物理像素去渲染它。
我們必須用一種單位來同時告訴不同分辨率的手機,它們在界面上顯示元素的大小是多少,這個單位就是設備獨立像素(Device Independent Pixels)簡稱DIP或DP。上面我們說,列表的寬度為300個像素,實際上我們可以說:列表的寬度為300個設備獨立像素。

打開chrome的開發者工具,我們可以模擬各個手機型號的顯示情況,每種型號上面會顯示一個尺寸,比如iPhone X顯示的尺寸是375x812,實際iPhone X的分辨率會比這高很多,這里顯示的就是設備獨立像素。

3.1 設備像素比
設備像素比device pixel ratio簡稱DPR,即未縮放狀態,下物理像素和設備獨立像素的比值。(DPR = 物理像素 / 設備獨立像素)
在web中,瀏覽器為我們提供了window.devicePixelRatio來獲取DPR。
在css中,可以使用媒體查詢min-device-pixel-ratio,區分dpr:
@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2){ }
在React Native中,我們也可以使用PixelRatio.get()來獲取DPR。
當然,上面的規則也有例外,iPhone 6、7、8 Plus的實際物理像素是1080 x 1920,在開發者工具中我們可以看到:它的設備獨立像素是414 x 736,設備像素比為3,設備獨立像素和設備像素比的乘積並不等於1080 x 1920,而是等於1242 x 2208。
實際上,手機會自動把1242 x 2208個像素點塞進1080 * 1920個物理像素點來渲染,我們不用關心這個過程,而1242 x 2208被稱為屏幕的設計像素。我們開發過程中也是以這個設計像素為准。
實際上,從蘋果提出視網膜屏幕開始,才出現設備像素比這個概念,因為在這之前,移動設備都是直接使用物理像素來進行展示。
緊接着,Android同樣使用了其他的技術方案來實現DPR大於1的屏幕,不過原理是類似的。由於Android屏幕尺寸非常多、分辨率高低跨度非常大,不像蘋果只有它自己的幾款固定設備、尺寸。所以,為了保證各種設備的顯示效果,Android按照設備的像素密度將設備分成了幾個區間:

當然,所有的Android設備不一定嚴格按照上面的分辨率,每個類型可能對應幾種不同分辨率,所以,每個Android手機都能根據給定的區間范圍,確定自己的DPR,從而擁有類似的顯示。當然,僅僅是類似,由於各個設備的尺寸、分辨率上的差異,設備獨立像素也不會完全相等,所以各種Android設備仍然不能做到在展示上完全相等。
3.2 移動端開發
在iOS、Android和React Native開發中樣式單位其實都使用的是設備獨立像素。
iOS的尺寸單位為pt,Android的尺寸單位為dp,React Native中沒有指定明確的單位,它們其實都是設備獨立像素dp。
在使用React Native開發App時,UI給我們的原型圖一般是基於iphone6的像素給定的。
為了適配所有機型,在寫樣式時需要把物理像素轉換為設備獨立像素:例如:如果給定一個元素的高度為200px(這里的px指物理像素,非CSS像素),iphone6的設備像素比為2,我們給定的height應為200px/2=100dp。
當然,最好的是,你可以和設計溝通好,所有的UI圖都按照設備獨立像素來出。
我們還可以在代碼(React Native)中進行px和dp的轉換:
import {PixelRatio } from 'react-native';
const dpr = PixelRatio.get();
/**
* px轉換為dp
*/
export function pxConvertTodp(px) {
return px / dpr;
}
/**
* dp轉換為px
*/
export function dpConvertTopx(dp) {
return PixelRatio.getPixelSizeForLayoutSize(dp);
}
3.3 WEB端開發
在寫CSS時,用到最多的單位是px,即CSS像素,當頁面縮放比例為100%時,一個CSS像素等於一個設備獨立像素。
但是CSS像素是很容易被改變的,當用戶對瀏覽器進行了放大,CSS像素會被放大,這時一個CSS像素會跨越更多的物理像素。
頁面的縮放系數 = CSS像素 / 設備獨立像素
3.4 關於屏幕
這里多說兩句Retina屏幕,因為我在很多文章中看到對Retina屏幕的誤解。
Retina屏幕只是蘋果提出的一個營銷術語:
在普通的使用距離下,人的肉眼無法分辨單個的像素點。
為什么強調普通的使用距離下呢?我們來看一下它的計算公式:

a代表人眼視角,h代表像素間距,d代表肉眼與屏幕的距離,符合以上條件的屏幕可以使肉眼看不見單個物理像素點。
它不能單純的表達分辨率和PPI,只能一種表達視覺效果。
讓多個物理像素渲染一個獨立像素只是Retina屏幕為了達到效果而使用的一種技術。而不是所有DPR > 1的屏幕就是Retina屏幕。
比如:給你一塊超大尺寸的屏幕,即使它的PPI很高,DPR也很高,在近距離你也能看清它的像素點,這就不算Retina屏幕。

我們經常見到用K和P這個單位來形容屏幕:
P代表的就是屏幕縱向的像素個數,1080P即縱向有1080個像素,分辨率為1920X1080的屏幕就屬於1080P屏幕。
我們平時所說的高清屏其實就是屏幕的物理分辨率達到或超過1920X1080的屏幕。
K代表屏幕橫向有幾個1024個像素,一般來講橫向像素超過2048就屬於2K屏,橫向像素超過4096就屬於4K屏。
四、視口
視口(viewport)代表當前可見的計算機圖形區域。在Web瀏覽器術語中,通常與瀏覽器窗口相同,但不包括瀏覽器的UI, 菜單欄等——即指你正在瀏覽的文檔的那一部分。
一般我們所說的視口共包括三種:布局視口、視覺視口和理想視口,它們在屏幕適配中起着非常重要的作用。
4.1 布局視口

布局視口(layout viewport):當我們以百分比來指定一個元素的大小時,它的計算值是由這個元素的包含塊計算而來的。當這個元素是最頂級的元素時,它就是基於布局視口來計算的。
所以,布局視口是網頁布局的基准窗口,在PC瀏覽器上,布局視口就等於當前瀏覽器的窗口大小(不包括borders 、margins、滾動條)。
在移動端,布局視口被賦予一個默認值,大部分為980px,這保證PC的網頁可以在手機瀏覽器上呈現,但是非常小,用戶可以手動對網頁進行放大。
我們可以通過調用document.documentElement.clientWidth / clientHeight來獲取布局視口大小。
4.2 視覺視口

視覺視口(visual viewport):用戶通過屏幕真實看到的區域。
視覺視口默認等於當前瀏覽器的窗口大小(包括滾動條寬度)。
當用戶對瀏覽器進行縮放時,不會改變布局視口的大小,所以頁面布局是不變的,但是縮放會改變視覺視口的大小。
例如:用戶將瀏覽器窗口放大了200%,這時瀏覽器窗口中的CSS像素會隨着視覺視口的放大而放大,這時一個CSS像素會跨越更多的物理像素。
所以,布局視口會限制你的CSS布局而視覺視口決定用戶具體能看到什么。
我們可以通過調用window.innerWidth / innerHeight來獲取視覺視口大小。
4.3 理想視口

布局視口在移動端展示的效果並不是一個理想的效果,所以理想視口(ideal viewport)就誕生了:網站頁面在移動端展示的理想大小。
如上圖,我們在描述設備獨立像素時曾使用過這張圖,在瀏覽器調試移動端時頁面上給定的像素大小就是理想視口大小,它的單位正是設備獨立像素。
上面在介紹CSS像素時曾經提到頁面的縮放系數 = CSS像素 / 設備獨立像素,實際上說頁面的縮放系數 = 理想視口寬度 / 視覺視口寬度更為准確。
所以,當頁面縮放比例為100%時,CSS像素 = 設備獨立像素,理想視口 = 視覺視口。
我們可以通過調用screen.width / height來獲取理想視口大小。
4.4 Meta viewport
元素表示那些不能由其它HTML相關元素之一表示的任何元數據信息,它可以告訴瀏覽器如何解析頁面。可以借助 元素的viewport來幫助我們設置視口、縮放等,從而讓移動端得到更好的展示效果。
<meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1; minimum-scale=1; user-scalable=no;">
上面是viewport的一個配置,我們來看看它們的具體含義:
Value |
可能值 | 描述 |
|---|---|---|
width |
正整數或device-width |
以pixels(像素)為單位, 定義布局視口的寬度。 |
height |
正整數或device-height |
以pixels(像素)為單位, 定義布局視口的高度。 |
initial-scale |
0.0 - 10.0 |
定義頁面初始縮放比率。 |
minimum-scale |
0.0 - 10.0 |
定義縮放的最小值;必須小於或等於maximum-scale的值。 |
maximum-scale |
0.0 - 10.0 |
定義縮放的最大值;必須大於或等於minimum-scale的值。 |
user-scalable |
一個布爾值(yes或者no) |
如果設置為 no,用戶將不能放大或縮小網頁。默認值為 yes。 |
4.5 移動端適配
為了在移動端讓頁面獲得更好的顯示效果,我們必須讓布局視口、視覺視口都盡可能等於理想視口。
device-width就等於理想視口的寬度,所以設置width=device-width就相當於讓布局視口等於理想視口。
由於initial-scale = 理想視口寬度 / 視覺視口寬度,所以我們設置initial-scale=1;就相當於讓視覺視口等於理想視口。
這時,1個CSS像素就等於1個設備獨立像素,而且我們也是基於理想視口來進行布局的,所以呈現出來的頁面布局在各種設備上都能大致相似。
4.6 縮放
上面提到width可以決定布局視口的寬度,實際上它並不是布局視口的唯一決定性因素,設置initial-scale也有肯能影響到布局視口,因為布局視口寬度取的是width和視覺視口寬度的最大值。
例如:若手機的理想視口寬度為400px,設置width=device-width,initial-scale=2,此時視覺視口寬度 = 理想視口寬度 / initial-scale即200px,布局視口取兩者最大值即device-width 400px。
若設置width=device-width,initial-scale=0.5,此時視覺視口寬度 = 理想視口寬度 / initial-scale即800px,布局視口取兩者最大值即800px。
4.7 獲取瀏覽器大小
瀏覽器為我們提供的獲取窗口大小的API有很多,下面我們再來對比一下:

- window.innerHeight:獲取瀏覽器視覺視口高度(包括垂直滾動條)。
- window.outerHeight:獲取瀏覽器窗口外部的高度。表示整個瀏覽器窗口的高度,包括側邊欄、窗口鑲邊和調正窗口大小的邊框。
- window.screen.Height:獲取獲屏幕取理想視口高度,這個數值是固定的,設備的分辨率/設備像素比
- window.screen.availHeight:瀏覽器窗口可用的高度。
- document.documentElement.clientHeight:獲取瀏覽器布局視口高度,包括內邊距,但不包括垂直滾動條、邊框和外邊距。
- document.documentElement.offsetHeight:包括內邊距、滾動條、邊框和外邊距。
- document.documentElement.scrollHeight:在不使用滾動條的情況下適合視口中的所有內容所需的最小寬度。測量方式與clientHeight相同:它包含元素的內邊距,但不包括邊框,外邊距或垂直滾動條。
