輕鬆掌握移動端web開發【尺寸適配】常用解決方案

芒僧發表於2018-04-13

keycode

本文主要針對初學移動端web開發的讀者,筆者也是初學者,文中有眾多用詞不當之處望讀者指正。

前言

從開始做web app開發到現在,一直對移動端的尺寸適配有一種模糊的概念。能說得上來‘媒體查詢’,‘柵格化佈局’,‘流式佈局’等若干技術名詞和實現方式,但是每次自己做web app開發的時候,做出來的產物總是不盡人意,比如在iPhone5上出現文字溢位,調整好的佈局位置在一些小尺寸手機上發現位置非常不對,或是遮蓋了其他元素,或是換行了。

如果是之前,我是這樣的做法:

不斷寫媒體查詢做相容,直到PM或者QA滿意為止。

這樣的方法,存在以下幾個問題:

  1. 難以適應所有的手機螢幕尺寸,總是會有不相容的尺寸出現,問題仍然存在,只是尚未被發現。
  2. 太累了,非常折磨人。特別是這些問題一般會集中湧現在上線前被一併提出來,而那個時候正好是壓力最大的時候。

我想了想,為什麼會出現治標不治本的情況:

  1. 在專案開始的時候沒和UI協調好規範。
  2. 身邊沒有太多的測試機,沒法測試得太全面。
  3. 沒有意識到移動端適配是一個棘手的問題。

那麼,有沒有那樣一種一勞永逸全尺寸支援不用動腦子算的移動端尺寸適配方案呢?

**答案當然是有的。**筆者結合了自己所看的幾篇熱門的部落格,總結了其中比較有用的幾個知識點,希望能讓讀者更快的掌握並使用這個'一勞永逸的方法'。能偷懶的事情絕對要偷懶。?(熱門部落格題目如圖,含flexible的github repo)

螢幕快照 2018-04-12 下午6.15.30

螢幕快照 2018-04-12 下午6.15.44

1523528305234

我們要達到的效果

  • 直接根據UI的標註視覺稿上面的尺寸進行開發。如標註的是230px, 通過函式將其轉為rem而不用人工計算。
  • 在大部分的手機機型上看起來的頁面視覺效果都一致。

什麼是rem

一句話概括: 假如<html>標籤上設定了樣式font-size:16px,那麼 1rem = 16px。 所以:

re

與UI的配合

首先,需要和UI小姐姐說一句話:

"標註元素的時候請按照750px * 1334px為準。"

那麼,你將會拿到一張如下的標註圖:

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

【核心】動態計算+rem

到這一步,我們仍然沒有解決核心問題:

  1. 要自己去將px換算成rem。(可能旁邊會放一個計算器)
  2. 全尺寸適配。

接下來,就是最為核心的環節了,筆者通過步驟圖向大家還原計算的過程。

第一步:假設有三款不同長寬的手機。

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

第二步:把手機的寬分為10份,那麼上述三款手機的每份寬度是35px/36px/37px。並且將<html>標籤新增不同的font-size設定。

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

即:一份分別為35px/36px/37px

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

第三步:根據UI的px標註圖計算出相應的rem:

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

第四步:rem將轉化成不同的px尺寸在不同的手機上呈現:(ps:圖中的除法結果算錯了)

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

通過這樣的方式,即可以在不同尺寸的手機上有相同的展示效果。而最cool的地方,是上述整個過程時自動適配的。開發者只需根據UI標註圖無腦寫就行了,再也不用擠眉弄眼地對著Chrome Devtools 瘋狂除錯了。

程式碼實現

把手機的寬分為10份,那麼上述三款手機的每份寬度是35px/36px/37px。並且將<html>標籤新增不同的font-size設定。

通過JavaScript動態計算出當前的螢幕寬度,切割為10份並將<html>fontSize設定為1份單位寬度

key-code

document.addEventListener('DOMContentLoaded', function(e) {
    document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px';
}, false);

複製程式碼

當初始的 HTML 文件被完全載入和解析完成之後,DOMContentLoaded 事件被觸發,而無需等待樣式表、影象和子框架的完成載入。另一個不同的事件 load 應該僅用於檢測一個完全載入的頁面。 MDN::DOMContentLoaded

根據UI的px標註圖計算出相應的rem

這一步需要使用Sass來定義一個px2rem的工具函式:

scssFunction

// utils.scss
@function px2rem($px){
    $rem : 75px; // '750/10':分成10份
    @return ($px/$rem) + rem;
}

// foo.scss

.box1 {
 	width: px2rem(320px); // '(320/750) * 10 = 4.266rem'
 }
複製程式碼

這樣,我們在styleSheet中實際生效的是height: 4.266rem,而1rem對應多少px是上述JavaScript程式碼根據不同的window.innerWidth提前計算好的。這樣就實現了自動適配。

