更新:感謝各位評論區大佬提醒,我發現關於 rem 佈局思路的用法最初是由手淘團隊的 flexible 方案引入的。本文你們隨便看不看都行,真正有技術含量的內容必定請看:使用Flexible實現手淘H5頁面的終端適配,再聊移動端頁面的適配。css
在移動端使用 rem 進行切圖適配應該是比較常見的一種手法,近日在工做中遇到了一次有趣(纔怪)的兼容性問題。特此記之,同時梳理使用 rem 適配的思路,以及一個使用 sass 函數的小技巧 ( ✪ω✪ )。html
rem 和 em 都是 CSS 中基於字體大小的相對單位,兩者的區別在於:em 使用當前元素的字體大小肯定實際尺寸,而使用 rem 依據的則是 html 根節點的字體大小。html5
使用 em 其實很惱人,例如分別爲父元素和子元素的 font-size
設置某個值,如 1.2em
,假設父元素繼承獲得的字體大小本來是 10px,應用樣式後,父元素字體大小變成了 12px,而子元素則變成了 14.4px。一份代碼中,相同寫法的尺寸的效果可能到處不同。可想而知,若是大量使用 em,很容易使得代碼混亂難懂,因此程序員不會常用它。webpack
相比之下,rem 顯示出某種統一性。因爲僅依賴於 html 根節點的字體大小,所以代碼中任何地方的 2rem
都絕對是根元素 2 倍大小,沒有使用 em 時的「驚喜」。程序員
這是一種簡單粗暴可是的確好用的屏幕適配方法。注意,前提條件是不對頁面高度作出限制,也就是說這種適配方法僅能適應屏幕寬度的變化;若是還須要適配整屏高度,那麼單純使用 rem 可能沒法完成。web
簡單來講,設計師傅出圖時使用某款機型尺寸,如 iphone6。切圖時咱們先徹底按照設計圖中的像素尺寸進行,chrome 的設備模擬器也設置爲 iphone6 的視圖。chrome
這樣一來,若是切換到其餘寬度的手機屏幕,或者將設備橫屏,都會出現佈局混亂的問題。sass
接下來進行一個操做:將全部使用 px
的單位,統一換算到 rem
單位(方法見後文)。iphone6 的 webview 默認 html 字體大小是 16px,因此若是 CSS 中某元素是 32px 的寬度,那就換算爲 2rem,並以此類推。iphone6 的屏幕寬度是 375px,假設使用了一個佔據屏幕一半寬度的 div 元素,本來的 CSS 是 width: 187.5px
,那麼換算後是 width: 11.71875rem
,保持了原始的尺寸。iphone
不過你應該發現了,雖然如今使用的是 rem 佈局,可是換到不一樣寬度的屏幕,並不能保證這個 div 仍是佔據屏幕寬度的一半。例如 chrome 設備模擬器中的 Pixel2 手機,寬度爲 411px,而 html 默認 font-size 仍是 16px。此時本來的 width: 11.71875rem
計算獲得的實際寬度只是 iphone6 屏幕寬度的一半 187.5px,並不是期待的 Pixel2 屏寬的一半 205.5px。函數
下一步是 rem 適配思路的關鍵:調整 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';
複製代碼
前面提到須要將 CSS 中的 px 單位換算成 rem 單位。一般的作法是在 webpack 中配置一個 loader,編譯過程當中自動匹配 px 單位,而後按照設定的比例換算。
實際上,若是項目中使用了 sass (通常使用更貼近原生 CSS 語法的 scss 的寫法) 代替 CSS,那麼有一種更巧妙的方法:使用 sass 函數。
定義尺寸換算函數以下:
@function rem($size) {
@return $size / 16 * 1rem;
}
複製代碼
使用時,直接利用該函數將 px 單位的數值寫入,便可自動換算爲 rem 單位:
.foo {
width: rem(20);
}
複製代碼