把為PC端設計的網頁展示到移動端瀏覽器上,也許第一個面臨的就是設備寬度不夠帶來的排版錯亂問題,為了讓頁面在不同尺寸的設備上都能合理排版顯示,我們需要做一些適配措施。
前置知識
1. 設備的一些物理屬性
- 屏幕尺寸:屏幕對角線的長度
- 設備像素:設備像素是物理概念,指的是屏幕的發光點
- 屏幕分辨率是指屏幕的像素點數。分辨率2400*1080指有2400*1080(= 2592000)個像素點
- PPI(Pixels Per Inch)是像素密度(Screen density)的單位,指每英寸的像素數量。屏幕尺寸和分辨率已知,(根據分辨率計算對角線上的像素數量 / 屏幕尺寸),就能計算出像素密度
2. 設備獨立像素(device-independent pixel,簡稱DIP或DP)
設備獨立像素是與設備無關的邏輯像素,【獨立于設備】用于【邏輯上衡量尺寸】的單位,這樣我們就【不需要關注設備的物理分辨率】了
我們在web開發布局時使用的px都是邏輯像素
當我們在布局時將一個元素設置為100px(不管屏幕分辨率和尺寸是多少)
—— 如果這個設備的視口寬度是100dp(也就是設備提供給你的這個視覺窗口內能顯示的邏輯px值是100),則這個元素顯示的寬度剛好充滿屏幕
—— 如果這個設備的視口寬度是200dp,則這個元素顯示的寬度為屏幕的一半從上面可以看出px是個相對單位,即使元素都設置為100px,顯示出的物理尺寸也可能是不同的。
3. DPR(設備像素比,devicePixelRatio )
我們布局時使用的是邏輯像素,設備顯示時需要將邏輯像素轉換為實際的物理像素。DPR就描述的是設備物理像素與邏輯像素之間的轉換關系:
設備像素比(DPR):window.devicePixelRatio = 物理像素 / 邏輯像素。
- 當像素比為1:1(DPR=1)時,即使用1個物理像素顯示1個CSS像素;
-
當像素比為2:1(DPR=2)時,使用4個物理像素顯示1個CSS像素;
dpr.png
<略>了解了物理像素、邏輯像素、DPR
之后,我們知道影響元素物理尺寸的是:元素所設置的px值、dpr、ppi:px值和dpr決定了顯示元素所使用的物理像素數量,再由ppi值(每英寸的像素數量)計算物理顯示尺寸
4. <略>PC端瀏覽器視口的邏輯寬度
瀏覽器視口的邏輯寬度與我們調整的顯示器分辨率和縮放值有關。上圖配置,瀏覽器全屏時視口寬度為
1920 / 1.5 = 1280dp
。如果我們將縮放調至100%,視口寬度為1920dp-
瀏覽器縮放,不斷放大時,元素顯示的物理尺寸變大,說明顯示1px所用的物理像素在變多,即dpr值在變大,而且視口的邏輯寬度在變小。下圖中可以看到這種改變:
dpr.gif
移動端適配1——viewport
1. 布局視口
layout viewport,這個視口就是HTML頁面布局的區域,開發者可以自定義它的寬高(主要是加寬),使得原本為PC端設計的頁面結構不會在移動設備上被破壞。用戶可以在視覺視口(visual viewport,手持設備屏幕的可視區域)中拖動或者縮放網頁,來獲得良好的瀏覽效果。使用viewport meta標簽,可以創建一個虛擬的布局視口
移動設備上的瀏覽器會給viewport設置默認的尺寸,常見寬度為980px和1024px。可以通過
document.documentElement.clientWidth
查看。-
因為移動設備的默認布局視口往往大于設備邏輯寬度,此時就會在橫向出現滾動條才能完整的容納頁面。例:在邏輯寬度為375px的設備上瀏覽網頁:
不設置viewport,默認寬度.gif
2. 理想視口
移動端瀏覽器為viewport設置默認尺寸雖然不會排版錯亂,但是有滾動條問題
理想視口,最理想的視口大小——用戶無需縮放或滾動就可以瀏覽橫向的全部內容。它只是一種概念,用于指導開發者設計最理想的頁面大小,它的寬度應為設備邏輯寬度。
移動端適配的第一步,將視口設置為理想視口,這時我們需要能自己定義視口大小:
3. 自定義viewport的尺寸
<meta name="viewport" content="">
- viewport 標簽只對移動端瀏覽器有效,對 PC 端瀏覽器是無效的
content支持的屬性 | 注 |
---|---|
width | 定義視口寬度,單位為px。值可以自定義或設為device-width,device-width表示設備邏輯寬度 |
height | 設置layout viewport 的高度 |
initial-scale | 定義初始頁面的縮放值,[0.0-10.0](chrome 105上測試小于0.25的視為0.25,大于5的視為5) |
minimum-scale | 定義用戶可縮小最小比例 |
maximum-scale | 定義用戶可放大最大比例 |
user-scalable | 定義是否允許用戶手動縮放頁面,yes/no,默認值yes |
-
<meta name="viewport" content="width=device-width">
將viewport寬度設置為設備邏輯寬度,達到理想視口:
image.png <meta name="viewport" content="initial-scale=1">
也能將viewport的寬度設置為設備邏輯寬度。因為縮放是相對于 ideal viewport來進行縮放的,當縮放值為1的時候,就得到了 ideal viewport了。-
為什么同時寫
width=device-width, initial-scale=1
image.png 同時設置這兩個屬性還有一個原因:如果設置了viewport寬度大于設備邏輯寬度,如果不設置initial-scale,照理來說會出現橫向滾動條,chrome devTools下模擬是有滾動條的,但是手機上測試沒有,它會自動計算一個縮放值,縮放至不出現橫向滾動條的狀態,導致頁面內容顯示很小,所以還是需要設置initial-scale來覆蓋這種默認行為,不要自動縮放,而是【讓它正常出現滾動條】。
視口寬度設置為800,沒有出現滾動條,而是自動縮放 |
對比width設置為device-width |
---|
- 如果同時設置initial-scale和width,而且有沖突?
則會取較大的值。
<略>例如當設備邏輯寬度為375px時,設置initial-scale為3.0,則viewport的寬度為375px / 3 = 125px;(initial-scale = 設備邏輯寬度 / viewport寬度
, initial-scale設置的越大,viewport的寬度就越小)。比如同時設置了width為400,則viewport的寬度為400px
將viewport設置到理想視口,使用戶不需要進行拖動和縮放,是在移動端瀏覽網頁獲得良好體驗的第一步
移動端適配2——媒體查詢
rem和vw的方式都是等比縮放元素,但是移動設備越來越大并不是為了讓元素越來越大,而是為了展示更多的內容。當設備尺寸差別過大可能布局設計已經不一樣了,不再是單純縮放,這時就需要使用 媒體查詢
@media not|only mediatype and (mediafeature and|or|not mediafeature) {
CSS-Code;
}
移動端適配3——相對單位之 rem
保持頁面設計結構不變,大屏時讓元素顯示的更大,小屏則尺寸小一點,是適配各種設備的一種方法,使用rem能便捷地達到這種效果
- rem也是相對單位,相對于根元素(html)的font-size值來計算,假如根元素的字號為20px,則1rem為20px
:root { font-size: 20px; }
1. 如何根據設計稿來使用rem?
- 先不考慮ui設計稿的整體尺寸,將設計稿上的1rem視為100px(因為100方便換算,不設置為10px的原因:chrome不支持小于12px的字號),也就是說設計稿上根節點font-size為100px
- 將設計稿上元素尺寸換算為rem:假如設計稿上某元素字體為20px,則換算為0.2rem,那么在開發時,任何設備,這個字體設置時都是0.2rem
- 當我們能把設計稿上的px都換算為rem,就只需要關注在各種設備上將根節點的字號設置為多少px
- 以我們移動端ui設計稿為例,寬度為750px,1rem視為100px。當設備邏輯寬度為375px,這個設備上1rem = 375 / (750 / 100) = 50px
-
需要js動態獲取當前設備邏輯寬度,計算根節點的font-size值
移動端.png
2. 是把設計稿上所有px都轉成rem嗎?
- rem只是我們工具箱中的一個,對于需要適配屏幕等比縮放的元素可以選用 rem 作為單位,不需要等比縮放的元素可以依舊選用其他單位。
- 這需要我們自己分辨,例如對font-size使用rem,對border使用px,對padding、margin、border-radius等使用em,聲明容器的寬度用百分比等。當然這都不是絕對的
3. 使用插件將px換算成rem
- 雖然將1rem視為100px已經便于換算,但畢竟還是多了一步計算,最好可以直接把設計稿上的px值拿來直接用!
- 我們移動端就使用了postcss-pxtorem插件,以1rem為100px為基準將代碼中的px值轉換成rem,這樣開發時就能直接用設計稿上的px值了。
配置.png
移動端適配4——相對單位之 vw
vw和vh是相對于viewport的單位,也能實現【設備寬度不同時,網頁元素寬高等比縮放】的效果
1vw = 1% viewport width
1vh = 1% viewport height
1. 換算
- 計算邏輯:假如設計稿寬度750px,那么30px則換算為
30 / 750 * 100 = 4vw
,不需要關注各種設備實際的vw是多少 - 插件:手動計算顯然過于麻煩,postcss-px-to-viewport 同樣可以自動將px轉換為vw(配置viewportWidth為設計稿的寬度),這樣就能在開發時直接使用設計稿上的px值了
2. 與rem的對比
- 如果使用rem:px → 換算成rem → 計算不同viewport下根節點的font-size
- 如果使用vw:px → 換算成vw
所以,使用vw比rem方便之處在于——這是個純css方案,不需要使用js去關注當前設備的vw值。
<略>3. 100vh有點方便
我們總會遇到這種場景:底部按鈕固定,上方表單區域滾動顯示:
:root {
--height: 98px;
}
.form {
height: calc(100vh - var(--height));
overflow: auto;
}
.btn {
height: var(--height);
}
.container {
height: 100vh;
display: flex;
flex-direction: column;
}
.form {
flex: 1;
overflow: auto;
}
.btn {
height: 98px;
}
<略>4. vw和vh混用可能會導致元素變形
- 以750x1334的設計稿為例,假如元素是一個30 x 30px的正方形
- 如果網頁的vw和vh分別是
375px x 667px
(iPhone6的理想視口),則此元素寬為30 / 375 * 100 = 8vw
,高為30 / 667 * 100 ≈ 4.498vh
- 將元素寬高設置為8vw和4.498vh
- 如果網頁的vw和vh分別是
414px x 896px
(iPhone XR的理想視口),按照8vw和4.498vh計算,這個元素的尺寸為33.12 x 40.3px,就不是一個正方形了,也就是會發生變形,所以建議不要混用vw和vh
5. 兼容性
最后
1. 微信小程序的rpx
微信小程序規定所有設備上邏輯寬度都是750rpx。設計師只要按照iPhon6的750*1334進行設計,開發者可以將設計稿中的px以1:1直接替換為rpx,不再需要自己考慮自適應的問題
- 使用rem,是開發者根據設備邏輯寬度和設計稿寬度算出根節點的font-size值,即1rem為多少px作為基準
- 使用rpx,是小程序根據設備邏輯寬度和自己規定的750rpx算出1rpx為多少px作為基準,省去了我們自己去計算基準值的步驟。
2. 移動端1px問題
當設計師要求邊框寬度為1px(750x1334px的設計稿),開發時設置為0.01rem或者1px時,那么高dpr設備中看這個邊框,會比在設計稿中看起來要粗。
<略>這里所說的border更粗,是分別看設計稿和設備上的border和其他元素的比例,而不是去比較2個不同dpr的設備上顯示1px border的物理尺寸
<略>不同的瀏覽器處理小于1px的方式不同,有些采用四舍五入,有些大于某個值展示1px否則就不展示,有些只是線條的顏色變淺了,從視覺上看就變細了
- 邊框設置為0.01rem,在邏輯寬度為375px的設備上計算為0.5px(根元素字號50px),如果設備將0.5四舍五入為1px
- 或者由于設備可能不支持小于1px的值而直接設置border-width: 1px
也就是說,設計師要求的1px是使用1排物理像素點的寬度,而開發時設置的1px是使用1排邏輯像素,而在高dpr設備中,顯示1px會使用多個物理像素點,實際上我們使用了多排物理像素來顯示這1px
1.1 媒體查詢
在高dpr設備中再使用小于1的值
.border {
border: 1px solid #fff;
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.border {
border-width: 0.5px;
}
}
但這種方法只在ios上支持,安卓上小于1px的可能會四舍五入或當做0處理。所以出現了第二種方式:
1.2 偽元素模擬邊框 + scale縮放
為偽元素設置1px的邊框,在此基礎上再利用transform: scale()縮小,避免直接使用小于1px值時各設備兼容性處理不同。
.border { position: relative; }
.border::after {
content: "";
position: absolute;
top: 0;
left: 0;
/*---------注意這里----------*/
box-sizing: border-box;
width: 200%; /* 因為之后要縮小到0.5 所以初始尺寸先放大 */
height: 200%;
border: 1px solid #000;
transform: scale(0.5);
transform-origin: top left;
/*--------------------------*/
}
dpr為1的設備其實只需要正常使用1px,不需要縮放;dpr為2時scale(0.5),dpr為3時應scale(0.3333)。可以配合媒體查詢在不同dpr時設置不同的縮放值
:root {
--borderScale: 1;
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
:root {
--borderScale: 0.5;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 3) {
:root {
--borderScale: 0.3333;
}
}
.border { position: relative; }
.border::after {
/* ... */
transform: scale(var(--borderScale));
/* ... */
}
這種方法的缺點是對于已有偽元素的元素需要多層嵌套。
在uView框架中也使用了縮放來處理線條:
3. 小數問題
根據rem或vw去計算時,都會出現小數點問題,插件配置時給我們提供了小數位精度的選項,但如果需要展示小于1物理像素的部分,是無法精確處理的
4. 劉海屏
5. 1px → 0.667px
開發過程中遇見設置border為1px而實際computed style為0.667px,后來發現是顯示器分辨率導致的,但只有border屬性有這個問題
- image.png
|
- image.png
|
---|