为什么我敢说很多人呢?因为这确实有点难理解,而且官网也没有明确指出该怎么使用。(虽然这个数据是我没经过调查乱说的,不过如果看完后不知道的同学可以评论区抠 1,知道的抠 2,咱们统计一下!)
记得很久以前(大概两年),我就对这个 postcss-pxtotem 插件很疑惑不解,为什么要刚好设置 rootValue 为设计稿的十分之一呢?为什么又要配合 amfe-flexible 库使用呢?奈何当时水平太菜(现在也是),只能百思不得其解然后放弃。
两年后的今天,我算是想明白了。然后就写下了这篇文章,来给同样有疑惑的同学提供下参考答案。
注:本文并没有探究 postcss-pxtorem 的源码(不会),而是通过另外一种方式推测了解其实现,所以大家可以放轻松的观看。
背景介绍
postcss-pxtorem 这个库总是搭配 amfe-flexible 一起使用。目的是在不同大小手机的屏幕上成比例的还原设计稿。
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) * 根元素字体大小 === 浏览器实际显示单位数字
再写简洁点:
举例说明
项目的 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。
此时打开浏览器,发现
在上图用红线提醒的三个地方,之间有什么关联呢?哈哈,我猜很多人都不知道吧。因为两年前直到今天之前,我也不知道!没事,看了这篇文章之后,你就知道了!
你会发现,他们之间满足一个关系:
((你写的 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'
重启运行,会发现盒子刚好占满了屏幕宽度。
甚至可以将 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;
}
重启运行,会发现盒子还是刚好占满了屏幕宽度。
动态获取用户手机宽度
上面举例的时候都把根元素字体大小写死了,实际上不同用户的手机大小不一样,所以得动态获取屏幕宽度,然后设置字体大小为屏幕宽度的十分之一。
可以在入口导入下面文件:
(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))
这样你就可以为所欲为的改变屏幕宽度大小啦,不用担心内容的排版会变乱,它会根据宽度自动缩放的!
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))
结尾
最后,通过上面的举例论证,得出了一个公式,在文章开头已经给出了。
不知道各位同学都能理解么?如果还有点迷糊,建议自己去实践一下,加深理解!