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

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

相关推荐
2分钟速写快排23 分钟前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60611 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角2 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi4 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马5 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒6 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端
乘风gg6 小时前
DeepSeek V4.1 Flash 来了,明天中午 Flash 降价 60%
前端·ai编程·claude