rem适配方案原理
- 以根html元素的font-size为基准大小,来设置页面元素的尺寸,字体大小等
- 当页面大小发生变化时,同步修改根html元素的font-size大小,这样凡属rem单位的实际px也会跟着适配改变
谨慎使用amfe-flexible
+postcss-pxtorem
适配方案
简述amfe-flexible
源码与postcss-pxtorem
配置方式
在网上找rem适配方案,很容易就会找到以此组合实现的相关文章。本质上没问题,确实是依据rem适配方案原理来的。但amfe-flexible
是将视口宽度分为10等分,如果你是基于375宽度的模式调试开发,那么此时根html的字体大小被设置成了37.5
,你需要将postcss-pxtorem
中的根字体大小也设置成37.5
,然后打包时postcss-pxtorem
会帮你自动将项目中的px转换为对应的rem值。amfe-flexible
会自动帮你调整根html
的字体大小
amfe-flexible
源码
js
(function flexible (window, document) {
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1
// adjust body font size
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
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))
为什么需要谨慎使用此方案?
因为以此方案适配出来的界面,即使在设计稿的视口宽高下,适配出来的效果图也存在一定的偏差。
如:375px宽度的设计稿,在375px宽度的视口下,那么amfe-flexible
算出的1rem的宽度为37.5px,反过来,1px就等于1 rem / 37.5 px ≈ 0.026666666 rem
,10px计算方式为 0.026666666 rem * 10 ≈ 0.26rem
,计算结果全是约等于,那自然无法完美还原设计稿。
amfe-flexible
源码改进
amfe-flexible
的问题在于,1rem计算出来的px值与设计稿的尺寸无法整除,那如果1rem计算出来的px值为10px或100px这种,那就无问题了。
还是以之前375px宽度的设计稿,在375px宽度的视口,还原10px为例。根据改进后的amfe-flexible
,以10px
为基准字体大小(即:在375设计稿,以及375px的视口宽度下,1rem为10px,1px为0.1rem
),那么10px转换为rem为1rem,15px为1.5rem,得到的都是确定的值,因此至少在与设计稿同样宽度的浏览器视口下,是能完美还原设计稿的。
使用改进后的amfe-flexible源码
后postcss-pxtorem
如何设置,无论实际的设计稿宽度是多少,postcss-pxtorem
的rootValue
设置为10
即可
重点在setRemUnit
方法
ts
// set 1rem = viewWidth / 10
function setRemUnit() {
// 设计稿宽度(需要设置为你实际拿到的ui稿宽度)
const uiDesignWidth = 375
// 当前浏览器视口宽度相当于设计稿宽度的缩放比 = 屏幕宽度/设计稿宽度
const scale = docEl.clientWidth / uiDesignWidth
// 基准字体大小(同时也表示1rem对应多少px),无论你实际的设计稿宽度是多少,这个baseFontSize都不需要变
// P.S. postcss-pxtorem的rootValue值,需要与baseFontSize一致
const baseFontSize = 10
// 实际根html元素字体大小 = 基准字体大小 * 当前浏览器视口宽度相当于设计稿宽度的缩放比
const oneRemPx = baseFontSize * scale
// 设置根html元素字体大小(即: 1rem等于多少px)
docEl.style.fontSize = oneRemPx + 'px'
}
改造后的amfe-flexible
源码
flexible.ts
ts
(function flexible (window, document) {
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1
// adjust body font size
function setBodyFontSize () {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
}
else {
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10
function setRemUnit () {
// 设计稿宽度(需要设置为你实际拿到的ui稿宽度)
const uiDesignWidth = 375
// 当前浏览器视口宽度相当于设计稿宽度的缩放比 = 屏幕宽度/设计稿宽度
const scale = docEl.clientWidth / uiDesignWidth
// 基准字体大小(同时也表示1rem对应多少px),无论你实际的设计稿宽度是多少,这个baseFontSize都不需要变
// P.S. postcss-pxtorem的rootValue值,需要与baseFontSize一致
const baseFontSize = 10
// 实际根html元素字体大小 = 基准字体大小 * 当前浏览器视口宽度相当于设计稿宽度的缩放比
const oneRemPx = baseFontSize * scale
// 设置根html元素字体大小(即: 1rem等于多少px)
docEl.style.fontSize = oneRemPx + 'px'
}
setRemUnit()
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
// 探测浏览器对0.5px的显示是否支持
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
// 将测试元素设置0.5px的边框
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
// 检测测试元素的高度,如果高度为1px,那说明浏览器不支持直接显示0.5px的元素,凡属小于1px的元素都当作1px渲染,此时在根html标签添加`hairlines`类,css或js就可以通过判断根html中是否有该class来,判定浏览器是否支持直接渲染0.5px,有该class就表示不支持,否则表示支持
docEl.classList.add('hairlines')
}
// 删除测试元素
docEl.removeChild(fakeBody)
}
}(window, document))
扩展
vw或vh适配方案也有这种问题吗?
是的,也有同样的问题。vw或vh,实际就是将视口分成了100份,而原始的amfe-flexible
, 是将视口分成了10
份. 因此采用vh
,vw
方案,即使浏览器视口与设计稿宽度一致,也无法完美还原设计稿
依然以375px设计稿和视口宽度为例
ini
100vw = 375px
1vw = 375px / 100 = 37.5px
1px ≈ 1vw / 37.5 ≈ 0.02666 vw