为什么要自己造轮子?
市面上现成的 lib/flexible、postcss-px-to-rem 都很好,但我们的项目有三个"小脾气":
- 设计稿宽度随业务而变:PC 1920、PAD 768、Mobile 375,中间还有 590 的"折叠屏"。
- 设计师坚持「文字在任何视口都不能小于 12px,最大不超过 42px」。
- 项目历史包袱:大量旧页面仍用 px,需要渐进式迁移。
于是,我们决定手写一个"极简、渐进、可插拔"的响应式脚本,并把它封装成 <RemScaler />
组件,随 React / Vue / 原生项目都能即插即用。
代码 30 行,原理 3 句话
js
// RemScaler.js
(function (win) {
const doc = win.document
const docEl = doc.documentElement
const baseSize = 14 // 14px = 1rem
const maxScale = 3 // 防止大屏字体爆炸
function setRem () {
const clientWidth = docEl.clientWidth
// 1. 先选「设计稿宽度」
let designWidth = 1920
if (clientWidth < 500) designWidth = 375
else if (clientWidth < 769) designWidth = 590
// 2. 再算「缩放比例」
const scale = Math.min(clientWidth / designWidth, maxScale)
// 3. 最后把比例变成 rem
docEl.style.fontSize = `${baseSize * scale}px`
}
setRem()
win.addEventListener('resize', setRem, { passive: true })
})(window)
为什么要分段写 designWidth?
视口宽度 | 设计稿宽度 | 场景举例 |
---|---|---|
< 500 | 375 | 手机竖屏 |
500--768 | 590 | Pad 竖屏、折叠屏 |
≥ 769 | 1920 | PC、横屏 Pad |
这样划分后,任何设备看到的 1rem 都是"最接近设计稿比例"的物理大小,不会在小屏上字太小,也不会在大屏上字太大。
三步接入
1. 把脚本丢进项目
bash
src/
├── utils/
│ └── RemScaler.js // 上面那段
└── main.js // 入口
2. 入口文件里 import
js
// React 项目
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './utils/RemScaler' // 只需 import 一次
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
Vue、Angular、Svelte 都一样,只要在首屏之前跑起来即可。
3. 写样式
css
/* 设计稿 1920 下宽度 300px */
.card {
width: 21.4286rem; /* 300 / 14 = 21.4286 */
font-size: 1rem; /* 14px */
}
踩坑与补丁
坑 1:SSR 时 window is not defined
解决:加一个判断,Node 环境不执行。
js
if (typeof window !== 'undefined') {
(function (win) { ... })(window)
}
坑 2:用户手动缩放浏览器字体
解决:监听 window.orientationchange
与 pageshow
,双保险。
js
;['resize', 'orientationchange', 'pageshow'].forEach(evt =>
win.addEventListener(evt, setRem, { passive: true })
)
坑 3:横竖屏切换时闪一下
解决:在 setRem
里加 100ms 节流。
js
let tid
function setRem() {
clearTimeout(tid)
tid = setTimeout(() => { /* 原逻辑 */ }, 100)
}
进阶:把变量暴露出去,让业务层可配置
js
window.RemScaler = {
setBaseSize: (size) => { baseSize = size; setRem() },
setMaxScale: (scale) => { maxScale = scale; setRem() }
}
业务代码里随时改:
js
// 大屏活动页,允许字体放大 4 倍
window.RemScaler.setMaxScale(4)
总结
- 30 行代码搞定"多设计稿 + 渐进迁移"的响应式方案。
- 不依赖 webpack / vite 插件,无构建成本。
- 可插拔:迁移完成后直接删掉
import './RemScaler'
即可回到纯 rem 方案。
响应式不是"把 1920 压成 375",而是"在 1920 与 375 之间找到最舒服的那个点"。
希望这篇小记能帮你少踩一个坑,早点下班去撸猫 🐱