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

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

相关推荐
CF14年老兵26 分钟前
5 个最佳工具,可立即从代码生成 API 文档
前端·后端·api
♡喜欢做梦31 分钟前
HTML 与 CSS:从 “认识标签” 到 “美化页面” 的入门指南
前端·html
前端小巷子41 分钟前
Vue脚手架模式与环境变量
前端·vue.js·面试
CF14年老兵1 小时前
99% 的前端开发者忽略了这个 React 性能利器
前端·react.js·trae
麓殇⊙3 小时前
redis--黑马点评--用户签到模块详解
前端·数据库·redis
大雷神4 小时前
站在JS的角度,看鸿蒙中的ArkTs
开发语言·前端·javascript·harmonyos
杨荧9 小时前
基于大数据的美食视频播放数据可视化系统 Python+Django+Vue.js
大数据·前端·javascript·vue.js·spring boot·后端·python
cmdyu_10 小时前
如何解决用阿里云效流水线持续集成部署Nuxt静态应用时流程卡住,进行不下去的问题
前端·经验分享·ci/cd
WordPress学习笔记10 小时前
根据浏览器语言判断wordpress访问不同语言的站点
前端·javascript·html
yuanmenglxb200410 小时前
解锁webpack核心技能(二):配置文件和devtool配置指南
前端·webpack·前端工程化