移動端常見適配方案
做移動端頁面有一段時間了,總結下工作中常用的幾種移動端適配方案。
基礎
網上已經有非常多的基礎知識總結,不再贅訴,詳情可以見
其中容易搞混的概念是 視口
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,viewport-fit=cover">
meta
標籤中的 viewport
屬性,就是 檢視
的含義
視口分為
- 佈局視口
- 視覺視口
- 理想視口
佈局視口
也就是 <meta name="viewport" content="width=device-width">
中 width
屬性的含義
我們在css中寫的所有樣式,就是相對於 佈局視口
進行佈局的
預設情況下,移動端的佈局視口並不是螢幕寬度,而是一般在768px ~ 1024px間(大部分情況下980px)
可以通過 document.documentElement.clientWidth
獲取 (根據 width
和 initial-scale
來確定)
視覺視口
視覺視口是指使用者通過裝置螢幕看到的區域,預設等於當前瀏覽器的視窗大小(當 initial-scale
為1)
當用戶對瀏覽器進行縮放時,不會改變佈局視口的大小,所以頁面佈局是不變的,但是 縮放會改變覺視口的大小
可以通過 window.innerWidth
獲取 (會隨著縮放進行改變)
放大頁面,此時 window.innerWidth
反而減小 (頁面放大,你看到的東西也變少了)
理想視口
理想視口是指網站在移動裝置中的理想大小,這個大小就是裝置的螢幕大小
也就是 <meta name="viewport" content="width=device-width">
中 device-width
的含義
可以通過 screen.width
獲取 (常量,不會改變)
initial-scale
<meta name="viewport" content="width=device-width, initial-scale=0.5">
根據公式 initial-scale = 理想視口寬度 / 視覺視口寬度
假設理想視口寬度為 414px
(device-width),此時設定 initial-scale
為0.5,那麼視覺視口寬度就是 414 / 0.5 = 818
如果這時你獲取 document.documentElement.clientWidth
(佈局視口)的值,會發現不是 414px
而是 818px
結論: 佈局視口寬度取的是width和視覺視口寬度的最大值
思考題:
<meta name="viewport" content="width=600, initial-scale=2">
假設理想視口寬度為 414px
(device-width),此時 document.documentElement.clientWidth
(佈局視口)的值是多少?
視覺視口 = 414 / 2 = 207 佈局視口 = Math.max(207, 600) 佈局視口 = 600
總結
-
document.documentElement.clientWidth
: 佈局視口,css中一般寫成width=device-width
-
window.innerWidth
: 視覺視口,頁面縮放都會實時改變該值 -
screen.width
: 理想視口,頁面螢幕大小(裝置獨立畫素),也就是css中的device-width
常見適配方案
簡單一句話概括:移動端適配就是在進行 螢幕寬度
的 等比例縮放
:
平時我們開發中,拿到的移動端設計稿一般是 750 * 1334
尺寸大小( iPhone6 的裝置畫素為標準的設計圖)。那如果在 750px
設計稿上量出的元素寬度為 100px
,那麼在 375px
寬度的螢幕下,這個元素寬度就應該等比例縮放成 50px
。
所以適配的難點是:如果實現頁面的等比例縮放?
Rem 方案
該方案的核心就是:所有需要動態佈局的元素,不再使用 px
固定尺寸,而是採用 rem
相對尺寸
rem
的大小是相對於根元素 html
的字型大小:如果 html
的 font-size
為100px,那麼 1rem
就等於100px
現在我們假定:
750px
螢幕下 html
的 font-size
為100px,也就是 1rem
為100px,那麼 200px
寬度的 .box
元素,就應該寫成 2rem
.box { /* 750px螢幕下,200px */ width: 2rem; }
那麼現在:
375px
螢幕下,我們需要 .box
元素渲染成 100px
.box { width: 2rem; }
由於 .box
的寬度仍然是 2rem
,因此,這時候我們就需要 1rem
為50px,也就是說,此時 html
的 font-size
為50px
於是此時,我們可以得出一個公式:
(750) / (100) = (當前螢幕尺寸) / (當前螢幕1rem)
把這個公式進行一次數學轉換就能得到:
(當前螢幕1rem) = 100 * (當前螢幕尺寸) / 750
翻譯成js語言就是
document.documentElement.style.fontSize = 100 * (document.documentElement.clientWidth) / 750 + 'px';
將程式碼優化一下
const PAGE_WIDTH = 750; // 設計稿的寬度 const PAGE_FONT_SIZE = 100;// 設計稿1rem的大小 const setView = () => { //設定html標籤的fontSize document.documentElement.style.fontSize = PAGE_FONT_SIZE * (document.documentElement.clientWidth) / PAGE_WIDTH + 'px'; } window.onresize = setView; // 如果視窗大小發生改變,就觸發 setView 事件 setView()
考慮到Andorid端字型渲染的問題以及頁面大小變化的監聽,最終的程式碼如下:
(function () { var timer = null; var PAGE_WIDTH = 750; // 設計稿的寬度 var PAGE_FONT_SIZE = 100;// 設計稿1rem的大小 function onResize() { var e = PAGE_FONT_SIZE * document.documentElement.clientWidth / PAGE_WIDTH; document.documentElement.style.fontSize = e + 'px'; // 二次計算縮放畫素,解決移動端webkit字型縮放bug var realitySize = parseFloat(window.getComputedStyle(document.documentElement).fontSize); if (e !== realitySize) { e = e * e / realitySize; document.documentElement.style.fontSize = e + 'px'; } } window.addEventListener('resize', function () { if (timer) clearTimeout(timer); timer = setTimeout(onResize, 100); }); onResize(); })();
注意的是:我們取 100px
作為設計稿的1rem,是因為方便計算,比如設計稿上量出 250px
,我們就可以很容易的計算出為 2.5rem
。
我們當然也可以把 50px
作為設計稿的1rem,這時設計稿上的 250px
,就要寫成 5rem
。
其實我們也可以藉助於postcss-pxtorem或者 SCSS
函式來幫我們自動轉換單位
@function px2rem($px) { // 根元素字型為100px @return $px / 100 * 1rem; } .box { width: px2rem(200); }
通過Rem方案,需要動態縮放的元素,我們使用 rem
相對單位,不需要縮放的元素,我們仍然可以使用 px
固定單位。
不過在大屏裝置下(例如ipad或者pc端),由於我們的頁面是等比例縮放,這時候頁面的元素會被放大很多(螢幕寬度大,導致根元素字型1rem也變大)。但是在大屏下,我們真正希望的是使用者看到更多的內容,這時候我們可以使用媒體查詢的方式來限制根元素的字型,從而防止在大屏下元素過大的問題。
@media screen and (min-width: 450px) { html { font-size: 50px !important; } }
或者修改js指令碼的邏輯
const PAGE_WIDTH = 750; // 設計稿的寬度 let PAGE_FONT_SIZE = 100;// 設計稿1rem的大小 const setView = () => { if (document.documentElement.clientWidth > 450) { // 大屏下減小根元素字型 PAGE_FONT_SIZE = 50; } document.documentElement.style.fontSize = PAGE_FONT_SIZE * (document.documentElement.clientWidth) / PAGE_WIDTH + 'px'; }
VW 方案
vw 是相對單位,1vw 表示螢幕寬度的 1%
其實我們的 REM方案
就是 VW方案
的模擬,之前我們有一個公式:
(750) / (100) = (當前螢幕尺寸) / (當前螢幕1rem)
換一個轉換方式:
(當前螢幕1rem) = (當前螢幕尺寸) / 7.5
而 vw 單位其實就是:
(當前螢幕1vw) = (當前螢幕尺寸) / 100
因此, REM方案
就是用 JS 把螢幕寬度分成了7.5份,而 CSS3 中新增的 vw
單位,原生實現了把螢幕寬度分成了100份
所以,在 VW方案
中,我們不再需要使用JS指令碼了!
750px
設計稿中, 1vw
等於 7.5px
(750 / 100),因此,在設計稿中,量出 200px
的寬度,就因為寫成 26.667vw
(200 / 7.5)
.box { /* 750px螢幕下,200px */ width: 26.667vw; }
不過使用 vw
換算,並不像 rem
那麼方便,這時候我們可以藉助 postcss-px-to-viewport
或者 SCSS
函式來幫我們自動轉換單位
@function px2vw($px) { @return $px / 750 * 100vw; } .box { width: px2vw(200); }
同樣,在大屏裝置下,由於螢幕寬度大,所以頁面的元素同樣會放大很多(螢幕寬度大,1vw也很大)。但是由於 vw
是相對螢幕寬度的,所以我們不能像 REM方案
中一樣,手動控制 html
的根字型大小,這也是使用 VW方案
的一個缺點。
REM + VW 方案
REM方案
的優勢是可以手動控制 rem
的大小,防止螢幕太大時,頁面元素也縮放很大,但是缺點就是需要使用 JS
。 VW方案
剛好相反,無需使用 JS
但是無法手動控制 vw
的大小。
其實我們可以把兩者結合:
html { /* 750px 的設計圖,1rem = 100px */ font-size: calc(100 * 100vw / 750); } .box { /* 750px螢幕下,200px */ width: 2rem; }
對於佈局元素,我們仍然使用 rem
單位。但是對於根元素的字型大小,我們不需要使用JS來動態計算了
100 * (document.documentElement.clientWidth) / 750
這段js可以直接使用css來實現
calc(100 * 100vw / 750)
對於大屏裝置,我們使用媒體查詢
@media screen and (min-width: 450px) { html { font-size: calc(50 * 100vw / 750); } }
更詳細的 vw+rem佈局方案
可以見 《基於vw等viewport視區單位配合rem響應式排版和佈局》
viewport 縮放方案
還有一種更簡單粗暴的方法,就是我們設定 initial-scale
我們的佈局完全基於設計稿 750px
,佈局元素單位也使用 px
固定單位 (佈局視口寫死750px)
對於 375px
寬度,我們就將整個頁面縮放 0.5
:
<meta name="viewport" content="width=750, initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5, user-scalable=0">
<head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>demo</title> <script> var clientWidth = document.documentElement.clientWidth; var viewport = document.querySelector('meta[name="viewport"]'); var viewportWidth = 750; var viewportScale = clientWidth / viewportWidth; viewport.setAttribute('content', 'width=' + viewportWidth + ', initial-scale=' + viewportScale + ', minimum-scale=' + viewportScale + ', maximum-scale=' + viewportScale + ', user-scalable=0'); </script> </head>
.box { width: 200px; }
此方案的缺點: 整個頁面都被縮放了,對於不想縮放的元素無法控制。
市面上一些營銷H5頁面,由於是通過後臺視覺化拖拽搭建出來的,為了適配各種尺寸的螢幕,該方案是成本最低的實現(易企秀就是使用這種方案)
實戰
我們拿線上B站的會員購作為示例
請使用chrome開發者工具模擬移動端裝置檢視
原始碼直接右鍵檢視即可,程式碼沒有經過壓縮,可以很直觀的看到各種方案的css適配寫法
參考
- Dubbo學習筆記(一)基本概念與簡單使用
- 為什麼一定要從DevOps走向BizDevOps?
- Vue3響應式原始碼分析 - ref ReactiveEffect篇
- 2022 年程式語言趨勢:Swift、Kotlin 熱度持續增長,收入最高的 5 種語言竟是它們
- 雲音樂FeatureStore建設與實踐
- Vue專案之使用EditorConfig, Eslint和Prettier實現程式碼規範
- 如何使用 DataAnt 監控 Apache APISIX
- 基於 Nebula Graph 構建百億關係知識圖譜實踐
- 元宇宙 3D 開荒場 - 探味奇遇記
- 摺疊面板元件的設計與實現
- web技術分享| 【高德地圖】實現自定義的軌跡回放
- Vue3中的teleport節點傳送
- Flutter 常見異常分析
- React Native如何做線上錯誤與效能監控
- shell指令碼程式設計學習筆記——變數
- Object.prototype.toString.call()的原理
- 玩轉 AbortController 控制器
- Go十大常見錯誤第2篇:benchmark效能測試的坑
- 技術分享 | dbslower 工具學習之探針使用
- TypeScript 中令人迷惑的物件型別:Object、{}和 object