rem和em单位详解:从原理到实战的前端适配指南

设计师珂簪 移动 阅读 2,983
赞 4 收藏
二维码
手机扫码查看
反馈

先上代码,再聊原理

做移动端项目时,我基本不用 px 写布局了。不是不能用,是太麻烦——不同屏幕尺寸下,字体、间距、容器大小全得手动调,累死。后来我直接上 rem,配合一段 JS 动态设置根字体大小,一套样式跑遍所有机型,亲测有效。

rem和em单位详解:从原理到实战的前端适配指南

核心就这一段 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,动态设置 htmlfont-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 做混合方案),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论