rem是相对于根元素font-size的单位,1rem默认为16px,通过动态设置document.documentElement.style.fontSize实现响应式;em则相对于父元素font-size,易因嵌套连乘失控,适合局部适配;clamp()提供无JS、无FOUC的响应式方案,但需考虑兼容性。
浏览器默认根元素 的 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,逐层继承、容易嵌套失控。比如父元素是 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,但 iOS Safari 在横竖屏切换时可能触发双倍缩放,导致 clientWidth 计算失真。
规避方法:
viewport 中写 maximum-scale 或 user-scalable=no,它们会干扰系统缩放逻辑window.innerWidth 替代 document.documentElement.clientWidth 做计算基准(更稳定)orientationchange 事件,而非仅靠 resize
min-width: 768px 后固定根字号为 16px,防止桌面端误缩放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、以及设计系统对缩放一致性的要求程度。