我猜很多人只是会配置,但不明白 postcss-pxtorem 中的 rootValue 配置项是什么意思

2,351 阅读6分钟

为什么我敢说很多人呢?因为这确实有点难理解,而且官网也没有明确指出该怎么使用。(虽然这个数据是我没经过调查乱说的,不过如果看完后不知道的同学可以评论区抠 1,知道的抠 2,咱们统计一下!)

记得很久以前(大概两年),我就对这个 postcss-pxtotem 插件很疑惑不解,为什么要刚好设置 rootValue 为设计稿的十分之一呢?为什么又要配合 amfe-flexible 库使用呢?奈何当时水平太菜(现在也是),只能百思不得其解然后放弃。

两年后的今天,我算是想明白了。然后就写下了这篇文章,来给同样有疑惑的同学提供下参考答案。

注:本文并没有探究 postcss-pxtorem 的源码(不会),而是通过另外一种方式推测了解其实现,所以大家可以放轻松的观看。

背景介绍

postcss-pxtorem 这个库总是搭配 amfe-flexible 一起使用。目的是在不同大小手机的屏幕上成比例的还原设计稿。

未命名文件.jpg

postcss-pxtorem 是 postcss 的一个插件。以 vite 创建的 react 项目为例,使用它需要先下载

npm install postcss postcss-pxtorem --save-dev

然后在根目录下创建 postcss.config.cjs 文件并写入内容。pnpm dev 之后打开浏览器,会发现你写的 css 的单位 px 都被转为 rem 了。

// eslint-disable-next-line no-undef
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      // =========???????=========
      rootValue: 75, 
      // 允许 px 转换为 rem 精确到小数点后几位
      unitPrecision: 5, 
      // 存储哪些将被转换的属性列表,这里设置为 ['*'] 全部。
      propList: ['*'], 
      // 对 css 选择器进行忽略的数组。
      // 比如你设置为 ['el-'],那所有el-类名里面有关 px 的样式将不被转换
      selectorBlackList: [], 
      // 媒体查询( @media screen 之类的)中不生效
      mediaQuery: false, 
      // px 绝对值小于 0 的不会被转换
      minPixelValue: 0, 
      // 忽略转换的文件路径
      exclude: /node_modules/i,
    },
  },
}

这上面的配置中,最重要且难以让人搞懂的配置项就是 rootValue 了。

所以下面,我将用实际的代码一步步让你明白这个配置项的意思到底是啥。

先说结论

看不懂没关系,先把后面看完再回过头来看就理解了。


((你写的 css 代码中的 数字) / (postcss-pxtorem 配置的 rootValue)) * 根元素字体大小(默认 16px) === 浏览器中显示的数字 80

// 写简洁点就是
(css 中的单位数字 / rootValue) * 根元素字体大小 === 浏览器实际显示单位数字

再写简洁点:

css单位数字×根元素字体大小rootValue===浏览器显示数字\frac{css单位数字 \times 根元素字体大小}{rootValue} === 浏览器显示数字

举例说明

项目的 App.tsx 文件中

import './App.css'

function App() {
  return (
    <div className='title'>hell,world</div>
  )
}

export default App

项目的 App.css 文件中

body,html {
  padding: 0;
  margin: 0;
}

.title {
  width: 375px;
  background-color: red;
}

然后 postcss-pxtorem 配置的 rootValue 为 75。

此时打开浏览器,发现

image.png

在上图用红线提醒的三个地方,之间有什么关联呢?哈哈,我猜很多人都不知道吧。因为两年前直到今天之前,我也不知道!没事,看了这篇文章之后,你就知道了!

你会发现,他们之间满足一个关系

((你写的 css 代码中的 数字) / (postcss-pxtorem 配置的 rootValue)) * 根元素字体大小(默认 16px) === 浏览器中显示的数字 80

// 写简洁点就是
(css 中的单位数字 / rootValue) * 根元素字体大小(12) === 80

