likes
comments
collection
share

【只因】还在为rem配置苦恼?一个库解放你的双手,只因你太美🐔

作者站长头像
站长
· 阅读数 79

思考

随着工作经验不断积累,工作职责逐渐扩大。除了业务开发和团队管理,还需要考虑如何通过前端工程化、基建设施,有效的提高能效,降低成本,减少事故,增强协同。从工程师角度,将现有技术与业务融会贯通,相辅相成。接下来在【只因】专栏中,作者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

【只因】还在为rem配置苦恼?一个库解放你的双手,只因你太美🐔

小结:不论root font-size 是多少px,1rem的实际px值就等于这个值。在实际应用中我们希望通过只修改root font-size,影响1rem的实际大小。因此从代码中看,变的是root font-size,不变的是1rem。记住这点很重要!

了解rem适配、设计稿、实际视窗的关系

背景:项目设计稿宽度为750px,有一个元素A宽度为100px

目的:假设在375px宽的视窗下,该元素A所展示的效果,需要和750px上的效果保持一致。其他宽度视窗同理。

明确了背景和目的后,我们进一步梳理1rem、设计稿、实际视窗的关系。

  1. 为了方便理解,我们称设计稿宽度为designWidth,实际视窗宽度为clientWidth,root font-size为fz,designWidth下元素A的px宽度为adWidth,clientWidth下元素A的px宽度为acWidth。为了方便计算,我们首先默认在designWidth下,fz = 100px

  2. 已知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。式子依然成立!其他视窗大小不影响式子!

小结:根据我们的推导,我们明白了如果要达成目的其实很简单只有两点,也不需要记这么多的式子和关系。只需要做到以下几点:

  1. 首先,我们在代码中写死具体的rem值。而为了方便计算设计稿上100px=?rem的问题,我们默认设置100px=1rem。这样就能一眼看出来,当要写50px时,实际就写0.5rem,当要写200px时,实际就写2rem。
  2. 其次,我们需要动态的设置fz的值,从而影响1rem的实际大小,来达成原有的比例。

部分核心实现思路

动态初始化root font-size

我们先来实现第二点,这里我以750px的设计稿为基准。

  1. 封装一个方法用来监听视窗改变设置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,
    );
  };
  1. 编写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>

【只因】还在为rem配置苦恼?一个库解放你的双手,只因你太美🐔

为了更加方便拓展以及语义化,我们新增一个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相关的生态,这里我们只需要配置即可。

  1. 安装依赖
npm install post-css postcss-pxtorem postcss-loader -D
  1. 配置postcss-loader
  // webpack.config.js,非webpack配置方式请参考官方文档
  module.exports = {
    // ...
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'postcss-loader'],
      },
    ]
  }
  1. 配置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一致即可,其他根据喜好配置。

痛点的解决

通过全篇介绍+使用案例,我们了解到:

  1. rem单位的原理和作用
  2. rem适配、设计稿、实际视窗大小的关系,并得出了式子
  3. 根据式子,完成了动态初始化root font-size的方法initRem(),以及更为方便的在js中使用rem的方法getRem()
  4. 通过postcss生态完成了css编译时 px => rem 的自动转换
  5. 多项目之间通过接入px2rem2js进行js中rem初始化和获取rem的相关方法的统一与维护

最后

我是14,一名5+工作经验、base成都的前端工程狮。期待交流与合作

有正在遇到的问题可以打在评论区,我都会看,之后会不定时更新同系列文章,欢迎分享你的小故事

谢谢你的点赞与收藏,只因你太美🐔

转载自:https://juejin.cn/post/7200282892348833851
评论
请登录