關(guān)于移動端適配特碳,你必須要知道的

導(dǎo)讀

移動端適配蹋岩,是我們在開發(fā)中經(jīng)常會遇到的外恕,這里面可能會遇到非常多的問題:

1px問題

UI圖完美適配方案

iPhoneX適配方案

橫屏適配

高清屏圖片模糊問題

...

上面這些問題可能我們在開發(fā)中已經(jīng)知道如何解決杆逗,但是問題產(chǎn)生的原理乡翅,以及解決方案的原理可能會模糊不清。在解決這些問題的過程中罪郊,我們往往會遇到非常多的概念:像素蠕蚜、分辨率、PPI悔橄、DPI靶累、DP、DIP癣疟、DPR挣柬、視口等等,你真的能分清這些概念的意義嗎睛挚?

本文將從移動端適配的基礎(chǔ)概念出發(fā)邪蛔,探究移動端適配各種問題的解決方案和實現(xiàn)原理。

一扎狱、英寸

一般用英寸描述屏幕的物理大小店溢,如電腦顯示器的17、22委乌,手機顯示器的4.8床牧、5.7等使用的單位都是英寸。

需要注意遭贸,上面的尺寸都是屏幕對角線的長度:

英寸(inch,縮寫為in)在荷蘭語中的本意是大拇指戈咳,一英寸就是指甲底部普通人拇指的寬度。

英寸和厘米的換算:1英寸=2.54厘米

二壕吹、分辨率

2.1 像素

像素即一個小方塊著蛙,它具有特定的位置和顏色。

圖片耳贬、電子屏幕(手機踏堡、電腦)就是由無數(shù)個具有特定顏色和特定位置的小方塊拼接而成。

像素可以作為圖片或電子屏幕的最小組成單位咒劲。

下面我們使用sketch打開一張圖片:

將這些圖片放大即可看到這些像素點:

通常我們所說的分辨率有兩種顷蟆,屏幕分辨率和圖像分辨率。

2.2 屏幕分辨率

屏幕分辨率指一個屏幕具體由多少個像素點組成腐魂。

下面是apple的官網(wǎng)上對手機分辨率的描述:

iPhone XSMax和iPhone SE的分辨率分別為2688x1242和1136x640帐偎。這表示手機分別在垂直和水平上所具有的像素點數(shù)。

當(dāng)然分辨率高不代表屏幕就清晰蛔屹,屏幕的清晰程度還與尺寸有關(guān)削樊。

2.3 圖像分辨率

我們通常說的圖片分辨率其實是指圖片含有的像素數(shù),比如一張圖片的分辨率為800x400。這表示圖片分別在垂直和水平上所具有的像素點數(shù)為800和400漫贞。

同一尺寸的圖片甸箱,分辨率越高,圖片越清晰迅脐。

2.4 PPI

PPI(PixelPerInch):每英寸包括的像素數(shù)摇肌。

PPI可以用于描述屏幕的清晰度以及一張圖片的質(zhì)量。

使用PPI描述圖片時仪际,PPI越高,圖片質(zhì)量越高昵骤,使用PPI描述屏幕時树碱,PPI越高,屏幕越清晰变秦。

在上面描述手機分辨率的圖片中成榜,我們可以看到:iPhone XSMax和iPhone SE的PPI分別為458和326,這足以證明前者的屏幕更清晰蹦玫。

由于手機尺寸為手機對角線的長度赎婚,我們通常使用如下的方法計算PPI:

$$ \frac{\sqrt{水平像素點數(shù)^2+垂直像素點數(shù)^2}}{尺寸}$$

iPhone6的PPI為 $ \frac{\sqrt{1334^2+750^2}}{4.7}=325.6$,那它每英寸約含有326個物理像素點樱溉。

2.5 DPI

DPI(DotPerInch):即每英寸包括的點數(shù)挣输。

這里的點是一個抽象的單位,它可以是屏幕像素點福贞、圖片像素點也可以是打印機的墨點撩嚼。

平時你可能會看到使用DPI來描述圖片和屏幕,這時的DPI應(yīng)該和PPI是等價的挖帘,DPI最常用的是用于描述打印機完丽,表示打印機每英寸可以打印的點數(shù)。

一張圖片在屏幕上顯示時拇舀,它的像素點數(shù)是規(guī)則排列的逻族,每個像素點都有特定的位置和顏色。

當(dāng)使用打印機進行打印時骄崩,打印機可能不會規(guī)則的將這些點打印出來聘鳞,而是使用一個個打印點來呈現(xiàn)這張圖像,這些打印點之間會有一定的空隙要拂,這就是DPI所描述的:打印點的密度搁痛。

