H5 - rem自适應方案
對于H5應用來說,為了更通用地滿足各機型螢幕的自适應布局要求,我們目前采用rem布局方案。
rem
rem是相對于根元素(html)字型大小的機關,它隻是一種相對機關。不同于另一個相對機關em,em是相對于父元素的字型大小,而rem則相對于根元素(html),與父元素的字型大小無關。
等比例适配所有螢幕
不論傳統的px絕對像素布局,還是流式布局、固定寬度和響應式做法,都有其缺陷,并不能完全做到自适應所有螢幕。
但是,rem方案可以比較容易地做到等比例适配所有螢幕,保證各螢幕的顯示效果與原始設計稿一緻。
我們看看rem是如何工作的。
舉個例子:
html{
font-size: 20px;
}
.btn {
width: 6rem;
height: 3rem;
line-height: 3rem;
font-size: 1.2rem;
display: inline-block;
background: #06c;
color: #fff;
border-radius: .5rem;
text-decoration: none;
text-align: center;
}
如果我們把html的font-size改改,再看看效果。
html{
font-size: 40px;
}
可以看到,按鈕的 width 、 height 、 font-size 和 border-radius 都被放大了一倍,我們隻需要改變 html 的 font-size ,就能改變按鈕在頁面上的顯示大小,而不必再去重設按鈕的樣式規則。
按鈕的大小 = 按鈕的rem * html.font-size
于是,利用這個特性,我們可以這樣實作等比例适配所有螢幕。
基準螢幕寬度
以設計稿寬度作為最理想的基準螢幕寬度,假設設計稿寬度是 750px ,那麼基準螢幕寬度就是 750px ,寬度大于 750px 的螢幕,等同于等比例放大了頁面,小于 750px 的螢幕,等同于等比例縮小了頁面。
把設計稿750px十等分一下,每等分= 75px ,我們可以把這個 75px 當做 1個rem機關 ,那麼 750px 寬度就等于是 10rem 。
設定html的 font-size: 75px ,即 1rem ,也就是 rem的基準px=75px 。
設計稿上的元素尺寸換算公式: 原始px值 / rem基準px ;例如 240px * 120px 的元素,其實就是 3.2rem * 1.6rem 。
适配任意螢幕
我們可以通過js來取得目前機型螢幕的寬度值,然後10等分得出目前螢幕1個rem應該代表的絕對像素值,再将這個 rem基準px 動态設定到 html.font-size ,核心代碼如下:
var docEl = document.documentElement;
var width = docEl.getBoundingClientRect().width;
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
我們還可以通過less預處理工具,編寫一個絕對px轉rem的函數,自動轉換px值,省卻我們手動計算rem的麻煩,核心代碼如下:
@design-width: 750px; // 設計稿寬度
@rem: @design-width / 10; // 10等分得到的rem基準px
.px2rem(@attr; @px) when (ispixel(@px)) {
@{attr}: unit(@px / @rem, rem);
}
// 處理非px值
.px2rem(@attr; @px) when (default()) {
@{attr}: @px;
}
.px2rem(@attr; @px1; @px2) when (ispixel(@px1)) and (ispixel(@px2)) {
@{attr}: unit(@px1 / @rem, rem) unit(@px2 / @rem, rem);
}
.px2rem(@attr; @px1; @px2) when (ispixel(@px1)) and not (ispixel(@px2)) {
@{attr}: unit(@px1 / @rem, rem) @px2;
}
.px2rem(@attr; @px1; @px2) when not (ispixel(@px1)) and (ispixel(@px2)) {
@{attr}: @px1 unit(@px2 / @rem, rem);
}
//
處理非px值
.px2rem(@attr; @px1; @px2) when (default()) {
@{attr}: @px1 @px2;
}
然後用法如:
.px2rem(width; 240px);
.px2rem(padding; 10px; 20px);
對于Retina高清螢幕的處理
對于2倍和3倍的高清螢幕,分别做2倍和3倍處理。
處理方法是:重設viewport,将viewport縮小指定倍數。
核心代碼如:
var dpr = 1;
var scale = 1;
// 僅ios考慮2/3倍方案,其他裝置下,仍舊使用1倍的方案
if (/i(Phone|Pod|Pad)/.test(navigator.userAgent)) {
var ratio = win.devicePixelRatio;
dpr = ratio >= 3 ? 3 : (ratio >= 2 ? 2 : 1);
} else {
dpr = 1;
}
scale = 1 / dpr;
docEl.setAttribute('data-dpr', dpr);
metaEl.setAttribute('content', 'initial-scale=' + scale + ', width=device-width, maximum-scale=' + scale + ', user-scalable=no');
字号不用rem
字号大小不推薦用rem作為機關,是以,字号仍舊使用px作為機關,并配合 data-dpr 自定義屬性來在普通屏和2/3倍高清屏設定不同的 font-size 。
高清屏的 font-size =設計稿的font-size,普通屏是設計稿font-size的一半。
處理方式是:設定 body 的 font-size ,此後頁面上所有元素的字号大小都是相對于body的 font-size ,而不是html的 font-size 。
核心代碼如:
var fontBase = 16; // 普通屏基準字号:16px,高清屏基準字号:16px * 2、16px * 3
if (doc.readyState === 'complete') {
document.body.style.fontSize = fontBase * dpr + 'px';
} else {
document.addEventListener('DOMContentLoaded', function(e) {
document.body.style.fontSize = fontBase * dpr + 'px';
}, false);
}
// 預設普通屏,設計稿字号的一半
h3 {
font-size: 18px;
}
// 高清屏:設計稿的字号
[data-dpr="2"] h3 {
font-size: 36px;
}
[data-dpr="3"] h3 {
font-size: 54px;
}
文本字号應該用rem嗎? 顯然,我們在iPhone3G和iPhone4的Retina屏下面,希望看到的文本字号是相同的。也就是說,我們不希望文本在Retina螢幕下變小,另外,我們希望在大屏手機上看到更多文本,以及,現在絕大多數的字型檔案都自帶一些點陣尺寸,通常是16px和24px,是以我們不希望出現13px和15px這樣的奇葩尺寸。 如此一來, rem并不适合用到段落文本上。是以在整個适配方案中,考慮文本還是使用px作為機關。隻不過使用[data-dpr]屬性來區分不同dpr下的文本字号大小。
關于css圖檔
css圖檔隻需要按設計稿輸出的高清圖即可,編寫樣式的時候, width 和 height 分别取圖檔真實寬和高的一半,再折算成rem機關,然後設定 background-size: contain 。
.icon {
width: .px2rem(image-width / 2);
height: .px2rem(image-height / 2);
background-image: url(image);
background-size: contain;
}
關于img标簽圖檔
如果img标簽加載的圖檔可以知道其寬高的話,那麼也可以按照上一節的方法來設定圖檔的高清顯示。
如果不能預知圖檔大小,則需要通過js動态計算,将計算得出的寬高按上一節的方法動态設到 style 上。
動态計算rem的函數是: FS.px2rem(px) 。
完整代碼
<html>
<head>
<meta name="viewport" content="initial-scale=1, width=device-width, maximum-scale=1, user-scalable=no" />
<script type="text/javascript">
var FS = {};
(function(win, FS) {
var doc = win.document;
var docEl = doc.documentElement;
var metaEl = doc.querySelector('meta[name="viewport"]');
var dpr = 1;
var scale = 1;
var fontBase = 16;
// 僅ios考慮2/3倍方案,其他裝置下,仍舊使用1倍的方案
if (/i(Phone|Pod|Pad)/.test(navigator.userAgent)) {
var ratio = win.devicePixelRatio;
dpr = ratio >= 3 ? 3 : (ratio >= 2 ? 2 : 1);
} else {
dpr = 1;
}
scale = 1 / dpr;
docEl.setAttribute('data-dpr', dpr);
metaEl.setAttribute('content', 'initial-scale=' + scale + ', width=device-width, maximum-scale=' + scale + ', user-scalable=no');
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
FS.rem = rem;
}
var tid = null;
win.addEventListener('resize', function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}, false);
if (doc.readyState === 'complete') {
doc.body.style.fontSize = fontBase * dpr + 'px';
} else {
doc.addEventListener('DOMContentLoaded', function(e) {
doc.body.style.fontSize = fontBase * dpr + 'px';
}, false);
}
refreshRem();
FS.dpr = dpr;
FS.rem2px = function(d) {
var val = parseFloat(d) * FS.rem;
if (typeof d === 'string' && d.match(/rem$/)) {
val += 'px';
}
return val;
}
FS.px2rem = function(d) {
var val = parseFloat(d) / FS.rem;
if (typeof d === 'string' && d.match(/px$/)) {
val += 'rem';
}
return val;
}
})(window, FS);
</script>
</head>
<body></body>
</html>