把「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.orientationchangepageshow,双保险。

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 之间找到最舒服的那个点"。

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

相关推荐
Csvn37 分钟前
🧩「找不到模块」排查全记录——Monorepo 下 TypeScript 路径别名的 5 种「不通」与根治方案
前端
腻害兔44 分钟前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:字典、短信、邮件、通知——后台系统的“基础设施四件套“!
java·前端·vue.js·产品经理·ai编程
CodexDave1 小时前
MySQL事务隔离级别与MVCC机制解析
前端·数据库·mysql·nginx·性能优化·负载均衡
Ai_easygo2 小时前
AI Agent开发入门——从ReAct到Tool Calling,拆解Agent的底层运行逻辑
前端·人工智能·react.js
研☆香2 小时前
分析制作html页面,如何划分页面结构
前端
腻害兔2 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:支付模块 yudao-module-pay,一个让产品经理都看懂的支付中台设计
java·前端·vue.js·产品经理·ai编程
kyriewen2 小时前
我扒了最近的前端面经——2026年面试不背八股文了,考这5样
前端·面试·ai编程
IT_陈寒3 小时前
Redis的持久化配置把我坑惨了:你以为数据安全了?
前端·人工智能·后端
小徐_23333 小时前
uni-app 项目别再从零搭了!3 个 Wot UI 起手模板怎么选?
前端·uni-app
赵庆明老师4 小时前
Vben精讲:16-熟悉Vite前端工具链
前端