rem和em单位详解:从原理到实战的前端适配指南
先上代码,再聊原理
做移动端项目时,我基本不用 px 写布局了。不是不能用,是太麻烦——不同屏幕尺寸下,字体、间距、容器大小全得手动调,累死。后来我直接上 rem,配合一段 JS 动态设置根字体大小,一套样式跑遍所有机型,亲测有效。
核心就这一段 JS(放在 <head> 里,越早执行越好):
(function() {
function setRootFontSize() {
const width = document.documentElement.clientWidth || document.body.clientWidth;
// 以 375px 为基准,1rem = 16px
const rootFontSize = (width / 375) * 16;
document.documentElement.style.fontSize = rootFontSize + 'px';
}
setRootFontSize();
window.addEventListener('resize', setRootFontSize);
})();
然后 CSS 里这么写:
body {
font-size: 0.875rem; /* 相当于 14px(16 * 0.875) */
}
.container {
width: 100%;
padding: 1rem; /* 16px */
margin-top: 0.5rem; /* 8px */
}
效果?iPhone 13 上看着舒服,Redmi Note 12 上也一样比例缩放,根本不用管具体多少像素。这招我从 2019 年用到现在,项目交接给新人,他们都说“这布局怎么这么稳”。
这个场景最好用:全局字体和间距
我一般只在两类地方用 rem:一是全局字体大小,二是组件的内边距、外边距、容器宽高。比如按钮、卡片、列表项这些,用 rem 能保证视觉比例一致。
但注意:**图片、图标、固定尺寸元素别用 rem**。比如一个 40×40 的头像,你写成 2.5rem,在小屏上可能变成 30×30,糊了。这种固定尺寸,老老实实用 px 或者 vw/vh 更稳妥。
另外,em 呢?我用得少,但有个场景特别合适:**嵌套文本的缩进或行高**。比如引用块、评论回复的层级,用 em 可以基于父级字体大小自动缩放,避免写死。
.comment {
font-size: 0.875rem;
padding-left: 1em; /* 基于当前字体大小,约 14px */
}
.comment .reply {
font-size: 0.875rem; /* 和父级一样 */
padding-left: 1em; /* 依然基于自身字体,保持缩进一致 */
}
如果这里用 rem,缩进会固定为根字体大小的比例,一旦全局字体调整,缩进可能过大或过小。而 em 能“继承上下文”,更灵活。
踩坑提醒:这三点一定注意
别看上面代码简单,我踩过的坑可不少。列几个最痛的:
- 不要忘记监听 resize:虽然移动端横竖屏切换不频繁,但万一用户旋转了呢?而且有些安卓机在软键盘弹出时会触发 resize,导致布局错乱。加上监听,一劳永逸。
- 慎用 1rem = 100px 的方案:网上很多教程喜欢设
font-size: 100px,然后写0.16rem表示 16px。数字看着整齐,但实际开发时心算成本高,容易写错。我试过两周,最后还是改回 1rem = 16px,更符合直觉。 - 第三方 UI 库可能冲突:比如你引入了某个移动端组件库,它内部用的是
px,而你的全局用了rem,结果按钮文字忽大忽小。解决办法:要么 fork 库改成 rem,要么用 CSS 变量隔离,或者干脆在引入的组件上加font-size: 14px !important强制覆盖(不优雅但快)。
还有个小细节:Safari 在 iOS 15+ 有个 bug,动态设置 html 的 font-size 后,部分元素不会立即重绘。我的 workaround 是在 JS 末尾加一行强制重排:
// 在 setRootFontSize 最后加
document.body.style.display = 'none';
document.body.offsetHeight; // 触发重排
document.body.style.display = '';
虽然丑,但有效。苹果至今没修,只能这样凑合。
高级技巧:结合 CSS 变量做双模式
最近做的一个项目要求同时支持“正常模式”和“大字模式”(给老年人用)。我不想维护两套样式,于是用 rem + CSS 变量搞了个开关:
:root {
--base-font-size: 16px;
}
html {
font-size: var(--base-font-size);
}
/* 大字模式 */
html.large-text {
--base-font-size: 20px;
}
// 切换模式
function toggleLargeText(enable) {
if (enable) {
document.documentElement.classList.add('large-text');
} else {
document.documentElement.classList.remove('large-text');
}
// 重新计算 rem(因为 base 变了)
setRootFontSize(); // 复用前面的函数
}
这样,所有用 rem 的地方自动放大,而 px 的元素(比如图标)保持不变。用户点一下“大字模式”,整个界面就变清晰了,体验提升明显。
em 的隐藏用法:媒体查询里的相对单位
很多人不知道,em 在媒体查询里也能用,而且比 px 更健壮。比如:
@media (max-width: 48em) {
/* 48em = 48 * 16px = 768px(假设根字体 16px) */
.sidebar {
display: none;
}
}
为什么用 em?因为如果用户在浏览器里手动放大了页面(比如 Chrome 的 zoom),用 px 的媒体查询不会触发,但 em 会。这对无障碍访问很重要。不过要注意:这里的 em 是基于根字体大小,不是当前元素。
最后说两句
rem/em 不是银弹,但在移动端响应式布局里,它们确实能省下大量适配时间。我现在的项目,90% 的尺寸都用 rem,剩下 10% 固定尺寸用 px,配合上述 JS 方案,基本没出过兼容性问题。
当然,如果你用的是现代框架(比如 Vue/React + Vite),也可以考虑用 postcss-pxtorem 这类插件,写 px 自动转 rem。但我觉得直接写 rem 更直观,调试时也不用猜“这个 16px 到底转成多少 rem”。
以上是我踩坑后的总结,希望对你有帮助。这个技巧的拓展用法还有很多(比如结合 vw 做混合方案),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。

暂无评论