媒介
挪動端的興起,給了我們前端更大的舞台,與此同時,也給我們帶來了一系列頭疼的題目,挪動端適配就是其中之一,現在市面上最經常使用的計劃等於REM適配。
為何說她是一個磨人的小妖精?由於她確切讓人又愛又恨,天真的自適應規劃再搭配上css單元轉換東西,讓人愛不釋手;另一方面,由於挪動端的機型和表現光怪陸離,想要到達圓滿的兼容又讓人頭疼。
即使如此,依舊阻止不了筆者關於她的痴迷。本文將會繚繞REM適配這一話題舉行議論,同時也會將筆者個人的履歷以及本身現在在用的一套代碼分享給人人。別的,現在挪動端的兼容性越來越好,因而衍生出了一些其他的適配計劃,這點不在本文的議論局限以內。
實例剖析
全局變量
const docEl = document.documentElement
const metaEl = document.querySelector('meta[name="viewport"]')
const maxWidth = window.__MAX_WIDTH__ || 750
const divPart = window.__DIV_PART__ || 15
const bodySize = window.__BODY_SIZE__ || 12
let scale = 1
let dpr = 1
let timer = null
- metaEl:抓取現有viewport,以支撐使用者自定義頁面現實縮放比例,經由過程設置viewport能夠完成視覺上的現實物理像素。比方
initial-scale=0.5
,即二倍屏,假定根節點的font-size=100px,那末0.01rem就是物理像素1px;而initial-scale=1.0
,雖然在css單元中,0.01rem=1px,但我們曉得,在二倍屏中,1px現實有4個物理像素。 - maxWidth:UI稿寬度,平常以iphone6為基準,即750。
- divPart:將裝備寬度分別為若干份,上述代碼中,750/15=50,意義是750寬度的屏幕,1rem=50px,分別若干份現實上沒有牢固劃定,看個人習氣。
- bodySize:初始化時,設置body的字體大小。
- scale、dpr分別是頁面縮放比例、裝備像素比。
初始化設置
if (metaEl) {
console.warn('依據已有的meta標籤來設置縮放比例')
const match = metaEl.getAttribute('content').match(/initial-scale=([\d.]+)/)
if (match) {
scale = parseFloat(match[1])
dpr = parseInt(1 / scale)
}
} else {
if (window.navigator.appVersion.match(/iphone/gi)) {
dpr = parseInt(window.devicePixelRatio) || 1
scale = 1 / dpr
}
const newMetaEl = document.createElement('meta')
newMetaEl.setAttribute('name', 'viewport')
newMetaEl.setAttribute('content', `width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`)
docEl.firstElementChild.appendChild(newMetaEl)
}
// 設置根節點dpr
docEl.setAttribute('data-dpr', dpr)
這裏要重點將一下為何要辨別安卓和IOS裝備,很多人能夠會說由於IOS有多倍屏。現實上,安卓也有多倍屏,那為何我們不斟酌呢?
- 有些安卓機的裝備像素比很新鮮,比方2.5、3.8等一些新鮮的数字;
- 部份安卓機表現很新鮮,比方頁面寬度比屏幕寬度多一點,湧現橫向滾動條(詳細緣由不詳,已消除一切css滋擾),兼容起來本錢太高。
中心代碼
function bodyLoaded (cb) {
if (document.body) {
cb && cb()
} else {
document.addEventListener('DOMContentLoaded', function () {
cb && cb()
}, false)
}
}
// 窗口寬度轉變時,革新rem
function refreshRem () {
let width = docEl.clientWidth
if (width / dpr > maxWidth) {
width = maxWidth * dpr
}
// 設置根節點font-size
window.remUnit = width / divPart
docEl.style.fontSize = window.remUnit + 'px'
bodyLoaded(() => {
// 測試rem的準確性,假如和預期不一樣,則舉行縮放
let noEl = document.createElement('div')
noEl.style.width = '1rem'
noEl.style.height = '0'
document.body.appendChild(noEl)
let rate = noEl.clientWidth / window.remUnit
if (Math.abs(rate - 1) >= 0.01) {
docEl.style.fontSize = (window.remUnit / rate) + 'px'
}
document.body.removeChild(noEl)
})
}
// 初始化
refreshRem()
bodyLoaded(() => {
document.body.style.fontSize = bodySize * dpr + 'px'
document.body.style.maxWidth = maxWidth * dpr + 'px'
})
refreshRem
函數是全部rem適配的中心,每次須要更新都邑挪用此函數,我們還限制了頁面的最大寬度,能夠保證在pc端翻開也能看到不錯的視覺效果。
然則有一部份的安卓機,1rem並不等於根節點的font-size,舉個例子:html的font-size=20px,一般情況下1rem也應該是20px,但在部份機型中,它多是22px或18px等等(筆者疑心上文中提到的頁面寬度溢出也是這個題目)。因而,筆者加上了bodyLoaded這段代碼,在rem設置完成后,再與現實視覺上的1rem舉行比較,若誤差凌駕1%,則以為須要從新定義rem,如許就可以100%保證1rem就是我們希冀的大小。
頁面寬度監聽
window.addEventListener('resize', function () {
clearTimeout(timer)
timer = setTimeout(refreshRem, 200)
}, false)
// window.addEventListener('pageshow', function (e) {
// if (e.persisted) {
// refreshRem()
// }
// }, false)
這段代碼用於監聽resize事宜,以此來從新盤算根節點的font-size,定時器用來防備頻仍盤算(現實上在手機中,也不會有頻仍觸發resize的時機,因而定時器也能夠不加)。有些讀者能夠會題目,為何不監聽反正屏事宜(onorientationchange),實在沒有必要,反正屏切換實質也是resize的一種,我們已監聽了resize事宜,這裏就沒有必要再次監聽了。
那解釋掉的這段代碼是什麼意義呢?它是用來監聽瀏覽器返回,然則這段代碼在iPhone8、iPhoneX上會有題目,在返回的時刻,我們拿到的document.documentElement.clientWidth
是其現實的大小(沒有乘上裝備像素比),因而全部頁面規劃都亂了。筆者經由深圖遠慮,決議刪掉這段代碼,由於在返回的時刻,會保存和脫離時一摸一樣的狀況,沒有必要從新再盤算一遍。
東西函數
window.px2rem = function (d) {
let val = parseFloat(d) / window.remUnit
if (typeof d === 'string' && d.match(/px$/)) {
val += 'rem'
}
return val
}
window.rem2px = function (d) {
let val = parseFloat(d) * window.remUnit
if (typeof d === 'string' && d.match(/rem$/)) {
val += 'px'
}
return val
}
暴露全局函數,方便使用js來掌握尺寸大小。
CSS重置款式
篇幅所限,款式代碼就不在這裏貼了,感興趣能夠在這裏看:reset.css
總結
這一套rem適配代碼是筆者一樣平常開闢中總結提煉出來,不能說是100%圓滿,然則也充足適配市面上的主流機型了。再合營構建東西,自動轉換為rem單元,費心又省力。
末了引薦一個好用的全局構建東西fle-cli,幫你從龐雜煩瑣的構建設置中解放出來。
本文源碼地點:https://github.com/ansenhuang/axe/blob/master/packages/rem-resize/README.md