導讀
移動端適配违施,是我們在開發(fā)中經(jīng)常會遇到的竞漾,這里面可能會遇到非常多的問題:
????1px問題
????UI圖完美適配方案
????iPhoneX適配方案
????橫屏適配
????高清屏圖片模糊問題
????...
上面這些問題可能我們在開發(fā)中已經(jīng)知道如何解決穆趴,但是問題產(chǎn)生的原理次绘,以及解決方案的原理可能會模糊不清荠列。在解決這些問題的過程中沫换,我們往往會遇到非常多的概念:像素臭蚁、分辨率、PPI讯赏、DPI垮兑、DP、DIP漱挎、DPR系枪、視口等等,你真的能分清這些概念的意義嗎磕谅?
本文將從移動端適配的基礎概念出發(fā)私爷,探究移動端適配各種問題的解決方案和實現(xiàn)原理。
一膊夹、英寸
一般用英寸描述屏幕的物理大小衬浑,如電腦顯示器的17、22放刨,手機顯示器的4.8工秩、5.7等使用的單位都是英寸。
需要注意宏榕,上面的尺寸都是屏幕對角線的長度:
英寸(inch,縮寫為in)在荷蘭語中的本意是大拇指拓诸,一英寸就是指甲底部普通人拇指的寬度。
英寸和厘米的換算:1英寸 = 2.54 厘米
二麻昼、分辨率
2.1 像素
像素即一個小方塊,它具有特定的位置和顏色馋辈。
圖片抚芦、電子屏幕(手機、電腦)就是由無數(shù)個具有特定顏色和特定位置的小方塊拼接而成迈螟。
像素可以作為圖片或電子屏幕的最小組成單位叉抡。
下面我們使用sketch打開一張圖片:
將這些圖片放大即可看到這些像素點:
通常我們所說的分辨率有兩種,屏幕分辨率和圖像分辨率答毫。
2.2 屏幕分辨率
屏幕分辨率指一個屏幕具體由多少個像素點組成褥民。
下面是apple的官網(wǎng)上對手機分辨率的描述:
iPhone XS Max和iPhone SE的分辨率分別為2688 x 1242和1136 x 640。這表示手機分別在垂直和水平上所具有的像素點數(shù)洗搂。
當然分辨率高不代表屏幕就清晰消返,屏幕的清晰程度還與尺寸有關载弄。
2.3 圖像分辨率
我們通常說的圖片分辨率其實是指圖片含有的像素數(shù),比如一張圖片的分辨率為800 x 400撵颊。這表示圖片分別在垂直和水平上所具有的像素點數(shù)為800和400宇攻。
同一尺寸的圖片,分辨率越高倡勇,圖片越清晰逞刷。
2.4 PPI
PPI(Pixel Per Inch):每英寸包括的像素數(shù)。
PPI可以用于描述屏幕的清晰度以及一張圖片的質(zhì)量妻熊。
使用PPI描述圖片時夸浅,PPI越高,圖片質(zhì)量越高扔役,使用PPI描述屏幕時帆喇,PPI越高,屏幕越清晰厅目。
在上面描述手機分辨率的圖片中番枚,我們可以看到:iPhone XS Max和iPhone SE的PPI分別為458和326,這足以證明前者的屏幕更清晰损敷。
由于手機尺寸為手機對角線的長度葫笼,我們通常使用如下的方法計算PPI:
iPhone 6的PPI為圖1,那它每英寸約含有326個物理像素點拗馒。
2.5 DPI
DPI(Dot Per Inch):即每英寸包括的點數(shù)路星。
這里的點是一個抽象的單位,它可以是屏幕像素點诱桂、圖片像素點也可以是打印機的墨點洋丐。
平時你可能會看到使用DPI來描述圖片和屏幕,這時的DPI應該和PPI是等價的挥等,DPI最常用的是用于描述打印機友绝,表示打印機每英寸可以打印的點數(shù)。
一張圖片在屏幕上顯示時肝劲,它的像素點數(shù)是規(guī)則排列的迁客,每個像素點都有特定的位置和顏色。
當使用打印機進行打印時辞槐,打印機可能不會規(guī)則的將這些點打印出來掷漱,而是使用一個個打印點來呈現(xiàn)這張圖像,這些打印點之間會有一定的空隙榄檬,這就是DPI所描述的:打印點的密度卜范。
在上面的圖像中我們可以清晰的看到,打印機是如何使用墨點來打印一張圖像鹿榜。
所以海雪,打印機的DPI越高锦爵,打印圖像的精細程度就越高,同時這也會消耗更多的墨點和時間喳魏。
三棉浸、設備獨立像素
實際上,上面我們描述的像素都是物理像素刺彩,即設備上真實的物理單元迷郑。
下面我們來看看設備獨立像素究竟是如何產(chǎn)生的:
智能手機發(fā)展非常之快,在幾年之前创倔,我們還用著分辨率非常低的手機嗡害,比如下面左側的白色手機,它的分辨率是320x480畦攘,我們可以在上面瀏覽正常的文字霸妹、圖片等等。
但是知押,隨著科技的發(fā)展叹螟,低分辨率的手機已經(jīng)不能滿足我們的需求了。很快台盯,更高分辨率的屏幕誕生了罢绽,比如下面的黑色手機,它的分辨率是640x940静盅,正好是白色手機的兩倍良价。
理論上來講,在白色手機上相同大小的圖片和文字蒿叠,在黑色手機上會被縮放一倍明垢,因為它的分辨率提高了一倍。這樣市咽,豈不是后面出現(xiàn)更高分辨率的手機痊银,頁面元素會變得越來越小嗎?
然而施绎,事實并不是這樣的曼验,我們現(xiàn)在使用的智能手機,不管分辨率多高粘姜,他們所展示的界面比例都是基本類似的。喬布斯在iPhone4的發(fā)布會上首次提出了Retina Display(視網(wǎng)膜屏幕)的概念熔酷,它正是解決了上面的問題孤紧,這也使它成為一款跨時代的手機。
在iPhone4使用的視網(wǎng)膜屏幕中拒秘,把2x2個像素當1個像素使用号显,這樣讓屏幕看起來更精致臭猜,但是元素的大小卻不會改變。
如果黑色手機使用了視網(wǎng)膜屏幕的技術押蚤,那么顯示結果應該是下面的情況蔑歌,比如列表的寬度為300個像素,那么在一條水平線上揽碘,白色手機會用300個物理像素去渲染它次屠,而黑色手機實際上會用600個物理像素去渲染它。
我們必須用一種單位來同時告訴不同分辨率的手機雳刺,它們在界面上顯示元素的大小是多少劫灶,這個單位就是設備獨立像素(Device Independent Pixels)簡稱DIP或DP。上面我們說掖桦,列表的寬度為300個像素本昏,實際上我們可以說:列表的寬度為300個設備獨立像素。
打開chrome的開發(fā)者工具枪汪,我們可以模擬各個手機型號的顯示情況涌穆,每種型號上面會顯示一個尺寸,比如iPhone X顯示的尺寸是375x812雀久,實際iPhone X的分辨率會比這高很多宿稀,這里顯示的就是設備獨立像素。
3.1 設備像素比
設備像素比device pixel ratio簡稱dpr岸啡,即物理像素和設備獨立像素的比值原叮。
在web中,瀏覽器為我們提供了window.devicePixelRatio來幫助我們獲取dpr巡蘸。
在css中奋隶,可以使用媒體查詢min-device-pixel-ratio,區(qū)分dpr:
@media(-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){ }
在React Native中悦荒,我們也可以使用PixelRatio.get()來獲取DPR唯欣。
當然,上面的規(guī)則也有例外搬味,iPhone 6境氢、7、8 Plus的實際物理像素是1080 x 1920碰纬,在開發(fā)者工具中我們可以看到:它的設備獨立像素是414 x 736萍聊,設備像素比為3,設備獨立像素和設備像素比的乘積并不等于1080 x 1920悦析,而是等于1242 x 2208寿桨。
實際上,手機會自動把1242 x 2208個像素點塞進1080 * 1920個物理像素點來渲染强戴,我們不用關心這個過程亭螟,而1242 x 2208被稱為屏幕的設計像素挡鞍。我們開發(fā)過程中也是以這個設計像素為準。
實際上预烙,從蘋果提出視網(wǎng)膜屏幕開始墨微,才出現(xiàn)設備像素比這個概念,因為在這之前扁掸,移動設備都是直接使用物理像素來進行展示翘县。
緊接著,Android同樣使用了其他的技術方案來實現(xiàn)DPR大于1的屏幕也糊,不過原理是類似的炼蹦。由于Android屏幕尺寸非常多、分辨率高低跨度非常大狸剃,不像蘋果只有它自己的幾款固定設備掐隐、尺寸。所以钞馁,為了保證各種設備的顯示效果虑省,Android按照設備的像素密度將設備分成了幾個區(qū)間:
當然,所有的Android設備不一定嚴格按照上面的分辨率僧凰,每個類型可能對應幾種不同分辨率探颈,所以,每個Android手機都能根據(jù)給定的區(qū)間范圍训措,確定自己的DPR伪节,從而擁有類似的顯示。當然绩鸣,僅僅是類似怀大,由于各個設備的尺寸、分辨率上的差異呀闻,設備獨立像素也不會完全相等化借,所以各種Android設備仍然不能做到在展示上完全相等。
3.2 移動端開發(fā)
在iOS捡多、Android和React Native開發(fā)中樣式單位其實都使用的是設備獨立像素蓖康。
iOS的尺寸單位為pt,Android的尺寸單位為dp垒手,React Native中沒有指定明確的單位蒜焊,它們其實都是設備獨立像素dp。
在使用React Native開發(fā)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端開發(fā)
在寫CSS時,我們用到最多的單位是px朴恳,即CSS像素抄罕,當頁面縮放比例為100%時,一個CSS像素等于一個設備獨立像素于颖。
但是CSS像素是很容易被改變的呆贿,當用戶對瀏覽器進行了放大,CSS像素會被放大森渐,這時一個CSS像素會跨越更多的物理像素做入。
頁面的縮放系數(shù) = CSS像素 / 設備獨立像素。
3.4 關于屏幕
這里多說兩句Retina屏幕同衣,因為我在很多文章中看到對Retina屏幕的誤解竟块。
Retina屏幕只是蘋果提出的一個營銷術語:
在普通的使用距離下,人的肉眼無法分辨單個的像素點耐齐。
為什么強調(diào)普通的使用距離下呢浪秘?我們來看一下它的計算公式:
a代表人眼視角,h代表像素間距埠况,d代表肉眼與屏幕的距離耸携,符合以上條件的屏幕可以使肉眼看不見單個物理像素點。
它不能單純的表達分辨率和PPI询枚,只能一種表達視覺效果违帆。
讓多個物理像素渲染一個獨立像素只是Retina屏幕為了達到效果而使用的一種技術。而不是所有DPR > 1的屏幕就是Retina屏幕金蜀。
比如:給你一塊超大尺寸的屏幕刷后,即使它的PPI很高,DPR也很高渊抄,在近距離你也能看清它的像素點尝胆,這就不算Retina屏幕。
我們經(jīng)常見到用K和P這個單位來形容屏幕:
P代表的就是屏幕縱向的像素個數(shù)护桦,1080P即縱向有1080個像素含衔,分辨率為1920X1080的屏幕就屬于1080P屏幕。
我們平時所說的高清屏其實就是屏幕的物理分辨率達到或超過1920X1080的屏幕。
K代表屏幕橫向有幾個1024個像素贪染,一般來講橫向像素超過2048就屬于2K屏缓呛,橫向像素超過4096就屬于4K屏。
四杭隙、視口
視口(viewport)代表當前可見的計算機圖形區(qū)域哟绊。在Web瀏覽器術語中,通常與瀏覽器窗口相同痰憎,但不包括瀏覽器的UI票髓, 菜單欄等——即指你正在瀏覽的文檔的那一部分。
一般我們所說的視口共包括三種:布局視口铣耘、視覺視口和理想視口洽沟,它們在屏幕適配中起著非常重要的作用。
4.1 布局視口
布局視口(layout viewport):當我們以百分比來指定一個元素的大小時蜗细,它的計算值是由這個元素的包含塊計算而來的裆操。當這個元素是最頂級的元素時,它就是基于布局視口來計算的鳄乏。
所以跷车,布局視口是網(wǎng)頁布局的基準窗口,在PC瀏覽器上橱野,布局視口就等于當前瀏覽器的窗口大行嘟伞(不包括borders、margins水援、滾動條)密强。
在移動端,布局視口被賦予一個默認值蜗元,大部分為980px或渤,這保證PC的網(wǎng)頁可以在手機瀏覽器上呈現(xiàn),但是非常小奕扣,用戶可以手動對網(wǎng)頁進行放大薪鹦。
我們可以通過調(diào)用document.documentElement.clientWidth / clientHeight來獲取布局視口大小。
4.2 視覺視口
視覺視口(visual viewport):用戶通過屏幕真實看到的區(qū)域惯豆。
視覺視口默認等于當前瀏覽器的窗口大谐卮拧(包括滾動條寬度)。
當用戶對瀏覽器進行縮放時楷兽,不會改變布局視口的大小地熄,所以頁面布局是不變的,但是縮放會改變視覺視口的大小芯杀。
例如:用戶將瀏覽器窗口放大了200%端考,這時瀏覽器窗口中的CSS像素會隨著視覺視口的放大而放大雅潭,這時一個CSS像素會跨越更多的物理像素。
所以却特,布局視口會限制你的CSS布局而視覺視口決定用戶具體能看到什么扶供。
我們可以通過調(diào)用window.innerWidth / innerHeight來獲取視覺視口大小。
4.3 理想視口
布局視口在移動端展示的效果并不是一個理想的效果核偿,所以理想視口(ideal viewport)就誕生了:網(wǎng)站頁面在移動端展示的理想大小诚欠。
如上圖,我們在描述設備獨立像素時曾使用過這張圖漾岳,在瀏覽器調(diào)試移動端時頁面上給定的像素大小就是理想視口大小,它的單位正是設備獨立像素粉寞。
上面在介紹CSS像素時曾經(jīng)提到頁面的縮放系數(shù) = CSS像素 / 設備獨立像素尼荆,實際上說頁面的縮放系數(shù) = 理想視口寬度 / 視覺視口寬度更為準確。
所以唧垦,當頁面縮放比例為100%時捅儒,CSS像素 = 設備獨立像素,理想視口 = 視覺視口振亮。
我們可以通過調(diào)用screen.width / height來獲取理想視口大小巧还。
4.4 Meta viewport
<meta>元素表示那些不能由其它HTML元相關元素之一表示的任何元數(shù)據(jù)信息,它可以告訴瀏覽器如何解析頁面坊秸。
我們可以借助<meta>元素的viewport來幫助我們設置視口麸祷、縮放等,從而讓移動端得到更好的展示效果褒搔。
<meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1; minimum-scale=1; user-scalable=no;">
上面是viewport的一個配置阶牍,我們來看看它們的具體含義:
4.5 移動端適配
為了在移動端讓頁面獲得更好的顯示效果,我們必須讓布局視口星瘾、視覺視口都盡可能等于理想視口走孽。
device-width就等于理想視口的寬度,所以設置width=device-width就相當于讓布局視口等于理想視口琳状。
由于initial-scale = 理想視口寬度 / 視覺視口寬度磕瓷,所以我們設置initial-scale=1;就相當于讓視覺視口等于理想視口。
這時念逞,1個CSS像素就等于1個設備獨立像素困食,而且我們也是基于理想視口來進行布局的,所以呈現(xiàn)出來的頁面布局在各種設備上都能大致相似肮柜。
4.6 縮放
上面提到width可以決定布局視口的寬度陷舅,實際上它并不是布局視口的唯一決定性因素,設置initial-scale也有肯能影響到布局視口审洞,因為布局視口寬度取的是width和視覺視口寬度的最大值莱睁。
例如:若手機的理想視口寬度為400px待讳,設置width=device-width,initial-scale=2仰剿,此時視覺視口寬度 = 理想視口寬度 / initial-scale即200px创淡,布局視口取兩者最大值即device-width400px。
若設置width=device-width南吮,initial-scale=0.5琳彩,此時視覺視口寬度 = 理想視口寬度 / initial-scale即800px能庆,布局視口取兩者最大值即800px脱衙。
4.7 獲取瀏覽器大小
瀏覽器為我們提供的獲取窗口大小的API有很多,下面我們再來對比一下:
window.innerHeight:獲取瀏覽器視覺視口高度(包括垂直滾動條)笛园。
window.outerHeight:獲取瀏覽器窗口外部的高度涂邀。表示整個瀏覽器窗口的高度瘟仿,包括側邊欄、窗口鑲邊和調(diào)正窗口大小的邊框比勉。
window.screen.Height:獲取獲屏幕取理想視口高度劳较,這個數(shù)值是固定的,設備的分辨率/設備像素比
window.screen.availHeight:瀏覽器窗口可用的高度浩聋。
document.documentElement.clientHeight:獲取瀏覽器布局視口高度观蜗,包括內(nèi)邊距,但不包括垂直滾動條衣洁、邊框和外邊距墓捻。
document.documentElement.offsetHeight:包括內(nèi)邊距、滾動條毙替、邊框和外邊距袱院。
document.documentElement.scrollHeight:在不使用滾動條的情況下適合視口中的所有內(nèi)容所需的最小寬度欲虚。測量方式與clientHeight相同:它包含元素的內(nèi)邊距,但不包括邊框锈锤,外邊距或垂直滾動條摔握。
五、1px問題
為了適配各種屏幕痰滋,我們寫代碼時一般使用設備獨立像素來對頁面進行布局逻恐。
而在設備像素比大于1的屏幕上挽拂,我們寫的1px實際上是被多個物理像素渲染览爵,這就會出現(xiàn)1px在有些屏幕上看起來很粗的現(xiàn)象。
5.1 border-image
基于media查詢判斷不同的設備像素比給定不同的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;
????}
}
上面兩種都需要單獨準備圖片拍屑,而且圓角不是很好處理途戒,但是可以應對大部分場景。
5.3 偽類 + transform
基于media查詢判斷不同的設備像素比對線條進行縮放:
.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 設置viewport
通過設置縮放崔泵,讓CSS像素等于真正的物理像素秒赤。
例如:當設備像素比為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采用的方案幌甘。
當然潮售,這樣做是要付出代價的,這意味著你頁面上所有的布局都要按照物理像素來寫锅风。這顯然是不現(xiàn)實的酥诽,這時,我們可以借助flexible或vw皱埠、vh來幫助我們進行適配肮帐。
六、移動端適配方案
盡管我們可以使用設備獨立像素來保證各個設備在不同手機上顯示的效果類似边器,但這并不能保證它們顯示完全一致训枢,我們需要一種方案來讓設計稿得到更完美的適配。
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來做計算的袋坑。
我們通過設置document.documentElement.style.fontSize就可以統(tǒng)一整個頁面的布局標準。
上面的代碼中枣宫,將html節(jié)點的font-size設置為頁面clientWidth(布局視口)的1/10,即1rem就等于頁面布局視口的1/10吃环,這就意味著我們后面使用的rem都是按照頁面比例來計算的也颤。
這時,我們只需要將UI出的圖轉換為rem即可郁轻。
以iPhone6為例:布局視口為375px翅娶,則1rem = 37.5px,這時UI給定一個元素的寬為75px(設備獨立像素)好唯,我們只需要將它設置為75 / 37.5 = 2rem竭沫。
當然,每個布局都要計算非常繁瑣骑篙,我們可以借助PostCSS的px2rem插件來幫助我們完成這個過程蜕提。
下面的代碼可以保證在頁面大小變化時,布局可以自適應靶端,當觸發(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(設備獨立像素)赃泡,我們只需要將它設置為75 / 3.75 = 20vw寒波。
這里的比例關系我們也不用自己換算,我們可以使用PostCSS的postcss-px-to-viewport插件幫我們完成這個過程升熊。寫代碼時俄烁,我們只需要根據(jù)UI給的設計圖寫px單位即可。
當然级野,沒有一種方案是十全十美的页屠,vw同樣有一定的缺陷:
????px轉換成vw不一定能完全整除,因此有一定的像素差蓖柔。
????比如當容器使用vw辰企,margin采用px時,很容易造成整體寬度超過100vw况鸣,從而影響布局效果牢贸。當然我們也是可以避免的,例如使用padding代替margin镐捧,結合calc()函數(shù)使用等等...
七潜索、適配iPhoneX
iPhoneX的出現(xiàn)將手機的顏值帶上了一個新的高度,它取消了物理按鍵懂酱,改成了底部的小黑條竹习,但是這樣的改動給開發(fā)者適配移動端又增加了難度。
7.1 安全區(qū)域
在iPhoneX發(fā)布后列牺,許多廠商相繼推出了具有邊緣屏幕的手機由驹。
這些手機和普通手機在外觀上無外乎做了三個改動:圓角(corners)、劉海(sensor housing)和小黑條(Home Indicator)。為了適配這些手機蔓榄,安全區(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)容完全覆蓋可視窗口
默認情況下或者設置為auto和contain效果相同伍俘。
7.3 env、constant
我們需要將頂部和底部合理的擺放在安全區(qū)域內(nèi)勉躺,iOS11新增了兩個CSS函數(shù)env癌瘾、constant,用于設定安全區(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的版本中生效,這意味著我們往往要同時設置他們蜕企,將頁面限制在安全區(qū)域內(nèi):
body {
????padding-bottom: constant(safe-area-inset-bottom);
????padding-bottom: env(safe-area-inset-bottom);
}
當使用底部固定導航欄時咬荷,我們要為他們設置padding值:
{
? padding-bottom: constant(safe-area-inset-bottom);
? padding-bottom: env(safe-area-inset-bottom);
}
八、橫屏適配
很多視口我們要對橫屏和豎屏顯示不同的布局轻掩,所以我們需要檢測在不同的場景下給定不同的樣式:
8.1 JavaScript檢測橫屏
window.orientation:獲取屏幕旋轉方向
window.addEventListener("resize", ()=>{
????if (window.orientation === 180 || window.orientation === 0) {
????????// 正常方向或屏幕旋轉180度
????????console.log('豎屏');
????};
????if (window.orientation === 90 || window.orientation === -90 ){
????// 屏幕順時鐘旋轉90度或屏幕逆時針旋轉90度
????console.log('橫屏');
????}
});?
8.2 CSS檢測橫屏
@media screen and (orientation: portrait) {
? /*豎屏...*/}
@media screen and (orientation: landscape) {
? /*橫屏...*/}
九幸乒、圖片模糊問題
9.1 產(chǎn)生原因
我們平時使用的圖片大多數(shù)都屬于位圖(png、jpg...)唇牧,位圖由一個個像素點構成的罕扎,每個像素都具有特定的位置和顏色值:
理論上,位圖的每個像素對應在屏幕上使用一個物理像素來渲染丐重,才能達到最佳的顯示效果壳影。
而在dpr > 1的屏幕上,位圖的一個像素可能由多個物理像素來渲染弥臼,然而這些物理像素點并不能被準確的分配上對應位圖像素的顏色,只能取近似值根灯,所以相同的圖片在dpr > 1的屏幕上就會模糊:
9.2 解決方案
為了保證圖片質(zhì)量径缅,我們應該盡可能讓一個屏幕像素來渲染一個圖片像素,所以烙肺,針對不同DPR的屏幕纳猪,我們需要展示不同分辨率的圖片。
如:在dpr=2的屏幕上展示兩倍圖(@2x)桃笙,在dpr=3的屏幕上展示三倍圖(@3x)氏堤。
9.3 media查詢
使用media查詢判斷不同的設備像素比來顯示不同精度的圖片:
.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獲取設備像素比,遍歷所有圖片鼠锈,替換圖片地址:
const dpr = window.devicePixelRatio;
const images = document.querySelectorAll('img');
images.forEach((img)=>{
????img.src.replace(".", `@${dpr}x.`);
})
9.7 使用svg
SVG的全稱是可縮放矢量圖(Scalable Vector Graphics)闪檬。不同于位圖的基于像素,SVG?則是屬于對圖像的形狀描述购笆,所以它本質(zhì)上是文本文件粗悯,體積較小,且不管放大多少倍都不會失真同欠。
除了我們手動在代碼中繪制svg样傍,我們還可以像使用位圖一樣使用svg圖片:
<img src="conardLi.svg">
<img src="data:image/svg+xml;base64,[data]">
.avatar {
????background: url(conardLi.svg);
}