如果你嫌寫 px2rem()也麻煩,那麼可以把函式名定義簡單一些。

繞不開的viewport和dpr

在寫這篇部落格的開始,我曾試圖繞開闡述viewportdpr這個抽象的概念,因為上述的內容已經可以從一個維度解決大多數問題了。但是,如果想做得更完美,就必須從另一個維度出發,而這個維度,就是dpr

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

首先,要區分兩個概念:

  1. 裝置的pixels
  2. css的pixels

有這樣一個場景

一位前端工程師敲出了

.box {
    width: 100px;
    height:100px;
}
複製程式碼

那麼此時,他的意思是box在我們的螢幕中佔的實際長寬是100px,在他腦中是這樣的畫面:

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

專案上線之後,有一個使用者'不懷好意'地使用了放大鏡功能將長寬放大了兩倍,現在就變成了:

輕鬆掌握移動端web開發【尺寸適配】常用解決方案

你會發現,裝置花了200px的長寬來渲染CSS裡面定義的100px的長寬,而裝置pixels和樣式pixels的比值,就是dpr,即Device Pixel Ratio,如果對這個概念仍有問題,請檢視viewport剖析

dp

我們大家都知道Retina屏(視網膜屏),之所以看起來這麼高清,就是因為蘋果裝置花兩個畫素來渲染一個畫素的物體,那麼看起來肯定更為精緻。

所以,如果我們針對dpr=1的書寫了rem2px(100px),那麼在dpr=2的裝置看起來將會是被放大了2倍的元素。

那麼,如果我們能夠查詢出當前裝置的dpr,並且做相應的縮放就可以解決這個問題。

舉個例子:某些安卓機的dpr=1,但是UI做標註圖的時候是根據dpr=2來做的,就像我們上文的750px * 1334px。直接按照750px * 1334px寫出來的元素將會被放大兩倍,那麼我們就使頁面縮小兩倍,如何控制呢?

用viewport

簡言之,在這裡我們使用viewport是為了控制螢幕的縮放。

viewport

var dpr = window.devicePixelRatio;
meta.setAttribute('content', 'initial-scale=' + 1/dpr + ', maximum-scale=' + 1/dpr + ', minimum-scale=' + 1/dpr + ', user-scalable=no'); 
// 幫助理解 如果dpr=2,說明寫的100px渲染成了200px,所以需要縮小至1/2,即1/dpr
複製程式碼

另外值得一提的是,UI一般會以750px * 1334px的標準進行設計,因為這樣使得設計稿更加精細。 比如我們寫了rem2px(375px),那麼會經過下列的過程換算到裝置pixels寬度為390px且dpr=3的手機。

  1. rem2px(375px) ----> 5rem
  2. 5rem -----> 195px (樣式pixels)
  3. 樣式195px ------> 此時看起來(指的就是裝置pixels)有195*3 = 585px的長度
  4. 設定dpr=1/3------->此時看起來只有195px

這樣,我們完成了從dpr維度的適配。

show me the code:

sourceCode

<script>
  var dpr = window.devicePixelRatio;
  var meta = document.createElement('meta');

  // dpr
  meta.setAttribute('content', 'initial-scale=' + 1/dpr + ', maximum-scale=' + 1/dpr + ', minimum-scale=' + 1/dpr + ', user-scalable=no'); 
  document.getElementsByTagName('head')[0].appendChild(meta);

  // rem
  document.addEventListener('DOMContentLoaded', function (e) {
    document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px';
  }, false);
</script>
複製程式碼

為了防止全域性變數汙染或者覆蓋他人的變數,請封裝成模組再使用。

One More Thing

在寫這篇部落格的過程中,曾糾結過這樣的問題:rem佈局和百分比佈局感覺差距不大啊,因為在寫rem的時候是基於把寬度切為10份後再寫的,就像是1rem = 10% = 10vw一樣。這讓我一度覺得可以用百分比佈局。後來發現,如果出現盒子巢狀(這種場景太多了),那麼百分比佈局就出現問題了,因為其百分比的參考系選擇的是父元素,所以我們如果在子盒子裡面定義10%的寬度,指的是針對父盒子的而不是我們想要的針對整個window.innerWidth10%。而vw的程式碼可維護性不如上述的這套方案,且相容性也沒有rem好(這一點差距不是太大)。

如果想了解更多關於PC端or移動端佈局,請看參考資源&鳴謝板塊。

參考資源&鳴謝

移動端頁面適配方案

使用Flexible實現手淘H5頁面的終端適配

六種佈局+rem佈局的簡介

DOMContentLoaded與load的區別

rem是如何實現的

AlloyTeam 移動web適配利器

viewport剖析

lib-flexible

相關文章