Rem 等比適配始末

小蟲巨蟹發表於2019-03-01

Viewport 等比適配始末 說過使用 Viewport 來實現等比適配的例子,本文詳解等比適配的另一種方式

推導

拿到一個寬度為 vWidth 的視覺稿
設裝置螢幕寬度為 dWidth
在視覺稿上量得一個元素的寬度為 eleVWidth
那麼要實現按照寬度等比適配,在各種裝置中元素的實際寬度 x 將滿足公式

    //等比
    eleVWidth/vWidth = X/dWidth;

    //我們的目的是求X
    X = (eleVWidth/vWidth)dWidth;複製程式碼

拿到視覺稿之後,eleVWidth 和 vWidth 都已經固定了,但是 dWidth 在不同的裝置中確實不同的,而且需要實際執行的時候我們才能拿到 dWidth,這個時候就要利用 rem 的特性了,我們通過阻塞執行的 js 動態設定 rem,使得rem=dWdith/10,套入剛才的公式:


    X=(eleVWidth/vWidth)*10*rem

    //體現在css中
    .className {
        width: (eleVWidth/vWidth)*10 rem
    }複製程式碼

為什麼設定是 dWdith/10 呢?方便計算而已,當然也可以除以別的基數,但是儘量不要讓 rem 太小,有相容性問題

動態設定rem

這段 css 需要放在 body 標籤的開始位置,因為 rem 值要儘可能早的設定成功,避免無謂的重排重繪(有些裝置中若元素樣式已經生效,動態改變 rem 未必能起作用,所以這個動作更需要提前)

    <body>
        <script>
        (function() {
            var ua = window.navigator.userAgent;
            var docEl = document.documentElement;
            var html = document.querySelector(`html`);
            var isAndorid = /Android/i.test(ua);
            var dpr = window.devicePixelRatio || 1;
            var rem = docEl.clientWidth / 10;

            // 設定 rem 基準值
            html.style.fontSize = rem + `px`;

            // Nexus 5 上 rem 值不準,
            // 如:設定100px,getComputedStyle 中的值卻為 85px,導致頁面錯亂
            // 這時需要檢查設定的值和計算後的值是否一樣,
            // 不一樣的話重新設定正確的值
            var getCPTStyle = window.getComputedStyle;
            var fontSize = parseFloat(html.style.fontSize, 10);
            var computedFontSize = parseFloat(getCPTStyle(html)[`font-size`], 10);
            if (getCPTStyle && Math.abs(fontSize - computedFontSize) >= 1) {
                html.style.fontSize = fontSize * (fontSize / computedFontSize) + `px`;
            }

            // 設定 data-dpr 屬性,留作的 css hack 之用
            html.setAttribute(`data-dpr`, dpr);

            // 安卓平臺額外加上標記類
            if (isAndorid) {
                html.setAttribute(`data-platform`, `android`);
            }
        })();
        </script>
        ...
    </body>複製程式碼

css 中應該怎麼寫

如果每在視覺稿上量出一個值,在寫到樣式檔案之前都得通過那個公式計算一翻,那絕對不是一個好策略,我們希望量到啥就寫啥

使用 less,sass 等 css 前處理器的函式

    //設計稿為640
    @function rem($val) {
        @return $val/64 * 1rem;;
    }

    //設計稿為750
    @function rem750($val) {
        @return $val/75 * 1rem;
    }

    //使用
    .className {
        width: @rem(100);
    }複製程式碼

也可以使用 webpack-loader,例如:change-rem-loader,這個 loader 程式碼做的事也是上述的轉化計算(寫一個 webpack loader 並不複雜,你可以自己寫一個符合自己情況的 loader)

    `use strict`;
    module.exports = function(source) {
        source = source.replace(/rem(d*)((.*?))/g, function(match, type, value) {
            var divVal;

            // 不做 try catch,及早發現錯誤以免造成故障
            switch (type) {
                // 1080寬度的設計稿,rem1080(value)
                case `1080`:
                    divVal = 108;
                    break;
                    // 預設為750寬度的設計稿,rem(value)
                case `750`:
                    divVal = 75;
                    break;
                default:
                    divVal = 64;
            }

            return (value / divVal).toFixed(6) + `rem`;
        });
        return source;
    }複製程式碼

這樣之後,我們 css 同樣可以這樣寫(如何讓 loader 起作用不是本文的範圍)

    .className {
        width: rem(100);
    }複製程式碼

也許你的工程技術棧裡面,使用的不是 webpack,也沒有使用 less、sass 等前處理器,那你可以根據你的實際情況去尋找一種預處理方案,只要達到推導公式的效果就可以了。
什麼?沒有合適的方案,那你是時候充充電了

期待您的關注~~

相關文章