導讀
移動端適配,是我們在開發中經常會遇到的,這裡面可能會遇到非常多的問題:
-
1px
問題 -
UI
圖完美適配方案 -
iPhoneX
適配方案 - 橫屏適配
- 高清屏圖片模糊問題
- ...
上面這些問題可能我們在開發中已經知道如何解決,但是問題產生的原理,以及解決方案的原理可能會模糊不清。在解決這些問題的過程中,我們往往會遇到非常多的概念:畫素、解析度、PPI
、DPI
、DP
、DIP
、DPR
、視口等等,你真的能分清這些概念的意義嗎?
本文將從移動端適配的基礎概念出發,探究移動端適配各種問題的解決方案和實現原理。
一、英寸
一般用英寸描述螢幕的物理大小,如電腦顯示器的17
、22
,手機顯示器的4.8
、5.7
等使用的單位都是英寸。
需要注意,上面的尺寸都是螢幕對角線的長度:
英寸(inch
,縮寫為in
)在荷蘭語中的本意是大拇指,一英寸就是指甲底部普通人拇指的寬度。
英寸和釐米的換算:1英寸 = 2.54 釐米
二、解析度
2.1 畫素
畫素即一個小方塊,它具有特定的位置和顏色。
圖片、電子螢幕(手機、電腦)就是由無數個具有特定顏色和特定位置的小方塊拼接而成。
畫素可以作為圖片或電子螢幕的最小組成單位。
下面我們使用sketch
開啟一張圖片:
將這些圖片放大即可看到這些畫素點:
通常我們所說的解析度有兩種,螢幕解析度和影像解析度。
2.2 螢幕解析度
螢幕解析度指一個螢幕具體由多少個畫素點組成。
下面是apple
的官網上對手機解析度的描述:
iPhone XS Max
和 iPhone SE
的解析度分別為2688 x 1242
和1136 x 640
。這表示手機分別在垂直和水平上所具有的畫素點數。
當然解析度高不代表螢幕就清晰,螢幕的清晰程度還與尺寸有關。
2.3 影像解析度
我們通常說的圖片解析度
其實是指圖片含有的畫素數
,比如一張圖片的解析度為800 x 400
。這表示圖片分別在垂直和水平上所具有的畫素點數為800
和400
。
同一尺寸的圖片,解析度越高,圖片越清晰。
2.4 PPI
PPI(Pixel Per Inch)
:每英寸包括的畫素數。
PPI
可以用於描述螢幕的清晰度以及一張圖片的質量。
使用PPI
描述圖片時,PPI
越高,圖片質量越高,使用PPI
描述螢幕時,PPI
越高,螢幕越清晰。
在上面描述手機解析度的圖片中,我們可以看到:iPhone XS Max
和 iPhone SE
的PPI
分別為458
和326
,這足以證明前者的螢幕更清晰。
由於手機尺寸為手機對角線的長度,我們通常使用如下的方法計算PPI
:
iPhone 6
的PPI
為
那它每英寸約含有326
個物理畫素點。
2.5 DPI
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
,即物理畫素和裝置獨立畫素的比值。
在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
<meta>
元素表示那些不能由其它HTML
元相關元素之一表示的任何後設資料資訊,它可以告訴瀏覽器如何解析頁面。
我們可以藉助<meta>
元素的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
相同:它包含元素的內邊距,但不包括邊框,外邊距或垂直滾動條。
五、1px問題
為了適配各種螢幕,我們寫程式碼時一般使用裝置獨立畫素來對頁面進行佈局。
而在裝置畫素比大於1
的螢幕上,我們寫的1px
實際上是被多個物理畫素渲染,這就會出現1px
在有些螢幕上看起來很粗的現象。
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
建立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
採用的方案。
當然,這樣做是要付出代價的,這意味著你頁面上所有的佈局都要按照物理畫素來寫。這顯然是不現實的,這時,我們可以藉助flexible
或vw、vh
來幫助我們進行適配。
六、移動端適配方案
儘管我們可以使用裝置獨立畫素來保證各個裝置在不同手機上顯示的效果類似,但這並不能保證它們顯示完全一致,我們需要一種方案來讓設計稿得到更完美的適配。
6.1 flexible方案
flexible
方案是阿里早期開源的一個移動端適配解決方案,引用flexible
後,我們在頁面上統一使用rem
來佈局。
它的核心程式碼非常簡單:
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit();
rem
是相對於html
節點的font-size
來做計算的。
我們通過設定document.documentElement.style.fontSize
就可以統一整個頁面的佈局標準。
上面的程式碼中,將html
節點的font-size
設定為頁面clientWidth
(佈局視口)的1/10
,即1rem
就等於頁面佈局視口的1/10
,這就意味著我們後面使用的rem
都是按照頁面比例來計算的。
這時,我們只需要將UI
出的圖轉換為rem
即可。
以iPhone6
為例:佈局視口為375px
,則1rem = 37.5px
,這時UI
給定一個元素的寬為75px
(裝置獨立畫素),我們只需要將它設定為75 / 37.5 = 2rem
。
當然,每個佈局都要計算非常繁瑣,我們可以藉助PostCSS
的px2rem
外掛來幫助我們完成這個過程。
下面的程式碼可以保證在頁面大小變化時,佈局可以自適應,當觸發了window
的resize
和pageShow
事件之後自動調整html
的fontSize
大小。
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
由於viewport
單位得到眾多瀏覽器的相容,上面這種方案現在已經被官方棄用:
lib-flexible這個過渡方案已經可以放棄使用,不管是現在的版本還是以前的版本,都存有一定的問題。建議大家開始使用viewport來替代此方案。
下面我們來看看現在最流行的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
外掛幫我們完成這個過程。寫程式碼時,我們只需要根據UI
給的設計圖寫px
單位即可。
當然,沒有一種方案是十全十美的,vw
同樣有一定的缺陷:
-
px
轉換成vw
不一定能完全整除,因此有一定的畫素差。 - 比如當容器使用
vw
,margin
採用px
時,很容易造成整體寬度超過100vw
,從而影響佈局效果。當然我們也是可以避免的,例如使用padding
代替margin
,結合calc()
函式使用等等...
七、適配iPhoneX
iPhoneX
的出現將手機的顏值帶上了一個新的高度,它取消了物理按鍵,改成了底部的小黑條,但是這樣的改動給開發者適配移動端又增加了難度。
7.1 安全區域
在iPhoneX
釋出後,許多廠商相繼推出了具有邊緣螢幕的手機。
這些手機和普通手機在外觀上無外乎做了三個改動:圓角(corners
)、劉海(sensor housing
)和小黑條(Home Indicator
)。為了適配這些手機,安全區域這個概念變誕生了:安全區域就是一個不受上面三個效果的可視視窗範圍。
為了保證頁面的顯示效果,我們必須把頁面限制在安全範圍內,但是不影響整體效果。
7.2 viewport-fit
viewport-fit
是專門為了適配iPhoneX
而誕生的一個屬性,它用於限制網頁如何在安全區域內進行展示。
contain
: 可視視窗完全包含網頁內容
cover
:網頁內容完全覆蓋可視視窗
預設情況下或者設定為auto
和contain
效果相同。
7.3 env、constant
我們需要將頂部和底部合理的擺放在安全區域內,iOS11
新增了兩個CSS
函式env、constant
,用於設定安全區域與邊界的距離。
函式內部可以是四個常量:
-
safe-area-inset-left
:安全區域距離左邊邊界距離 -
safe-area-inset-right
:安全區域距離右邊邊界距離 -
safe-area-inset-top
:安全區域距離頂部邊界距離 -
safe-area-inset-bottom
:安全區域距離底部邊界距離
注意:我們必須指定viweport-fit
後才能使用這兩個函式:
<meta name="viewport" content="viewport-fit=cover">
constant
在iOS < 11.2
的版本中生效,env
在iOS >= 11.2
的版本中生效,這意味著我們往往要同時設定他們,將頁面限制在安全區域內:
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 產生原因
我們平時使用的圖片大多數都屬於點陣圖(png、jpg...
),點陣圖由一個個畫素點構成的,每個畫素都具有特定的位置和顏色值:
理論上,點陣圖的每個畫素對應在螢幕上使用一個物理畫素來渲染,才能達到最佳的顯示效果。
而在dpr > 1
的螢幕上,點陣圖的一個畫素可能由多個物理畫素來渲染,然而這些物理畫素點並不能被準確的分配上對應點陣圖畫素的顏色,只能取近似值,所以相同的圖片在dpr > 1
的螢幕上就會模糊:
9.2 解決方案
為了保證圖片質量,我們應該儘可能讓一個螢幕畫素來渲染一個圖片畫素,所以,針對不同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
屬性,瀏覽器會自動根據畫素密度匹配最佳顯示圖片:
<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
則是屬於對影像的形狀描述,所以它本質上是文字檔案,體積較小,且不管放大多少倍都不會失真。
除了我們手動在程式碼中繪製svg
,我們還可以像使用點陣圖一樣使用svg
圖片:
<img src="conardLi.svg">
<img src="data:image/svg+xml;base64,[data]">
.avatar {
background: url(conardLi.svg);
}
參考
小結
希望你閱讀本篇文章後可以達到以下幾點:
- 理清移動端適配常用概念
- 理解移動端適配問題產生的原理,至少掌握一種解決方案
文中如有錯誤,歡迎在評論區指正,如果這篇文章幫助到了你,歡迎點贊和關注。
想閱讀更多優質文章、可關注我的github部落格,你的star✨、點贊和關注是我持續創作的動力!
推薦關注我的微信公眾號【code祕密花園】,每天推送高質量文章,我們一起交流成長。