在上面的圖像中我們可以清晰的看到,打印機是如何使用墨點來打印一張圖像宇弛。

所以鸡典,打印機的DPI越高,打印圖像的精細程度就越高枪芒,同時這也會消耗更多的墨點和時間彻况。

三谁尸、設(shè)備獨立像素

實際上,上面我們描述的像素都是物理像素纽甘,即設(shè)備上真實的物理單元良蛮。

下面我們來看看設(shè)備獨立像素究竟是如何產(chǎn)生的:

智能手機發(fā)展非常之快,在幾年之前悍赢,我們還用著分辨率非常低的手機决瞳,比如下面左側(cè)的白色手機,它的分辨率是320x480左权,我們可以在上面瀏覽正常的文字皮胡、圖片等等。

但是赏迟,隨著科技的發(fā)展屡贺,低分辨率的手機已經(jīng)不能滿足我們的需求了。很快锌杀,更高分辨率的屏幕誕生了甩栈,比如下面的黑色手機,它的分辨率是640x940糕再,正好是白色手機的兩倍量没。

理論上來講,在白色手機上相同大小的圖片和文字突想,在黑色手機上會被縮放一倍允蜈,因為它的分辨率提高了一倍。這樣蒿柳,豈不是后面出現(xiàn)更高分辨率的手機饶套,頁面元素會變得越來越小嗎?

然而,事實并不是這樣的,我們現(xiàn)在使用的智能手機仁热,不管分辨率多高氏堤,他們所展示的界面比例都是基本類似的。喬布斯在iPhone4的發(fā)布會上首次提出了RetinaDisplay(視網(wǎng)膜屏幕)的概念,它正是解決了上面的問題,這也使它成為一款跨時代的手機。

在iPhone4使用的視網(wǎng)膜屏幕中构挤,把2x2個像素當(dāng)1個像素使用,這樣讓屏幕看起來更精致惕鼓,但是元素的大小卻不會改變筋现。

如果黑色手機使用了視網(wǎng)膜屏幕的技術(shù),那么顯示結(jié)果應(yīng)該是下面的情況,比如列表的寬度為300個像素矾飞,那么在一條水平線上一膨,白色手機會用300個物理像素去渲染它,而黑色手機實際上會用600個物理像素去渲染它洒沦。

我們必須用一種單位來同時告訴不同分辨率的手機豹绪,它們在界面上顯示元素的大小是多少,這個單位就是設(shè)備獨立像素(DeviceIndependentPixels)簡稱DIP或DP申眼。上面我們說瞒津,列表的寬度為300個像素,實際上我們可以說:列表的寬度為300個設(shè)備獨立像素括尸。

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

3.1 設(shè)備像素比

設(shè)備像素比device pixel ratio簡稱dpr,即物理像素和設(shè)備獨立像素的比值功戚。

在web中娶眷,瀏覽器為我們提供了window.devicePixelRatio來幫助我們獲取dpr。

在css中啸臀,可以使用媒體查詢min-device-pixel-ratio届宠,區(qū)分dpr:

@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2){ }

在ReactNative中,我們也可以使用PixelRatio.get()來獲取DPR乘粒。

當(dāng)然豌注,上面的規(guī)則也有例外,iPhone6灯萍、7轧铁、8Plus的實際物理像素是1080x1920,在開發(fā)者工具中我們可以看到:它的設(shè)備獨立像素是414x736旦棉,設(shè)備像素比為3齿风,設(shè)備獨立像素和設(shè)備像素比的乘積并不等于1080x1920,而是等于1242x2208绑洛。

實際上救斑,手機會自動把1242x2208個像素點塞進1080*1920個物理像素點來渲染,我們不用關(guān)心這個過程真屯,而1242x2208被稱為屏幕的設(shè)計像素脸候。我們開發(fā)過程中也是以這個設(shè)計像素為準。

實際上,從蘋果提出視網(wǎng)膜屏幕開始纪他,才出現(xiàn)設(shè)備像素比這個概念鄙煤,因為在這之前,移動設(shè)備都是直接使用物理像素來進行展示茶袒。

緊接著梯刚,Android同樣使用了其他的技術(shù)方案來實現(xiàn)DPR大于1的屏幕,不過原理是類似的薪寓。由于Android屏幕尺寸非常多亡资、分辨率高低跨度非常大,不像蘋果只有它自己的幾款固定設(shè)備向叉、尺寸锥腻。所以,為了保證各種設(shè)備的顯示效果母谎,Android按照設(shè)備的像素密度將設(shè)備分成了幾個區(qū)間:

