本文主要針對初學移動端web開發的讀者,筆者也是初學者,文中有眾多用詞不當之處望讀者指正。
前言
從開始做web app開發到現在,一直對移動端的尺寸適配有一種模糊的概念。能說得上來‘媒體查詢’,‘柵格化佈局’,‘流式佈局’等若干技術名詞和實現方式,但是每次自己做web app開發的時候,做出來的產物總是不盡人意,比如在iPhone5
上出現文字溢位,調整好的佈局位置在一些小尺寸手機上發現位置非常不對,或是遮蓋了其他元素,或是換行了。
如果是之前,我是這樣的做法:
不斷寫媒體查詢做相容,直到PM或者QA滿意為止。
這樣的方法,存在以下幾個問題:
- 難以適應所有的手機螢幕尺寸,總是會有不相容的尺寸出現,問題仍然存在,只是尚未被發現。
- 太累了,非常折磨人。特別是這些問題一般會集中湧現在上線前被一併提出來,而那個時候正好是壓力最大的時候。
我想了想,為什麼會出現治標不治本的情況:
- 在專案開始的時候沒和UI協調好規範。
- 身邊沒有太多的測試機,沒法測試得太全面。
- 沒有意識到移動端適配是一個棘手的問題。
那麼,有沒有那樣一種一勞永逸
,全尺寸支援
,不用動腦子算
的移動端尺寸適配方案呢?
**答案當然是有的。**筆者結合了自己所看的幾篇熱門的部落格,總結了其中比較有用的幾個知識點,希望能讓讀者更快的掌握並使用這個'一勞永逸的方法'。能偷懶的事情絕對要偷懶。?(熱門部落格題目如圖,含flexible的github repo)
我們要達到的效果
- 直接根據UI的標註視覺稿上面的尺寸進行開發。如標註的是
230px
, 通過函式將其轉為rem
而不用人工計算。 - 在大部分的手機機型上看起來的頁面視覺效果都一致。
什麼是rem
一句話概括: 假如<html>
標籤上設定了樣式font-size:16px
,那麼 1rem = 16px
。 所以:
與UI的配合
首先,需要和UI小姐姐說一句話:
"標註元素的時候請按照
750px * 1334px
為準。"
那麼,你將會拿到一張如下的標註圖:
【核心】動態計算+rem
到這一步,我們仍然沒有解決核心問題:
- 要自己去將px換算成rem。(可能旁邊會放一個計算器)
- 全尺寸適配。
接下來,就是最為核心的環節了,筆者通過步驟圖向大家還原計算的過程。
第一步:假設有三款不同長寬的手機。
第二步:把手機的寬分為10份,那麼上述三款手機的每份寬度是35px/36px/37px。並且將<html>
標籤新增不同的font-size
設定。
即:一份分別為35px/36px/37px
第三步:根據UI的px標註圖計算出相應的rem:
第四步:rem將轉化成不同的px尺寸在不同的手機上呈現:(ps:圖中的除法結果算錯了)
通過這樣的方式,即可以在不同尺寸的手機上有相同的展示效果。而最cool的地方,是上述整個過程時自動適配的。開發者只需根據UI標註圖無腦寫就行了,再也不用擠眉弄眼地對著Chrome Devtools 瘋狂除錯了。
程式碼實現
把手機的寬分為10份,那麼上述三款手機的每份寬度是35px/36px/37px。並且將
<html>
標籤新增不同的font-size
設定。
通過JavaScript動態計算出當前的螢幕寬度,切割為10份並將<html>
的fontSize
設定為1份單位寬度
。
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
的工具函式:
// 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
在寫這篇部落格的開始,我曾試圖繞開闡述viewport
和dpr
這個抽象的概念,因為上述的內容已經可以從一個維度解決大多數問題了。但是,如果想做得更完美,就必須從另一個維度出發,而這個維度,就是dpr。
首先,要區分兩個概念:
- 裝置的pixels
- css的pixels
有這樣一個場景
一位前端工程師敲出了
.box {
width: 100px;
height:100px;
}
複製程式碼
那麼此時,他的意思是box
在我們的螢幕中佔的實際長寬是100px
,在他腦中是這樣的畫面:
專案上線之後,有一個使用者'不懷好意'地使用了放大鏡功能將長寬放大了兩倍,現在就變成了:
你會發現,裝置花了200px的長寬來渲染CSS裡面定義的100px的長寬,而裝置pixels和樣式pixels的比值,就是dpr,即Device Pixel Ratio,如果對這個概念仍有問題,請檢視viewport剖析。
我們大家都知道Retina屏(視網膜屏),之所以看起來這麼高清,就是因為蘋果裝置花兩個畫素來渲染一個畫素的物體,那麼看起來肯定更為精緻。
所以,如果我們針對dpr=1
的書寫了rem2px(100px)
,那麼在dpr=2
的裝置看起來將會是被放大了2倍的元素。
那麼,如果我們能夠查詢出當前裝置的dpr,並且做相應的縮放就可以解決這個問題。
舉個例子:某些安卓機的dpr=1
,但是UI做標註圖的時候是根據dpr=2
來做的,就像我們上文的750px * 1334px
。直接按照750px * 1334px
寫出來的元素將會被放大兩倍,那麼我們就使頁面縮小兩倍,如何控制呢?
用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的手機。
rem2px(375px)
---->5rem
5rem
----->195px (樣式pixels)
樣式195px
------> 此時看起來(指的就是裝置pixels)有195*3 = 585px
的長度設定dpr=1/3
------->此時看起來只有195px
這樣,我們完成了從dpr
維度的適配。
show me the code:
<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.innerWidth
的10%
。而vw
的程式碼可維護性不如上述的這套方案,且相容性也沒有rem
好(這一點差距不是太大)。
如果想了解更多關於PC端or移動端佈局,請看參考資源&鳴謝
板塊。