移動端常見適配方案

語言: CN / TW / HK

做移動端頁面有一段時間了,總結下工作中常用的幾種移動端適配方案。

基礎

網上已經有非常多的基礎知識總結,不再贅訴,詳情可以見

《關於移動端適配,你必須要知道的》

《不要再問我移動適配的問題了》

其中容易搞混的概念是 視口

<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 獲取 (根據 widthinitial-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 的字型大小:如果 htmlfont-size 為100px,那麼 1rem 就等於100px

現在我們假定:

750px 螢幕下 htmlfont-size 為100px,也就是 1rem 為100px,那麼 200px 寬度的 .box 元素,就應該寫成 2rem

.box {
  /* 750px螢幕下,200px */
  width: 2rem;
}

那麼現在:

375px 螢幕下,我們需要 .box 元素渲染成 100px

.box {
  width: 2rem;
}

由於 .box 的寬度仍然是 2rem ,因此,這時候我們就需要 1rem 為50px,也就是說,此時 htmlfont-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 的大小,防止螢幕太大時,頁面元素也縮放很大,但是缺點就是需要使用 JSVW方案 剛好相反,無需使用 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適配寫法

參考