當(dāng)然瘦黑,所有的Android設(shè)備不一定嚴格按照上面的分辨率,每個類型可能對應(yīng)幾種不同分辨率奇唤,所以幸斥,每個Android手機都能根據(jù)給定的區(qū)間范圍,確定自己的DPR咬扇,從而擁有類似的顯示甲葬。當(dāng)然,僅僅是類似懈贺,由于各個設(shè)備的尺寸经窖、分辨率上的差異,設(shè)備獨立像素也不會完全相等梭灿,所以各種Android設(shè)備仍然不能做到在展示上完全相等画侣。

3.2 移動端開發(fā)

在iOS、Android和ReactNative開發(fā)中樣式單位其實都使用的是設(shè)備獨立像素堡妒。

iOS的尺寸單位為pt棉钧,Android的尺寸單位為dp,ReactNative中沒有指定明確的單位涕蚤,它們其實都是設(shè)備獨立像素dp宪卿。

在使用ReactNative開發(fā)App時,UI給我們的原型圖一般是基于iphone6的像素給定的万栅。

為了適配所有機型佑钾,我們在寫樣式時需要把物理像素轉(zhuǎn)換為設(shè)備獨立像素:例如:如果給定一個元素的高度為200px(這里的px指物理像素,非CSS像素)烦粒,iphone6的設(shè)備像素比為2休溶,我們給定的height應(yīng)為200px/2=100dp代赁。

當(dāng)然,最好的是兽掰,你可以和設(shè)計溝通好芭碍,所有的UI圖都按照設(shè)備獨立像素來出。

我們還可以在代碼(ReactNative)中進行px和dp的轉(zhuǎn)換:

import {PixelRatio } from 'react-native';

const dpr = PixelRatio.get();

/**

* px轉(zhuǎn)換為dp

*/

export function pxConvertTodp(px) {

? return px / dpr;

}

/**

* dp轉(zhuǎn)換為px

*/

export function dpConvertTopx(dp) {

? return PixelRatio.getPixelSizeForLayoutSize(dp);

}

3.3 WEB端開發(fā)

在寫CSS時孽尽,我們用到最多的單位是px窖壕,即CSS像素,當(dāng)頁面縮放比例為100%時杉女,一個CSS像素等于一個設(shè)備獨立像素瞻讽。

但是CSS像素是很容易被改變的,當(dāng)用戶對瀏覽器進行了放大熏挎,CSS像素會被放大速勇,這時一個CSS像素會跨越更多的物理像素。

頁面的縮放系數(shù)=CSS像素/設(shè)備獨立像素坎拐。

3.4 關(guān)于屏幕

這里多說兩句Retina屏幕烦磁,因為我在很多文章中看到對Retina屏幕的誤解。

Retina屏幕只是蘋果提出的一個營銷術(shù)語:

在普通的使用距離下哼勇,人的肉眼無法分辨單個的像素點都伪。

為什么強調(diào)普通的使用距離下呢?我們來看一下它的計算公式:

$$ a=2arctan(h/2d) $$

a代表人眼視角猴蹂,h代表像素間距院溺,d代表肉眼與屏幕的距離楣嘁,符合以上條件的屏幕可以使肉眼看不見單個物理像素點磅轻。

它不能單純的表達分辨率和PPI,只能一種表達視覺效果逐虚。

讓多個物理像素渲染一個獨立像素只是Retina屏幕為了達到效果而使用的一種技術(shù)聋溜。而不是所有DPR>1的屏幕就是Retina屏幕。

比如:給你一塊超大尺寸的屏幕叭爱,即使它的PPI很高撮躁,DPR也很高,在近距離你也能看清它的像素點买雾,這就不算Retina屏幕把曼。

我們經(jīng)常見到用K和P這個單位來形容屏幕:

P代表的就是屏幕縱向的像素個數(shù),1080P即縱向有1080個像素漓穿,分辨率為1920X1080的屏幕就屬于1080P屏幕嗤军。

我們平時所說的高清屏其實就是屏幕的物理分辨率達到或超過1920X1080的屏幕。

K代表屏幕橫向有幾個1024個像素晃危,一般來講橫向像素超過2048就屬于2K屏叙赚,橫向像素超過4096就屬于4K屏老客。

四、視口

視口(viewport)代表當(dāng)前可見的計算機圖形區(qū)域震叮。在Web瀏覽器術(shù)語中胧砰,通常與瀏覽器窗口相同,但不包括瀏覽器的UI苇瓣, 菜單欄等——即指你正在瀏覽的文檔的那一部分尉间。

一般我們所說的視口共包括三種:布局視口、視覺視口和理想視口钓简,它們在屏幕適配中起著非常重要的作用乌妒。

4.1 布局視口

布局視口(layout viewport):當(dāng)我們以百分比來指定一個元素的大小時,它的計算值是由這個元素的包含塊計算而來的外邓。當(dāng)這個元素是最頂級的元素時撤蚊,它就是基于布局視口來計算的。

