把「1920 ≠ 375」写进一行代码——前端响应式 rem 方案实战与踩坑记

为什么要自己造轮子?

市面上现成的 lib/flexible、postcss-px-to-rem 都很好,但我们的项目有三个"小脾气":

  1. 设计稿宽度随业务而变:PC 1920、PAD 768、Mobile 375,中间还有 590 的"折叠屏"。
  2. 设计师坚持「文字在任何视口都不能小于 12px,最大不超过 42px」。
  3. 项目历史包袱:大量旧页面仍用 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 之间找到最舒服的那个点"。

希望这篇小记能帮你少踩一个坑,早点下班去撸猫 🐱

相关推荐
前端snow17 小时前
ai agent --- postgreSQL 关系型数据库
前端
卷无止境18 小时前
ECharts:把数据变成故事的可视化利器
前端
Eric_见嘉18 小时前
在职前端 Skill 和 MCP 分享
前端·后端·agent
liangshanbo121519 小时前
面试题:如何优化 Webpack 的打包速度?
前端·webpack·node.js
wengad19 小时前
从 wangEditor 迁到 wangEditor-next
前端·javascript·vue.js
行者-全栈开发20 小时前
【前端/JS框架】CVE-2026-3854:GitHub Enterprise Server X-Stat注入XSS漏洞深度剖析与紧急修复指南
前端·javascript·github
计算机魔术师21 小时前
AMD砸82亿买下李飞飞,这次不卖芯片卖什么?
前端
hzxpaipai21 小时前
杭州企业官网怎么建设?从策划到上线的5步建站方法
运维·服务器·前端·网络
怕浪猫21 小时前
2026年金九银十,我面了22个前端
前端·javascript·面试
IT_陈寒1 天前
Vue 这个响应式陷阱,我的头发都掉没了
前端·人工智能·后端