手寫Flexible.js的原理實現,我終于明白移動端多端適配
今天在看阿里的面試題時,看到這樣一道面試題,問flexible.js的原理是什么?
然而我也不知道,但是剛好我又在我公司的項目上遇到過,于是研究一番,遂作此文。
核心原理
簡單的一句概括就是:flexible.js幫我們計算出1rem 等于多少px。
怎么計算的?
很簡單,就是1rem = 屏幕寬度的1/10.
var docEl = document.documentElement // 返回文檔的root元素,即html
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
我們知道rem的大小是根據html節點的font-size的相對值。
例如,iphone 6的屏幕寬度為375px,因此1rem === 37.5px。
計算rem干嘛?
那幫我們計算出rem的值有什么鬼用嗎?
確實,如果只是單純的計算出rem的值并沒什么用。發揮它的用處是當我們根據設計稿來轉化成頁面時需要用到。
舉個例子,現在有兩個手機,一個手機的屏幕寬度是375px,一個是750px,設計稿給我們的寬度是375px,那我們按照設計稿的設計在375px的手機上剛好完美匹配,但是卻會發現在750px的手機上頁面只有一半,空白了一半。
這就是我們需要解決的問題,即怎么解決移動端尺寸眾多的問題,我們的設計稿是固定,怎么辦,如果設計稿是彈性的可以隨意縮放該多好。
好吧,設計只給一張設計稿,我們只能想其他方法啦。
等比畫餅
想想,有辦法了,就像本來你在一張大的紙上面了一餅,現在讓你在小的紙上在畫一次要怎么畫,就是所有東西都等比例畫小,如果要畫到更大的紙上也是一個道理,等比畫大,對不對。
現在我們把設計稿分成10等份,設計稿 A = W/10,我們把設備可視區域也就是我們的各種移動端設備的這個畫布也分成10份,并賦值給根元素的fontSize,我們都知道rem是根據根元素字體大小計算的,所以我們的1rem也就是設備可視區域/10,現在設計稿上有一塊區域寬B,那它是不是等比放到設備可視區域的寬度為 B/A rem。
再啰嗦一下,B在設計稿上占B/A份,那在設備可視區域上也要占B/A份對不對,所以寬是B/A rem。這就是flexible.js能實現設備兼容的原理。下面看代碼。
// 首先是一個立即執行函數,執行時傳入的參數是window和document
(function flexible (window, document) {
var docEl = document.documentElement // 返回文檔的root元素
var dpr = window.devicePixelRatio || 1 // 獲取設備的dpr,即當前設置下物理像素與虛擬像素的比值
// adjust body font size 設置默認字體大小,默認的字體大小繼承自body
function setBodyFontSize () {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
}
else {
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports 檢測是否支持0.5像素,解決1px在高清屏多像素問題,需要css的配合。
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
}(window, document))
這就是flexible.js的源碼,超級簡單吧。
就這幾行代碼就有12k的star,要是我也早點發現這個方案就好了。那star就是我的了。
現在已經實現了將屏幕分為10等份,也就是1rem。
將設計稿分成10等份
根據我們上面畫餅的方案,現在也要把設計稿轉化為10等分才行。
我看了下我們項目的實現是用到了postcss-pxtorem插件來實現的。
因為設計稿給我們的是px單位的,所以我們在開發的時候只能寫px,然后這就需要postcss-pxtorem來幫我們將我們寫的px轉化為rem了。
安裝完postcss-pxtorem之后的配合非常簡單,只要在.postcssrc.js文件配置如下就好了。
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 75,
}
}
}
rootValue:75 為啥是75呢,這是因為我們的設計稿的寬度是750px,十分之一就是75px。
如果你們的設計稿是375px的,就需要將值改寫成37.5。
flexible.js升級版
我們公司的使用是在flexible.js的基礎上進行了更改,主要是添加了這樣一段代碼。
var metaEl = doc.querySelector('meta[name="viewport"]');
if (metaEl) {
console.warn('將根據已有的meta標簽來設置縮放比例');
var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
}
這一串主要是來實現iphone和安卓的設備像素比不一樣的問題,例如iphone的一些手機。
總結
就這么簡單的兩步就實現了移動端的適配。
相關參考
- flexible.js 原理解析(看了不會忘):https://juejin.cn/post/6923060568437817351。
- 通過插件postcss-pxtorem輕松實現px到rem轉換,完成移動端適配:https://blog.csdn.net/llq886/article/details/105737987。