【只因】还在为rem配置苦恼?一个库解放你的双手,只因你太美🐔
思考
随着工作经验不断积累,工作职责逐渐扩大。除了业务开发和团队管理,还需要考虑如何通过前端工程化、基建设施,有效的提高能效,降低成本,减少事故,增强协同。从工程师角度,将现有技术与业务融会贯通,相辅相成。接下来在【只因】专栏中,作者14会通过结合离谱小故事与自己的真实体验,以 痛点的诞生 -> 技术点介绍 -> 部分核心实现思路 -> 使用姿势 -> 痛点的解决
为知识铺垫,梳理相关知识点应用。源码篇幅较少,主讲思路与实践。
起(只)因
最近很多小伙伴说,平时项目中用到了rem单位,但是只了解到rem的原理,开发中别人怎么写,我就怎么写。遇到新项目了,跟着网上资料也还是下不去手。
小七:14!今天天气真不错,明晚约个饭走起
我:这么多年了还是不会说谎,你瞧瞧你自己在说什么?说吧,什么事儿
小七:公司有个新项目,领导要我来做rem的适配。设计稿是750px,构建打包工具是webpack,需要在js和css中都能更方便的使用rem,还要做到屏幕尺寸改变时重新计算,balabala...
我:网上很多方案呐,rem原理也不难。组内不是还有其他小哥哥么
小七:今天是2月14日情人节,小哥哥们都在忙着选礼物送对象!我透支了一整天划水的时间去查方案,有的不完整,有的看不懂,有的配上去就报错了。好吧!我一个滑铲直接开溜!😭
我:你怎么知道今天我要去Gank春熙路IFS楼下的垃圾桶?好吧好吧,只因你太美,接下来看我表演
痛点的诞生
明明懂得rem的原理,一放到项目中rem适配策略的应用方案中时,就是不知道该如何使用,如何配置,如何优化,以及如何封装。在偌大的资源海洋中难以找到能承载自己的布吉岛。
技术点介绍
初识rem单位
概括地说,rem 单位的意思是“根元素的字体大小”。 -- MDN
可以概括的理解为:HTML根元素上font-size值 = 1rem
举例:root font-size = 100px = 1rem
; root font-size = 50px = 1rem
小结:不论root font-size 是多少px,1rem的实际px值就等于这个值。在实际应用中我们希望通过只修改root font-size,影响1rem的实际大小。因此从代码中看,变的是root font-size,不变的是1rem。记住这点很重要!
了解rem适配、设计稿、实际视窗的关系
背景:项目设计稿宽度为750px,有一个元素A宽度为100px
目的:假设在375px宽的视窗下,该元素A所展示的效果,需要和750px上的效果保持一致。其他宽度视窗同理。
明确了背景和目的后,我们进一步梳理1rem、设计稿、实际视窗的关系。
-
为了方便理解,我们称设计稿宽度为
designWidth
,实际视窗宽度为clientWidth
,root font-size为fz
,designWidth下元素A的px宽度为adWidth
,clientWidth下元素A的px宽度为acWidth
。为了方便计算,我们首先默认在designWidth下,fz = 100px
。 -
已知designWidth下adWidth为100px。根据目的,希望在clientWidth下acWidth展示效果一致,也就是同类比例一致。
得出式子:adWidth/acWidth? = designWidth/clientWidth,带入值得到100px/50px = 750px/375px。
与fz的关系:还记得我们设置了designWidth下fz为100px吗?根据式子,此时clientWidth下的fz就应该是50px。
与rem的关系:还记得fz和rem的关系吗?你会发现此时fz变了,rem实际大小变了,但1rem还是等于fz。假如把元素A的宽改成1rem,你会发现在designWidth下,fz=100px=1rem=adWdth,而在clientWidth下,fz=50px=1rem=acWidth。式子依然成立!其他视窗大小不影响式子!
小结:根据我们的推导,我们明白了如果要达成目的其实很简单只有两点,也不需要记这么多的式子和关系。只需要做到以下几点:
- 首先,我们在代码中写死具体的rem值。而为了方便计算设计稿上100px=?rem的问题,我们默认设置100px=1rem。这样就能一眼看出来,当要写50px时,实际就写0.5rem,当要写200px时,实际就写2rem。
- 其次,我们需要动态的设置fz的值,从而影响1rem的实际大小,来达成原有的比例。
部分核心实现思路
动态初始化root font-size
我们先来实现第二点,这里我以750px的设计稿为基准。
- 封装一个方法用来监听视窗改变设置root font-size。
// src/utils/rem.ts
const BASE_FONT_SIZE = 100 // root font-size 基准值常量
const DESIGN_WIDTH = 750 // 设计稿宽度常量
// 初始化rem与动态适配,调用一次即可
/*
* @param designWidth - your ui design-width
* @param baseFontSize - default root font-size
*/
const initRem = () => {
const docEl: HTMLElement = window.document.documentElement;
const initFontSize = () => {
const clientWidth = docEl.clientWidth;
if (!clientWidth) {
return;
}
// font-size计算逻辑
// ...
};
//根据设计稿设置HTML字体大小
const recalc = debounce(initFontSize, 100);
initFontSize();
window.addEventListener('resize', recalc, false);
//页面显示时计算一次
window.addEventListener(
'pageshow',
function (e: any) {
if (e.persisted) {
initFontSize();
}
},
false,
);
};
- 编写font-size计算逻辑代码
// src/utils/rem.ts
const BASE_FONT_SIZE = 100 // root font-size 基准值常量
const DESIGN_WIDTH = 750 // 设计稿宽度常量
// ...
if (clientWidth >= DESIGN_WIDTH) {
// 限制最大宽度
docEl.style.fontSize = `${BASE_FONT_SIZE}px`;
} else {
// compute计算rem值,在这里需要乘上BASE_FONT_SIZE转换为font-size值,传入第二个参数是为了compute方法复用
docEl.style.fontSize = `${
compute(BASE_FONT_SIZE, true) * BASE_FONT_SIZE
}px`;
}
// ...
// compute rem
/*
* @param px - 需要转换的px值
* @param isInit - 是否是初始化时。该方法会用于后续其他地方,这里做了逻辑复用
*/
const compute = (px: number, isInit: boolean = false): number => {
const scale =
window.document.documentElement.clientWidth /
DESIGN_WIDTH;
// 初始化(重新计算根元素大小)时,总是取在设计稿宽度下的基准值*比例
let size: string | number = isInit
? BASE_FONT_SIZE + 'px'
: BASE_FONT_SIZE * scale + 'px';
size = Number(size.replace('px', ''));
return Number(((px / size) * scale).toFixed(2)); //保留两位,最后得到rem值
};
小结:至此,我们完成了核心部分:动态设置root font-size、编写compute计算rem值。但是为了能在js中声明式的获取rem值,我们还需要暴露getRem方法,简化获取方式。
js使用之getRem方法
我们首先来看看,通过以上的代码处理后,我们在浏览器环境下就已经可以直接使用rem单位了,随着视窗改变root font-size会重新计算,导致rem真实大小改变,而且很方便的看出在设计稿宽度下1rem的真实大小。
js:
// src/main.js
import {initRem} from './index.esm.js'
initRem() // 初始化
const dom = document.createElement('div')
dom.id='root'
// 在750宽设计稿下,100px就相当于1rem,px缩小100倍就是rem的值
dom.innerHTML = `
<p style="font-size:1rem">Hello World!</p>
`
document.body.appendChild(dom)
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
a {
font-size:1rem;
}
</style>
</head>
<body>
<a href="https://github.com/GisonL">14</a>
<script src="./index.esm.js" type="module"></script> <!-- 我们上面实现的方法 -->
<script src="./main.js" type="module"></script>
</body>
</html>
react or vue:
// src/app.ts
initRem() // 初始化
// src/pages/test.tsx
export default ()=><div style={{fontSize:'1rem'}}>Hello World!</div>
为了更加方便拓展以及语义化,我们新增一个getRem方法。
// src/utils/rem.ts
const UNIT = 'rem'
interface GetRemOptions {
suffix?: boolean; // 是否带有后缀单位
}
// 通过px获取rem大小
const getRem = (px: number | string, options?: GetRemOptions): string => {
let unit = UNIT;
if (options && !options.suffix) unit = '';
return this.compute(Number(px)) + unit;
};
使用(react版,其他类似):
// src/app.ts
initRem() // 初始化
// src/pages/test.tsx
// import {useState} from 'react;
export default ()=>{
const [flag,setFlag] = useState<boolean>(false)
return <div style={{fontSize:getRem(100+200)}} onClick={()=>setFlag(!flag)}>
Hello World!
{{getRem(flag?100:200,{suffix:false})}}
</div>
}
// $a = '3rem', $b = '1'|'2'
小结:通过封装一个getRem方法,使得我们可以在js中更优雅、方便、易于扩展、语义化的使用rem特性。
css使用之postcss + postcss-pxtorem
前面我们处理了js中的方法,接下来我们同样希望在css中使用px编码,但会自动转换为rem单位。
其实这部分能力已经有相应的库提供了,我们选用postcss相关的生态,这里我们只需要配置即可。
- 安装依赖
npm install post-css postcss-pxtorem postcss-loader -D
- 配置postcss-loader
// webpack.config.js,非webpack配置方式请参考官方文档
module.exports = {
// ...
rules: [
{
test: /\.css$/,
use: ['style-loader', 'postcss-loader'],
},
]
}
- 配置postcss-pxtorem插件
// postcss.config.js,参考
const px2rem = require('postcss-pxtorem')
module.exports = {
// ...
plugins: [
px2rem({
//根元素字体大小,与px2rem2js实例化入参baseFontSize保持一致,这里我使用环境变量取值
rootValue: process.env.BASE_FONT_SIZE,
//匹配CSS中的属性,* 代表启用所有属性
propList: ['*'],
//转换成rem后保留的小数点位数
unitPrecision: 5,
// 忽略一些文件,不进行转换。项目中我使用的是ant-design-mobile,需要转换,因此我注释掉了
// exclude: ['node_modules']
}),
]
}
小结:如此,在我们的css文件中,就可以直接编写px单位,而且也是根据设计稿的大小来写就可以了。postcss-loader会根据配置的插件,在编译时就帮我们转换为rem单位,而我们封装的初始化方法会自动修改1rem的值。因此终于做到了在js+css中,以设计稿宽度直接书写对应的px大小,实现rem转换+适配。
使用姿势
px2rem2js
前面在css中我们通过postcss生态处理了css中的转换。在js中为了更方便的使用初始化和转换功能,以及提高多项目之间的可维护性,提高能效,降低重复开发成本。我封装了一个可以直接使用的简易开源库,用法简单高效。大家可以直接引入到项目中加上postcss的处理即可安心食用。欢迎踊跃发起issues。
px2rem2js:https://github.com/GisonL/px2rem2js
简单码下使用方式:
安装
npm install --save px2rem2js
初始化一个简单的js适配场景
// src/utils/rem.ts
import px2rem2js from 'px2rem2js';
// global default option: {designWidth:750,baseFontSize:100,suffix:true,context:window}
const p2r2js = new px2rem2js()
export const initRem = p2r2js.initRem
// current default option: {suffix:global.default.option.suffix}
export const getRem = p2r2js.getRem
export default p2r2js
// src/app.ts
import { initRem } from '@/utils/rem.ts'
initRem()
// src/pages/home.tsx
import { getRem } from '@/utils/rem.ts'
export default () => {
return <div style={{width:getRem(100),height:getRem(100),fontSize:getRem(16)}}>Hello World!!</div>
}
css参考前面postcss篇章进行配置,保证baseFontSize与rootValue一致即可,其他根据喜好配置。
痛点的解决
通过全篇介绍+使用案例,我们了解到:
- rem单位的原理和作用
- rem适配、设计稿、实际视窗大小的关系,并得出了式子
- 根据式子,完成了动态初始化root font-size的方法initRem(),以及更为方便的在js中使用rem的方法getRem()
- 通过postcss生态完成了css编译时 px => rem 的自动转换
- 多项目之间通过接入px2rem2js进行js中rem初始化和获取rem的相关方法的统一与维护
最后
我是14,一名5+工作经验、base成都的前端工程狮。期待交流与合作
有正在遇到的问题可以打在评论区,我都会看,之后会不定时更新同系列文章,欢迎分享你的小故事
谢谢你的点赞与收藏,只因你太美🐔
转载自:https://juejin.cn/post/7200282892348833851