所以损话,布局視口是網(wǎng)頁布局的基準窗口侦啸,在PC瀏覽器上,布局視口就等于當(dāng)前瀏覽器的窗口大猩デ埂(不包括borders光涂、margins、滾動條)拧烦。

在移動端忘闻,布局視口被賦予一個默認值,大部分為980px恋博,這保證PC的網(wǎng)頁可以在手機瀏覽器上呈現(xiàn)齐佳,但是非常小,用戶可以手動對網(wǎng)頁進行放大债沮。

我們可以通過調(diào)用document.documentElement.clientWidth/clientHeight來獲取布局視口大小炼吴。

4.2 視覺視口

視覺視口(visual viewport):用戶通過屏幕真實看到的區(qū)域。

視覺視口默認等于當(dāng)前瀏覽器的窗口大幸唏谩(包括滾動條寬度)硅蹦。

當(dāng)用戶對瀏覽器進行縮放時,不會改變布局視口的大小闷煤,所以頁面布局是不變的童芹,但是縮放會改變視覺視口的大小。

例如:用戶將瀏覽器窗口放大了200%鲤拿,這時瀏覽器窗口中的CSS像素會隨著視覺視口的放大而放大假褪,這時一個CSS像素會跨越更多的物理像素。

所以皆愉,布局視口會限制你的CSS布局而視覺視口決定用戶具體能看到什么嗜价。

我們可以通過調(diào)用window.innerWidth/innerHeight來獲取視覺視口大小艇抠。

4.3 理想視口

布局視口在移動端展示的效果并不是一個理想的效果,所以理想視口(ideal viewport)就誕生了:網(wǎng)站頁面在移動端展示的理想大小久锥。

如上圖家淤,我們在描述設(shè)備獨立像素時曾使用過這張圖,在瀏覽器調(diào)試移動端時頁面上給定的像素大小就是理想視口大小瑟由,它的單位正是設(shè)備獨立像素絮重。

上面在介紹CSS像素時曾經(jīng)提到頁面的縮放系數(shù)=CSS像素/設(shè)備獨立像素,實際上說頁面的縮放系數(shù)=理想視口寬度/視覺視口寬度更為準確歹苦。

所以青伤,當(dāng)頁面縮放比例為100%時,CSS像素=設(shè)備獨立像素殴瘦,理想視口=視覺視口狠角。

我們可以通過調(diào)用screen.width/height來獲取理想視口大小。

4.4 Meta viewport

<meta>元素表示那些不能由其它HTML元相關(guān)元素之一表示的任何元數(shù)據(jù)信息蚪腋,它可以告訴瀏覽器如何解析頁面丰歌。

我們可以借助<meta>元素的viewport來幫助我們設(shè)置視口、縮放等屉凯,從而讓移動端得到更好的展示效果立帖。

<meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1; minimum-scale=1; user-scalable=no;">

上面是viewport的一個配置,我們來看看它們的具體含義:

Value| 可能值| 描述 -|-|-width| 正整數(shù)或device-width| 以pixels(像素)為單位悠砚, 定義布局視口的寬度晓勇。height| 正整數(shù)或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)| 如果設(shè)置為no宗苍,用戶將不能放大或縮小網(wǎng)頁。默認值為 yes薄榛。

4.5 移動端適配

為了在移動端讓頁面獲得更好的顯示效果讳窟,我們必須讓布局視口、視覺視口都盡可能等于理想視口敞恋。

device-width就等于理想視口的寬度丽啡,所以設(shè)置width=device-width就相當(dāng)于讓布局視口等于理想視口。

由于initial-scale=理想視口寬度/視覺視口寬度硬猫,所以我們設(shè)置initial-scale=1;就相當(dāng)于讓視覺視口等于理想視口补箍。

這時改执,1個CSS像素就等于1個設(shè)備獨立像素,而且我們也是基于理想視口來進行布局的坑雅,所以呈現(xiàn)出來的頁面布局在各種設(shè)備上都能大致相似辈挂。

4.6 縮放

上面提到width可以決定布局視口的寬度,實際上它并不是布局視口的唯一決定性因素裹粤,設(shè)置initial-scale也有肯能影響到布局視口终蒂,因為布局視口寬度取的是width和視覺視口寬度的最大值。

例如:若手機的理想視口寬度為400px遥诉,設(shè)置width=device-width拇泣,initial-scale=2,此時視覺視口寬度=理想視口寬度/initial-scale即200px矮锈,布局視口取兩者最大值即device-width400px霉翔。

若設(shè)置width=device-width,initial-scale=0.5苞笨,此時視覺視口寬度=理想視口寬度/initial-scale即800px早龟,布局視口取兩者最大值即800px。

