移動端 rem 佈局的一些總結
【資源一】基礎知識恕不回顧
基礎知識參考以下兩篇部落格:
【資源二】淘寶m站首頁的動態實現
最近讀到@大漠 的新文章《使用Flexible實現手淘H5頁面的終端適配》,和本部分有點關係。暫且加上來以供參考。(updated 2015-11-24)
原始碼進行美化、解讀之後,基本佈局部分的程式碼已經被我還原出來了:(2016-01-13補充:後來才發現,早就開源在github上了)
!function(win, lib) {
var timer,
doc = win.document,
docElem = doc.documentElement,
vpMeta = doc.querySelector('meta[name="viewport"]' ),
flexMeta = doc.querySelector('meta[name="flexible"]'),
dpr = 0,
scale = 0,
flexible = lib.flexible || (lib.flexible = {});
// 設定了 viewport meta
if (vpMeta) {
console.warn("將根據已有的meta標籤來設定縮放比例");
var initial = vpMeta.getAttribute("content" ).match(/initial\-scale=([\d\.]+)/);
if (initial) {
scale = parseFloat(initial[1]); // 已設定的 initialScale
dpr = parseInt(1 / scale); // 裝置畫素比 devicePixelRatio
}
}
// 設定了 flexible Meta
else if (flexMeta) {
var flexMetaContent = flexMeta.getAttribute("content" );
if (flexMetaContent) {
var initial = flexMetaContent.match(/initial\-dpr=([\d\.]+)/),
maximum = flexMetaContent.match(/maximum\-dpr=([\d\.]+)/);
if (initial) {
dpr = parseFloat(initial[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximum) {
dpr = parseFloat(maximum[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
// viewport 或 flexible
// meta 均未設定
if (!dpr && !scale) {
// QST
// 這裡的 第一句有什麼用 ?
// 和 Android 有毛關係 ?
var u = (win.navigator.appVersion.match(/android/gi), win.navigator.appVersion.match(/iphone/gi)),
_dpr = win.devicePixelRatio;
// 所以這裡似乎是將所有 Android 裝置都設定為 1 了
dpr = u ? ( (_dpr >= 3 && (!dpr || dpr >= 3))
? 3
: (_dpr >= 2 && (!dpr || dpr >= 2))
? 2
: 1
)
: 1;
scale = 1 / dpr;
}
docElem.setAttribute("data-dpr", dpr);
// 插入 viewport meta
if (!vpMeta) {
vpMeta = doc.createElement("meta");
vpMeta.setAttribute("name", "viewport");
vpMeta.setAttribute("content",
"initial-scale=" + scale + ", maximum-scale=" + scale + ", minimum-scale=" + scale + ", user-scalable=no");
if (docElem.firstElementChild) {
docElem.firstElementChild.appendChild(vpMeta)
} else {
var div = doc.createElement("div");
div.appendChild(vpMeta);
doc.write(div.innerHTML);
}
}
function setFontSize() {
var winWidth = docElem.getBoundingClientRect().width;
if (winWidth / dpr > 540) {
(winWidth = 540 * dpr);
}
// 根節點 fontSize 根據寬度決定
var baseSize = winWidth / 10;
docElem.style.fontSize = baseSize + "px";
flexible.rem = win.rem = baseSize;
}
// 調整視窗時重置
win.addEventListener("resize", function() {
clearTimeout(timer);
timer = setTimeout(setFontSize, 300);
}, false);
// 這一段是我自己加的
// orientationchange 時也需要重算下吧
win.addEventListener("orientationchange", function() {
clearTimeout(timer);
timer = setTimeout(setFontSize, 300);
}, false);
// pageshow
// keyword: 倒退 快取相關
win.addEventListener("pageshow", function(e) {
if (e.persisted) {
clearTimeout(timer);
timer = setTimeout(setFontSize, 300);
}
}, false);
// 設定基準字型
if ("complete" === doc.readyState) {
doc.body.style.fontSize = 12 * dpr + "px";
} else {
doc.addEventListener("DOMContentLoaded", function() {
doc.body.style.fontSize = 12 * dpr + "px";
}, false);
}
setFontSize();
flexible.dpr = win.dpr = dpr;
flexible.refreshRem = setFontSize;
flexible.rem2px = function(d) {
var c = parseFloat(d) * this.rem;
if ("string" == typeof d && d.match(/rem$/)) {
c += "px";
}
return c;
};
flexible.px2rem = function(d) {
var c = parseFloat(d) / this.rem;
if ("string" == typeof d && d.match(/px$/)) {
c += "rem";
}
return c;
}
}(window, window.lib || (window.lib = {}));
注意:
淘寶首頁在iPhone4上設定的initial-scale是0.5(其他尺寸類似)。
因此,這句在iPhone4上得出的結果是640:
var winWidth = docElem.getBoundingClientRect().width;
正是因為淘寶這種獨特的設定,使得 ios 上 1px邊框的問題完美解決(1px變2px, 又被 initial-scale=0.5
縮小了一半)。
【資源三】常規情況下js根據螢幕寬度動態計算
使用js動態計算:
!(function(doc, win) {
var docEle = doc.documentElement,
evt = "onorientationchange" in window ? "orientationchange" : "resize",
fn = function() {
var width = docEle.clientWidth;
width && (docEle.style.fontSize = 20 * (width / 320) + "px");
};
win.addEventListener(evt, fn, false);
doc.addEventListener("DOMContentLoaded", fn, false);
}(document, window));
【資源四】媒體查詢較密集的斷點
使用css3 media query 實現
@media screen and (min-width: 320px) {
html {font-size: 14px;}
}
@media screen and (min-width: 360px) {
html {font-size: 16px;}
}
@media screen and (min-width: 400px) {
html {font-size: 18px;}
}
@media screen and (min-width: 440px) {
html {font-size: 20px;}
}
@media screen and (min-width: 480px) {
html {font-size: 22px;}
}
@media screen and (min-width: 640px) {
html {font-size: 28px;}
}
【資源五】強大的單位——vw
使用單位 vw 實現動態計算。
html {
font-size: 31.25vw; /* 表示式:100*100vw/320 */
}
不過考慮到國內相容性的問題,還是結合媒體查詢來使用比較好。(媒體查詢的斷點暫時是借用上面的例子)
@media screen and (min-width: 320px) {
html {
font-size: 100px;
}
}
@media screen and (min-width: 360px) {
html {
font-size: 112.5px;
}
}
@media screen and (min-width: 400px) {
html {
font-size: 125px;
}
}
@media screen and (min-width: 440px) {
html {
font-size: 137.5px;
}
}
@media screen and (min-width: 480px) {
html {
font-size: 150px;
}
}
@media screen and (min-width: 640px) {
html {
font-size: 200px;
}
}
html {
font-size: 31.25vw;
}
【總結】
對以上種種方法的綜合:
1、meta:viewport, 還是initial-scale為 1;
2、320px螢幕下,把頁面根元素html的字型大小設定為50px;
3、鑑於我們拿到的設計圖目前是640px寬的基準,這樣我們就不用每次自己除以2了,直接在PS中量就好;
4、寬度什麼的最好還是用百分比處理;涉及到高度、字型大小之類的則用rem。
eg:
設計稿上,div高度為40px;那麼css就是 div {height: 0.4rem;}
結果就只剩下一步轉換:設計稿上量的長度轉化為小數。 50% => 0.5
這種計算,不要太簡單。。。
【方法一】純粹css,支援calc函式的動態計算;不支援的用css媒體查詢斷點,優雅降級。
@media screen and (min-width: 320px) {
html {
font-size: 50px;
}
}
@media screen and (min-width: 360px) {
html {
font-size: 56px;
}
}
@media screen and (min-width: 400px) {
html {
font-size: 63px;
}
}
@media screen and (min-width: 440px) {
html {
font-size: 69px;
}
}
@media screen and (min-width: 480px) {
html {
font-size: 75px;
}
}
/**
* 2016-01-13 訂正
* 做適當限制
* 大於640的螢幕 固定為100px
* 同時需要對body或者最外層wrapper做max-width: 640px的限制
*/
/*
@media screen and (min-width: 640px) {
html {
font-size: 100px;
}
}
html {
font-size: 15.625vw;
}
*/
html {
font-size: 15.625vw;
}
@media screen and (min-width: 640px) {
html {
font-size: 100px;
}
}
【方法二】指令碼動態計算
大前提:
1、initial-scale 為 1;
2、在專案css中(注意不要被公共的base、common之類的影響了,資源載入順序也是蠻重要的),先把html的fontSize設定為 50px(或者加上媒體查詢程式碼), 避免載入未完成時候樣式錯亂;
/* css */
html {font-size: 50px;}
/* javascript */
!(function(win, doc){
function setFontSize() {
// 獲取window 寬度
// zepto實現 $(window).width()就是這麼幹的
var winWidth = window.innerWidth;
// doc.documentElement.style.fontSize = (winWidth / 640) * 100 + 'px' ;
// 2016-01-13 訂正
// 640寬度以上進行限制 需要css進行配合
var size = (winWidth / 640) * 100;
doc.documentElement.style.fontSize = (size < 100 ? size : 100) + 'px' ;
}
var evt = 'onorientationchange' in win ? 'orientationchange' : 'resize';
var timer = null;
win.addEventListener(evt, function () {
clearTimeout(timer);
timer = setTimeout(setFontSize, 300);
}, false);
win.addEventListener("pageshow", function(e) {
if (e.persisted) {
clearTimeout(timer);
timer = setTimeout(setFontSize, 300);
}
}, false);
// 初始化
setFontSize();
}(window, document));
嗯。。。
就這麼愉快地結束了。。。
不知道解讀了某寶首頁的一點點程式碼,然後發在這裡,會不會有什麼後果。。。
==================================================
2016年1月13日補充
寫過這篇部落格之後,又陸續讀過幾篇關於佈局的文章。
具體已經忘了,大約是大漠的文章,還有一篇應該是搜車前端的博文,另外應該還有關於手淘首頁的分析的文章。
另外,自己也用rem佈局實踐過幾個專案。
不得不說,個人覺得rem佈局現在已經可以放棄了。flex
佈局已經很好用了,早已有之的百分比佈局等稍用點心思也並不難。
這篇部落格一直想改。但懶惰總是佔據著我的身體。
最後再說下,字型大小自適應是錯誤的,字型大小自適應是錯誤的,字型大小自適應是錯誤的。
rem 佈局,可以告別了。
迎接 flex 佈局吧。
=========================================
寫在最後
這篇部落格寫於半年前,那時候還是個剛畢業的菜鳥。
偶爾有點想法,看了一些大牛的文章,有了這篇部落格。
這也是半年來唯一一篇產出。
5k的瀏覽量,95收藏,13推薦,已經讓我很驚訝了。
謝謝各路大神們的關注。
半年來感受到的前端大環境變化還是很大。雖然在公司沒有太多變化,但眼睛總得看著世界吧。
接下來,還得繼續學習。
由於手上沒什麼專案,一直想探索出一套自己的自動化流程,但到現在也只是積累了許多版的草稿。
nodejs
方面也得有所探索,nodejs
再加上 shelljs
和 yargs
用起來是真的很爽。(鳴謝阮大神的文章)
算是年終總結了。在前端的路上繼續走吧。
=========================================
一點想法:評論區的回覆
媒體查詢和js動態計算是兩種方式。
首先,支援 CSS3 calc
方法 和 rem
、vw
單位的瀏覽器下,只需要html
{font-size: 15.625vw;}
這樣一句就好,另外加個媒體查詢限制下。
之前的一大堆密集的斷點只是為了hack不支援calc
或者calc
的情況。其次,js動態設定html的font-size
,只要瀏覽器支援rem單位即可。
為什麼會考慮到密集的mq斷點呢,因為當時還在考慮文字大小的自適應問題。
實踐證明,字型大小自適應是一種錯誤的想法。
移動開發在必要情況的下,可以適當使用mq來調整字型大小,但做成完全自適應則是一種存在問題的做法。
因此,這裡提到的 calc
和vh
rem
配合的做法,最好只用來做佈局的工作。js動態計算也是類似,更適合做佈局。