做移動端頁面有一段時間了,總結下工作中常用的幾種移動端適配方案。
基礎
網上已經有非常多的基礎知識總結,不再贅訴,詳情可以見
其中容易搞混的概念是視口
<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適配寫法