4.7 獲取瀏覽器大小

瀏覽器為我們提供的獲取窗口大小的API有很多猫缭,下面我們再來對比一下:

window.innerHeight:獲取瀏覽器視覺視口高度(包括垂直滾動條)葱弟。

window.outerHeight:獲取瀏覽器窗口外部的高度。表示整個瀏覽器窗口的高度猜丹,包括側(cè)邊欄芝加、窗口鑲邊和調(diào)正窗口大小的邊框。

window.screen.Height:獲取獲屏幕取理想視口高度射窒,這個數(shù)值是固定的藏杖,設(shè)備的分辨率/設(shè)備像素比

window.screen.availHeight:瀏覽器窗口可用的高度。

document.documentElement.clientHeight:獲取瀏覽器布局視口高度脉顿,包括內(nèi)邊距蝌麸,但不包括垂直滾動條、邊框和外邊距艾疟。

document.documentElement.offsetHeight:包括內(nèi)邊距来吩、滾動條、邊框和外邊距蔽莱。

document.documentElement.scrollHeight:在不使用滾動條的情況下適合視口中的所有內(nèi)容所需的最小寬度弟疆。測量方式與clientHeight相同:它包含元素的內(nèi)邊距,但不包括邊框盗冷,外邊距或垂直滾動條怠苔。

五、1px問題

為了適配各種屏幕仪糖,我們寫代碼時一般使用設(shè)備獨立像素來對頁面進行布局柑司。

而在設(shè)備像素比大于1的屏幕上迫肖,我們寫的1px實際上是被多個物理像素渲染,這就會出現(xiàn)1px在有些屏幕上看起來很粗的現(xiàn)象攒驰。

5.1 border-image

基于media查詢判斷不同的設(shè)備像素比給定不同的border-image:

? ? ? .border_1px{

? ? ? ? ? border-bottom: 1px solid #000;

? ? ? ? }

? ? ? ? @media only screen and (-webkit-min-device-pixel-ratio:2){

? ? ? ? ? ? .border_1px{

? ? ? ? ? ? ? ? border-bottom: none;

? ? ? ? ? ? ? ? border-width: 0 0 1px 0;

? ? ? ? ? ? ? ? border-image: url(../img/1pxline.png) 0 0 2 0 stretch;

? ? ? ? ? ? }

? ? ? ? }

5.2 background-image

和border-image類似咒程,準備一張符合條件的邊框背景圖,模擬在背景上讼育。

? ? ? .border_1px{

? ? ? ? ? border-bottom: 1px solid #000;

? ? ? ? }

? ? ? ? @media only screen and (-webkit-min-device-pixel-ratio:2){

? ? ? ? ? ? .border_1px{

? ? ? ? ? ? ? ? background: url(../img/1pxline.png) repeat-x left bottom;

? ? ? ? ? ? ? ? background-size: 100% 1px;

? ? ? ? ? ? }

? ? ? ? }

上面兩種都需要單獨準備圖片帐姻,而且圓角不是很好處理,但是可以應(yīng)對大部分場景奶段。

5.3 偽類 + transform

基于media查詢判斷不同的設(shè)備像素比對線條進行縮放:

? ? ? .border_1px:before{

? ? ? ? ? content: '';

? ? ? ? ? position: absolute;

? ? ? ? ? top: 0;

? ? ? ? ? height: 1px;

? ? ? ? ? width: 100%;

? ? ? ? ? background-color: #000;

? ? ? ? ? transform-origin: 50% 0%;

? ? ? ? }

