rem是相对于根元素font-size的单位,1rem默认为16px,通过动态设置document.documentElement.style.fontSize实现响应式;em则相对于父元素font-size,易因嵌套连乘失控,适合局部适配;clamp()提供无js、无FOUC的响应式方案,但需考虑兼容性。

rem 是相对于根元素 font-size 的单位
浏览器默认根元素 的 font-size 是 16px,所以 1rem 默认等于 16px。只要动态改 document.documentElement.style.fontSize,所有用 rem 的尺寸就同步缩放——这是实现响应式字体和间距的核心机制。
常见做法是在 中插入一段 JS,根据屏幕宽度按比例设置根字号,比如:
function setRootFontSize() { const width = document.documentElement.clientWidth; const base = 375; // 设计稿宽度 const scale = width / base; document.documentElement.style.fontSize = `${scale * 16}px`; } setRootFontSize(); window.addEventListener('resize', setRootFontSize);
注意:这段逻辑必须在 dom 加载前或早期执行,否则页面可能闪动;如果用 webpack/vite,建议封装为自执行函数并确保早于 css 加载。
em 是相对于父元素 font-size 的单位
em 的值取决于**直接父元素**的 font-size,逐层继承、容易嵌套失控。比如父元素是 1.2em(即 19.2px),子元素写 1.2em 就变成 19.2 × 1.2 ≈ 23px,再下一层继续乘——这不是“缩放”,而是“连乘”,不适合全局响应式控制。
立即学习“前端免费学习笔记(深入)”;
但 em 在局部场景仍有价值:
- 按钮内边距跟随文字大小变化:
padding: 0.5em 1em; - 图标尺寸与文字对齐:
font-size: 1.2em;配合line-height: 1; - 伪元素尺寸适配文字:
::before { font-size: 0.8em; }
别用 em 控制布局容器宽高,尤其在 flex/grid 容器里,会因父级字号变动导致意料外的缩放链。
rem + viewport 缩放容易踩的坑
很多方案同时设置 和动态 rem,但 ios safari 在横竖屏切换时可能触发双倍缩放,导致 clientWidth 计算失真。
规避方法:
- 避免在
viewport中写maximum-scale或user-scalable=no,它们会干扰系统缩放逻辑 - 用
window.innerWidth替代document.documentElement.clientWidth做计算基准(更稳定) - 监听
orientationchange事件,而非仅靠resize - 对 PC 端做降级:媒体查询判断
min-width: 768px后固定根字号为16px,防止桌面端误缩放
现代项目建议用 clamp() 替代纯 rem 方案
CSS clamp() 能在最小值、首选值、最大值之间平滑过渡,比 JS 动态改根字号更轻量、无 FOUC、支持服务端渲染:
p { font-size: clamp(14px, 2.5vw, 18px); } .card { padding: clamp(12px, 3vw, 24px); }
但要注意兼容性:clamp() 在 Safari 13.1+、chrome 88+、firefox 79+ 支持;老版本需 fallback 到 rem 或媒体查询。
真正复杂的地方不在单位本身,而在「何时该用 rem、何时该用 em、何时该放弃它们直接上视口单位或 clamp」——这取决于你是否需要 javaScript 参与控制、是否要兼顾 SSR、以及设计系统对缩放一致性的要求程度。