场景
对于大屏(大于 1920 以上的屏幕)的适配,我们通常可以将项目中的px单位转换成rem 如:
.container {
width: 100px;
}
// transform to rem html font-size: 16px;
.container {
width: 6.25rem;
}
对于html字体的设置,可以如下(less 语言):
html {
font-size: 16px;
@media (min-width: 1920px) {
font-size: (16 / 1920 * 100)px;
}
}
我们知道对于样式文件(css/less/scss/stylus),可以使用 postcss-pxtorem postcss插件自动转换,如:
// postcss.config.js
module.exports = {
plugins: {
autoprefixer: {},
'postcss-pxtorem': {
rootValue: 16,
unitPrecision: 5,
replace: true,
mediaQuery: false,
minPixelValue: 1,
propList: ['*'],
},
},
};
但对于 JSX(React) 中的内连样式 postcss-pxtorem 无法自动转换,需要开发人员手动转换,比较繁琐,且容易遗漏,下面介绍一个 JSX 内连样式自动转换的方案
编写babel插件自动将内连样式的px转换成rem
在介绍转换方案之前我们来下认识下,JSX 中的静态样式和动态样式
静态样式 & 动态样式
静态样式
const a = <div style={{width: 100, height: '100px'}}></div>
例子中 width 和 height 属性都是静态样式,所谓静态,是指不变的,确定的
动态样式
动态样式又可分为动态属性样式和动态style样式
动态属性样式
const width = 100;
const a = <div style={{width: width, height: width + 100}}></div>
例子中 width 和 height 属性都是动态属性样式
动态style
// case 1
const style = {width: 100, height: 200}
const a = <div style={style}></div>
// case 2
const spreadProps = {style: style}
const a = <div {...spreadProps}></div>
JSX Style 转rem 方案
静态样式转换
const a = <div style={{width: 100, height: '100px'}}></div>
// compiled to (automatic 模式)
import { jsx as _jsx } from '.xxxxx/jsx-runtime'
_jsx('div', {
style: {
width: '6.25rem',
height: '6.25rem'
}
})
// or compiled to (classic 模式)
import React from 'react'
React.createElement('div', {
style: {
width: '6.25rem',
height: '6.25rem'
}
})
动态属性转换
const width = 100;
const a = <div style={{width: width, height: width + 100}}></div>
// compiled to
import { jsx as _jsx } from 'react/jsx-runtime'
import { covertStylePropToRem } from 'jsx-style-px-to-rem-babel-plugin'
_jsx('div', {
style: {
width: covertStylePropToRem(width, 'width'),
height: covertStylePropToRem(width + 100, 'height')
}
})
covertStylePropToRem 为辅助函数,其作用是在运行时将 px 转换成 rem,下面的例子中出现了多个辅助函数,作用一致。
动态Style转换
// case 1
const style = {width: 100, height: 200}
const a = <div style={style}></div>
// compiled to
import { jsx as _jsx } from 'react/jsx-runtime'
import { covertJsxStyleToRem, covertJsxPropsToRem } from 'jsx-style-px-to-rem-babel-plugin'
_jsx('div', {
style: covertJsxStyleToRem(style)
})
// case 2
const spreadProps = {style: style}
const a = <div {...spreadProps}></div>
// compiled to
import { jsx as _jsx } from 'react/jsx-runtime'
import { covertJsxPropsToRem } from 'jsx-style-px-to-rem-babel-plugin'
_jsx('div', covertJsxPropsToRem(Object.assign({}, spreadProps)));
转换辅助函数
export function covertStylePropertyToRem(
value: string | number,
key?: string
): string | number {
if (key) {
const covertFn = SPECIAL_PROPERTYS_COVERTER[key];
if (covertFn) {
return covertFn(value) as string;
}
}
if (typeof value === "number") {
return pxtorem(value);
}
return covertStringPropertyToRem(value);
}
export function covertJsxStyleToRem(
style: Record<string, any>
): Record<string, string> {
if (!style) return style;
// const newStyle: Record<string, any> = {};
Object.keys(style).forEach((key) => {
if (isIgnoreUnitProperty(key)) {
return;
}
const value = style[key];
if (!value) {
return;
}
style[key] = covertStylePropertyToRem(value, key);
});
return style;
}
export function covertJsxPropsToRem(props: Record<string, any>) {
if (!props) return props;
if (!("style" in props)) return props;
props.style = covertJsxStyleToRem(props.style);
return props;
}
export function pxtorem(value: number): string {
if (value === 0) return "0";
if (getOptionsConfig().minPixelValue && value < getOptionsConfig().minPixelValue) {
return `${value}px`;
}
return `${(value / getBaseFontSize()).toFixed(
getOptionsConfig().unitPrecision
)}rem`;
}
export function covertStringPropertyToRem(value: string): string {
return value?.replace(PX_VALUE_REG, (_, p1) => {
return pxtorem(parseFloat(p1));
}) || '';
}