? ? ? ? @media only screen and (-webkit-min-device-pixel-ratio:2){

? ? ? ? ? ? .border_1px:before{

? ? ? ? ? ? ? ? transform: scaleY(0.5);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? @media only screen and (-webkit-min-device-pixel-ratio:3){

? ? ? ? ? ? .border_1px:before{

? ? ? ? ? ? ? ? transform: scaleY(0.33);

? ? ? ? ? ? }

? ? ? ? }

這種方式可以滿足各種場景饥瓷,如果需要滿足圓角,只需要給偽類也加上border-radius即可痹籍。

5.4 svg

上面我們border-image和background-image都可以模擬1px邊框呢铆,但是使用的都是位圖,還需要外部引入蹲缠。

借助PostCSS的postcss-write-svg我們能直接使用border-image和background-image創(chuàng)建svg的1px邊框:

@svg border_1px {

? height: 2px;

? @rect {

? ? fill: var(--color, black);

? ? width: 100%;

? ? height: 50%;

? ? }

? }

.example { border: 1px solid transparent; border-image: svg(border_1px param(--color #00b1ff)) 2 2 stretch; }

編譯后:

.example { border: 1px solid transparent; border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch; }

上面的方案是大漠在他的文章中推薦使用的棺克,基本可以滿足所有場景,而且不需要外部引入线定,這是我個人比較喜歡的一種方案娜谊。

5.5 設(shè)置viewport

通過設(shè)置縮放,讓CSS像素等于真正的物理像素斤讥。

例如:當(dāng)設(shè)備像素比為3時纱皆,我們將頁面縮放1/3倍,這時1px等于一個真正的屏幕像素芭商。

? ? const scale = 1 / window.devicePixelRatio;

? ? const viewport = document.querySelector('meta[name="viewport"]');

? ? if (!viewport) {

? ? ? ? viewport = document.createElement('meta');

? ? ? ? viewport.setAttribute('name', 'viewport');

? ? ? ? window.document.head.appendChild(viewport);

? ? }

? ? viewport.setAttribute('content', 'width=device-width,user-scalable=no,initial-scale=' + scale + ',maximum-scale=' + scale + ',minimum-scale=' + scale);

實際上派草,上面這種方案是早先flexible采用的方案。

當(dāng)然铛楣,這樣做是要付出代價的近迁,這意味著你頁面上所有的布局都要按照物理像素來寫。這顯然是不現(xiàn)實的簸州,這時鉴竭,我們可以借助flexible或vw、vh來幫助我們進行適配勿侯。

六拓瞪、移動端適配方案

盡管我們可以使用設(shè)備獨立像素來保證各個設(shè)備在不同手機上顯示的效果類似缴罗,但這并不能保證它們顯示完全一致助琐,我們需要一種方案來讓設(shè)計稿得到更完美的適配。

6.1 flexible方案

flexible方案是阿里早期開源的一個移動端適配解決方案面氓,引用flexible后兵钮,我們在頁面上統(tǒng)一使用rem來布局蛆橡。

它的核心代碼非常簡單:

// set 1rem = viewWidth / 10

function setRemUnit () {

? ? var rem = docEl.clientWidth / 10

? ? docEl.style.fontSize = rem + 'px'

}

setRemUnit();

rem是相對于html節(jié)點的font-size來做計算的。

我們通過設(shè)置document.documentElement.style.fontSize就可以統(tǒng)一整個頁面的布局標準掘譬。

上面的代碼中泰演,將html節(jié)點的font-size設(shè)置為頁面clientWidth(布局視口)的1/10,即1rem就等于頁面布局視口的1/10葱轩,這就意味著我們后面使用的rem都是按照頁面比例來計算的睦焕。

這時,我們只需要將UI出的圖轉(zhuǎn)換為rem即可靴拱。

以iPhone6為例:布局視口為375px垃喊,則1rem=37.5px,這時UI給定一個元素的寬為75px(設(shè)備獨立像素)袜炕,我們只需要將它設(shè)置為75/37.5=2rem本谜。

當(dāng)然,每個布局都要計算非常繁瑣偎窘,我們可以借助PostCSS的px2rem插件來幫助我們完成這個過程乌助。

下面的代碼可以保證在頁面大小變化時,布局可以自適應(yīng)陌知,當(dāng)觸發(fā)了window的resize和pageShow事件之后自動調(diào)整html的fontSize大小他托。

? // reset rem unit on page resize

window.addEventListener('resize', setRemUnit)window.addEventListener('pageshow', function (e) {

? ? if (e.persisted) {

? ? ? setRemUnit()

? ? }

})

由于viewport單位得到眾多瀏覽器的兼容,上面這種方案現(xiàn)在已經(jīng)被官方棄用:

lib-flexible這個過渡方案已經(jīng)可以放棄使用仆葡,不管是現(xiàn)在的版本還是以前的版本上祈,都存有一定的問題。建議大家開始使用viewport來替代此方案浙芙。

下面我們來看看現(xiàn)在最流行的vh登刺、vw方案。

6.2 vh嗡呼、vw方案

vh纸俭、vw方案即將視覺視口寬度window.innerWidth和視覺視口高度window.innerHeight等分為 100 份。

上面的flexible方案就是模仿這種方案南窗,因為早些時候vw還沒有得到很好的兼容揍很。

vw(Viewport's width):1vw等于視覺視口的1%

vh(Viewport's height):1vh為視覺視口高度的1%

vmin:vw和vh中的較小值

vmax: 選取vw和vh中的較大值

如果視覺視口為375px,那么1vw=3.75px万伤,這時UI給定一個元素的寬為75px(設(shè)備獨立像素)窒悔,我們只需要將它設(shè)置為75/3.75=20vw。

這里的比例關(guān)系我們也不用自己換算敌买,我們可以使用PostCSS的postcss-px-to-viewport插件幫我們完成這個過程简珠。寫代碼時,我們只需要根據(jù)UI給的設(shè)計圖寫px單位即可。

當(dāng)然聋庵,沒有一種方案是十全十美的膘融,vw同樣有一定的缺陷:

px轉(zhuǎn)換成vw不一定能完全整除,因此有一定的像素差祭玉。

比如當(dāng)容器使用vw氧映,margin采用px時,很容易造成整體寬度超過100vw脱货,從而影響布局效果岛都。當(dāng)然我們也是可以避免的,例如使用padding代替margin振峻,結(jié)合calc()函數(shù)使用等等...

七疗绣、適配iPhoneX

iPhoneX的出現(xiàn)將手機的顏值帶上了一個新的高度,它取消了物理按鍵铺韧,改成了底部的小黑條多矮,但是這樣的改動給開發(fā)者適配移動端又增加了難度。

7.1 安全區(qū)域

在iPhoneX發(fā)布后哈打,許多廠商相繼推出了具有邊緣屏幕的手機塔逃。

這些手機和普通手機在外觀上無外乎做了三個改動:圓角(corners)、劉海(sensor housing)和小黑條(HomeIndicator)料仗。為了適配這些手機湾盗,安全區(qū)域這個概念變誕生了:安全區(qū)域就是一個不受上面三個效果的可視窗口范圍。

為了保證頁面的顯示效果立轧,我們必須把頁面限制在安全范圍內(nèi)格粪,但是不影響整體效果。

7.2 viewport-fit

viewport-fit是專門為了適配iPhoneX而誕生的一個屬性氛改,它用于限制網(wǎng)頁如何在安全區(qū)域內(nèi)進行展示帐萎。

contain: 可視窗口完全包含網(wǎng)頁內(nèi)容

cover:網(wǎng)頁內(nèi)容完全覆蓋可視窗口

默認情況下或者設(shè)置為auto和contain效果相同。

7.3 env胜卤、constant

我們需要將頂部和底部合理的擺放在安全區(qū)域內(nèi)疆导,iOS11新增了兩個CSS函數(shù)env、constant葛躏,用于設(shè)定安全區(qū)域與邊界的距離澈段。

函數(shù)內(nèi)部可以是四個常量:

safe-area-inset-left:安全區(qū)域距離左邊邊界距離

safe-area-inset-right:安全區(qū)域距離右邊邊界距離

safe-area-inset-top:安全區(qū)域距離頂部邊界距離

safe-area-inset-bottom:安全區(qū)域距離底部邊界距離

注意:我們必須指定viweport-fit后才能使用這兩個函數(shù):

<meta name="viewport" content="viewport-fit=cover">

constant在iOS<11.2的版本中生效,env在iOS>=11.2的版本中生效舰攒,這意味著我們往往要同時設(shè)置他們败富,將頁面限制在安全區(qū)域內(nèi):

body {

? padding-bottom: constant(safe-area-inset-bottom);

? padding-bottom: env(safe-area-inset-bottom);

}

當(dāng)使用底部固定導(dǎo)航欄時,我們要為他們設(shè)置padding值:

{

? padding-bottom: constant(safe-area-inset-bottom);

? padding-bottom: env(safe-area-inset-bottom);

}

八摩窃、橫屏適配

很多視口我們要對橫屏和豎屏顯示不同的布局兽叮,所以我們需要檢測在不同的場景下給定不同的樣式:

8.1 JavaScript檢測橫屏

window.orientation:獲取屏幕旋轉(zhuǎn)方向

window.addEventListener("resize", ()=>{

? ? if (window.orientation === 180 || window.orientation === 0) {

? ? ? // 正常方向或屏幕旋轉(zhuǎn)180度

? ? ? ? console.log('豎屏');

? ? };

? ? if (window.orientation === 90 || window.orientation === -90 ){

? ? ? // 屏幕順時鐘旋轉(zhuǎn)90度或屏幕逆時針旋轉(zhuǎn)90度

? ? ? ? console.log('橫屏');

? ? }?

});

8.2 CSS檢測橫屏

@media screen and (orientation: portrait) {

? /*豎屏...*/

}

@media screen and (orientation: landscape) {

? /*橫屏...*/

}

九、圖片模糊問題

9.1 產(chǎn)生原因

我們平時使用的圖片大多數(shù)都屬于位圖(png、jpg...)充择,位圖由一個個像素點構(gòu)成的德玫,每個像素都具有特定的位置和顏色值:

理論上匪蟀,位圖的每個像素對應(yīng)在屏幕上使用一個物理像素來渲染椎麦,才能達到最佳的顯示效果。

而在dpr>1的屏幕上材彪,位圖的一個像素可能由多個物理像素來渲染观挎,然而這些物理像素點并不能被準確的分配上對應(yīng)位圖像素的顏色,只能取近似值段化,所以相同的圖片在dpr>1的屏幕上就會模糊:

9.2 解決方案

為了保證圖片質(zhì)量嘁捷,我們應(yīng)該盡可能讓一個屏幕像素來渲染一個圖片像素,所以显熏,針對不同DPR的屏幕雄嚣,我們需要展示不同分辨率的圖片。

如:在dpr=2的屏幕上展示兩倍圖(@2x)喘蟆,在dpr=3的屏幕上展示三倍圖(@3x)缓升。

9.3 media查詢

使用media查詢判斷不同的設(shè)備像素比來顯示不同精度的圖片:

? ? ? .avatar{

? ? ? ? ? ? background-image: url(conardLi_1x.png);

? ? ? ? }

? ? ? ? @media only screen and (-webkit-min-device-pixel-ratio:2){

? ? ? ? ? ? .avatar{

? ? ? ? ? ? ? ? background-image: url(conardLi_2x.png);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? @media only screen and (-webkit-min-device-pixel-ratio:3){

? ? ? ? ? ? .avatar{

? ? ? ? ? ? ? ? background-image: url(conardLi_3x.png);

? ? ? ? ? ? }

? ? ? ? }

只適用于背景圖

9.4 image-set

使用image-set:

.avatar {

? ? background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x );

}

只適用于背景圖

9.5 srcset

使用img標簽的srcset屬性,瀏覽器會自動根據(jù)像素密度匹配最佳顯示圖片:

<img src="conardLi_1x.png"

? ? srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">

9.6 JavaScript拼接圖片url

使用window.devicePixelRatio獲取設(shè)備像素比蕴轨,遍歷所有圖片港谊,替換圖片地址:

const dpr = window.devicePixelRatio;

const images =? document.querySelectorAll('img');

images.forEach((img)=>{

? img.src.replace(".", `@${dpr}x.`);

})

9.7 使用svg

SVG的全稱是可縮放矢量圖(ScalableVectorGraphics)。不同于位圖的基于像素橙弱,SVG則是屬于對圖像的形狀描述歧寺,所以它本質(zhì)上是文本文件,體積較小棘脐,且不管放大多少倍都不會失真斜筐。

除了我們手動在代碼中繪制svg,我們還可以像使用位圖一樣使用svg圖片:

<img src="conardLi.svg">

<img src="data:image/svg+xml;base64,[data]">

.avatar {

? background: url(conardLi.svg);

}

參考

https://99designs.com/blog/tips/ppi-vs-dpi-whats-the-difference/

https://www.w3cplus.com/css/vw-for-layout.html

https://aotu.io/notes/2017/11/27/iphonex/index.html

小結(jié)

希望你閱讀本篇文章后可以達到以下幾點:

理清移動端適配常用概念

理解移動端適配問題產(chǎn)生的原理蛀缝,至少掌握一種解決方案

文中如有錯誤奴艾,歡迎在后臺指正,如果這篇文章幫助到了你,歡迎關(guān)注微信公眾號【筑夢編程】内斯,了解學(xué)習(xí)更多有趣又有用的知識蕴潦!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市俘闯,隨后出現(xiàn)的幾起案子潭苞,更是在濱河造成了極大的恐慌,老刑警劉巖真朗,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件此疹,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機蝗碎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門湖笨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祷嘶,“玉大人订雾,你說我怎么就攤上這事÷园澹” “怎么了眠菇?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵边败,是天一觀的道長。 經(jīng)常有香客問我捎废,道長笑窜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任登疗,我火速辦了婚禮排截,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辐益。我一直安慰自己断傲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布荷腊。 她就那樣靜靜地躺著艳悔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪女仰。 梳的紋絲不亂的頭發(fā)上猜年,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音疾忍,去河邊找鬼乔外。 笑死,一個胖子當(dāng)著我的面吹牛一罩,可吹牛的內(nèi)容都是我干的杨幼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼聂渊,長吁一口氣:“原來是場噩夢啊……” “哼差购!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汉嗽,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤欲逃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饼暑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稳析,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡洗做,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了彰居。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诚纸。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖陈惰,靈堂內(nèi)的尸體忽然破棺而出畦徘,到底是詐尸還是另有隱情,我是刑警寧澤奴潘,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布旧烧,位于F島的核電站影钉,受9級特大地震影響画髓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜平委,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一奈虾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧廉赔,春花似錦肉微、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馏艾,卻和暖如春劳曹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背琅摩。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工铁孵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人房资。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓蜕劝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親轰异。 傳聞我的和親對象是個殘疾皇子岖沛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容