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