關於移動端 rem 布局的一些總結


【資源一】基礎知識恕不回顧

基礎知識參考以下兩篇博客:

http://isux.tencent.com/web-app-rem.html

http://www.w3cplus.com/css3/define-font-size-with-css3-rem

【資源二】淘寶m站首頁的動態實現

學習http://m.taobao.com 首頁的實現。

最近讀到@大漠的新文章《使用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 再加上 shelljsyargs 用起來是真的很爽。(鳴謝阮大神的文章

算是年終總結了。在前端的路上繼續走吧。

=========================================

一點想法:評論區的回復

媒體查詢和js動態計算是兩種方式。

首先,支持 CSS3 calc方法 和 remvw單位的瀏覽器下,只需要html {font-size: 15.625vw;}這樣一句就好,另外加個媒體查詢限制下。

之前的一大堆密集的斷點只是為了hack不支持calc或者calc的情況。其次,js動態設置html的font-size,只要瀏覽器支持rem單位即可。

為什么會考慮到密集的mq斷點呢,因為當時還在考慮文字大小的自適應問題。

實踐證明,字體大小自適應是一種錯誤的想法。

移動開發在必要情況的下,可以適當使用mq來調整字體大小,但做成完全自適應則是一種存在問題的做法。

因此,這里提到的 calcvh rem配合的做法,最好只用來做布局的工作。js動態計算也是類似,更適合做布局。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM