記一次移動端使用 rem 的相容性問題

leto發表於2019-03-17

更新:感謝各位評論區大佬提醒,我發現關於 rem 佈局思路的用法最初是由手淘團隊的 flexible 方案引入的。本文大家隨便看不看都行,真正有技術含量的內容一定請看:使用Flexible實現手淘H5頁面的終端適配再聊移動端頁面的適配


在移動端使用 rem 進行切圖適配應該是比較常見的一種手法,近日在工作中遇到了一次有趣(才怪)的相容性問題。特此記之,同時梳理使用 rem 適配的思路,以及一個使用 sass 函式的小技巧 ( ✪ω✪ )。

rem 與 em 的對比

rem 和 em 都是 CSS 中基於字型大小的相對單位,二者的區別在於:em 使用當前元素的字型大小確定實際尺寸,而使用 rem 依據的則是 html 根節點的字型大小。

使用 em 其實很惱人,例如分別為父元素和子元素的 font-size 設定某個值,如 1.2em,假設父元素繼承得到的字型大小原本是 10px,應用樣式後,父元素字型大小變成了 12px,而子元素則變成了 14.4px。一份程式碼中,相同寫法的尺寸的效果可能處處不一樣。可想而知,如果大量使用 em,很容易使得程式碼混亂難懂,所以程式設計師不會經常使用它。

相比之下,rem 顯示出某種統一性。由於僅依賴於 html 根節點的字型大小,因此程式碼中任何地方的 2rem 都絕對是根元素 2 倍大小,沒有使用 em 時的“驚喜”。

使用 rem 切圖

這是一種簡單粗暴但是的確好用的螢幕適配方法。注意,前提條件是不對頁面高度做出限制,也就是說這種適配方法僅能適應螢幕寬度的變化;如果還需要適配整屏高度,那麼單純使用 rem 可能無法完成。

簡單來說,設計師傅出圖時使用某款機型尺寸,如 iphone6。切圖時我們先完全按照設計圖中的畫素尺寸進行,chrome 的裝置模擬器也設定為 iphone6 的檢視。

這樣一來,如果切換到其他寬度的手機螢幕,或者將裝置橫屏,都會出現佈局混亂的問題。

單位換算

接下來進行一個操作:將所有使用 px 的單位,統一換算到 rem 單位(方法見後文)。iphone6 的 webview 預設 html 字型大小是 16px,所以如果 CSS 中某元素是 32px 的寬度,那就換算為 2rem,並以此類推。iphone6 的螢幕寬度是 375px,假設使用了一個佔據螢幕一半寬度的 div 元素,原本的 CSS 是 width: 187.5px,那麼換算後是 width: 11.71875rem,保持了原始的尺寸。

不過你應該發現了,雖然現在使用的是 rem 佈局,但是換到不同寬度的螢幕,並不能保證這個 div 還是佔據螢幕寬度的一半。例如 chrome 裝置模擬器中的 Pixel2 手機,寬度為 411px,而 html 預設 font-size 還是 16px。此時原本的 width: 11.71875rem 計算得到的實際寬度只是 iphone6 螢幕寬度的一半 187.5px,並非期待的 Pixel2 屏寬的一半 205.5px。

下一步是 rem 適配思路的關鍵:調整 html 元素 font-size 的大小。

調整 html 元素的 font-size

想要讓 iphone6 上的佈局效果完美切換到 Pixel2,就需要讓各個元素等比例放大,倍數是 411 / 375。所有單位都是基於 rem,也就是基於 html 元素 font-size。直接讓 html 的 font-size 放大這個比例,不久就可以使得所有元素同時放大了嗎。

所以,在頁面載入時,加入下面這段程式碼:

var count = 23.4375;
document.documentElement.style.fontSize = document.documentElement.clientWidth / count + 'px';
複製程式碼

程式碼中的 count 是從 iphone6 的屏寬除以預設字號計算得來:375 / 16。也就是說以 iphone6 的螢幕寬度,可以放下 count 個預設文字。

執行程式碼後,等效於設定 Pixel2 fontSize *= (Pixel2 width / iphone6 width)

或者換種理解:設定 font-size 後,螢幕寬度就可以容納 count 個字型,也就是說螢幕寬度成了 count rem。如果使用 2rem 作為元素寬度,那麼這個元素就佔用了螢幕的 2 / count,這個比例在任何寬度的裝置上都一樣。

有了對 html 預設字號的修改,理論上就可以無憂無慮地使用 rem 單位進行佈局了。任何螢幕上所見的佈局都是 iphone6 上的佈局等比例縮放的效果。

最後為 window 的 resize 進行事件繫結,視窗尺寸變化時重新設定字號。這樣,在旋轉螢幕時佈局就能自適應螢幕尺寸變化了。

我遇到的相容性問題

rem 雖好,但是在使用的細節上還要留意。比如最近我遇到了一次佈局混亂的詭異現象。出現在 Android 的 hybrid 應用上,也就是原生應用的內嵌 webview 元件中。

這個問題一開始並不是百分之百復現,在某些手機上偶爾出現開啟 webview 時,介面佈局會混亂,具體表現為文字元素全部消失,只留下一張張充滿螢幕寬度的圖片從上到下依次排布。仔細排查後,終於定位到是 html 元素的 font-size 問題:它居然是 0。

再次排查,發現問題來自於 document.documentElement.clientWidth

某些手機在開啟 webview 時,一開始獲取 html 元素的 clientWidth 是沒有值的,導致程式碼將 html 的 font-size 設定成了 0。那麼佈局出錯就是必然的了。至於為什麼圖片還能顯示,是因為佈局中使用了百分比設定圖片大小,如果也使用 rem 設定圖片尺寸,圖片也會消失。

雖然定位問題花了一番功夫,解決起來還是很容易的,使用相容的寫法獲取螢幕寬度就好了:

var width = document.documentElement.clientWidth
                || document.body.clientWidth || window.innerWidth;
document.documentElement.style.fontSize = width / count + 'px';
複製程式碼

sass 函式的應用

前面提到需要將 CSS 中的 px 單位換算成 rem 單位。通常的做法是在 webpack 中配置一個 loader,編譯過程中自動匹配 px 單位,然後按照設定的比例換算。

實際上,如果專案中使用了 sass (一般使用更貼近原生 CSS 語法的 scss 的寫法) 代替 CSS,那麼有一種更巧妙的方法:使用 sass 函式。

定義尺寸換算函式如下:

@function rem($size) {
   @return $size / 16 * 1rem;
}
複製程式碼

使用時,直接利用該函式將 px 單位的數值寫入,即可自動換算為 rem 單位:

.foo {
    width: rem(20);
}
複製程式碼

參考資料:codepen.io/jelleverzij…

相關文章