天天看點

H5 - rem自适應方案

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>      

繼續閱讀