算一下,(375 / 75) * 16 刚好等于 80。

如果你不信,可以自己去尝试。要注意修改了 rootValue 后项目需要重启。

成比例的还原设计稿

知道了上面的公式后,如何做到不同大小手机成比例的还原设计稿呢?

这里分情况简单说一下。

假设设计稿为 375px 宽

可以将 rootValue 设置为 37.5,根元素字体大小设置为屏幕宽度的十分之一。比如说 iPhone12 Pro 的屏幕宽度为 390px,那么我们就可以设置成 39px。

document.documentElement.style.fontSize = '39px'

重启运行,会发现盒子刚好占满了屏幕宽度。

image.png

甚至可以将 rootValue 设置为 375,根元素字体大小设置为屏幕的宽度。但是这样的话不好与后面介绍的 amfe-flexible 库配合使用。但是你可以这样试试,加深自己的理解!

假设设计稿为 750px 宽

可以将 rootValue 设置为 75,更元素大小大小设置为屏幕宽度的十分之一。比如说 iPhone XR 的屏幕宽度为 414px,那么我们就可以设置成 41.4px。

在项目的入口文件

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

+ document.documentElement.style.fontSize = '41.4px'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

然后修改下 css 文件

body,html {
  padding: 0;
  margin: 0;
}

.title {
+  width: 750px;
  background-color: red;
}

重启运行,会发现盒子还是刚好占满了屏幕宽度。

image.png

动态获取用户手机宽度

上面举例的时候都把根元素字体大小写死了,实际上不同用户的手机大小不一样,所以得动态获取屏幕宽度,然后设置字体大小为屏幕宽度的十分之一。

可以在入口导入下面文件:

(function flexible (window, document) {
  var docEl = document.documentElement

  // 设置 documentElement 的 font-size。为屏幕宽度的十分之一。
  function setRemUnit () {
    var rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
  }
  setRemUnit()

  // 窗口大小改变,就重新调用 setRemUnit 方法
  window.addEventListener('resize', setRemUnit)
}(window, document))

这样你就可以为所欲为的改变屏幕宽度大小啦,不用担心内容的排版会变乱,它会根据宽度自动缩放的!

2023-02-26-22-17-06.gif

amfe-flexible

本文开头说过,postcss-pxtorem 总是搭配 amfe-flexible 一起使用。(在配置好 postcss-pxtorem 后,再在入口文件引入 amfe-flexible)。

import 'amfe-flexible'

其实 amfe-flexible 的源码也特别简单。总共也就 43 行代码,主要做的事情就是动态获取屏幕宽度,然后设置字体大小为屏幕宽度的十分之一。

(function flexible (window, document) {
  var docEl = document.documentElement
  // 设备像素比
  var dpr = window.devicePixelRatio || 1

  // 设置 body 的 font-size
  // 这里根据 dpr 设置 body 标签的字体大小,是为了设置默认继承的字体大小。
  // 否则默认的字体大小会非常大。
  function setBodyFontSize () {
    if (document.body) {
      document.body.style.fontSize = (12 * dpr) + 'px'
    }
    else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize)
    }
  }
  setBodyFontSize();

  // 设置 documentElement 的 font-size
  function setRemUnit () {
    var rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
  }
  setRemUnit()

  // 窗口大小改变或页面是从缓存中加载,就重新调用 setRemUnit
  window.addEventListener('resize', setRemUnit)
  window.addEventListener('pageshow', function (e) {
    if (e.persisted) { // 如果页面是从缓存中加载
      setRemUnit()
    }
  })

  // 检测当前设备是否支持 0.5px。如果支持,docEl 将会增加类名 hairlines
  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))

结尾

最后,通过上面的举例论证,得出了一个公式,在文章开头已经给出了。

不知道各位同学都能理解么?如果还有点迷糊,建议自己去实践一下,加深理解!