Skip to content
返回博客
教程

px、rem、em 怎么选:CSS 单位完全对比指南

px、rem、em 三个 CSS 单位的区别与选择:rem 为何利于无障碍、em 复合陷阱、媒体查询缩放 bug,附按属性决策表与换算心智模型。

12 分钟

px、rem、em 怎么选:CSS 单位完全对比指南

先把 px、rem、em 的结论摆在前面,再展开解释。绝大多数尺寸都用 rem(字号、内边距、外边距、间距、圆角和断点),因为它会随读者浏览器的字号设置一起缩放。极少数永远不该缩放的东西用 px,比如 1px 的边框或精确的阴影偏移。em 留给那种希望某个值随元素自身字号增长的局部场景,比如按钮内边距要跟着按钮文字走。

这条规则覆盖了 90% 的决策。剩下 10% 才是麻烦所在:第一次遇到时人人都会被绕进去的 em 复合计算、会在缩放时破坏布局的媒体查询 bug,以及那些 px 其实才是更无障碍选择的情况。本文会把这三点逐一讲透,每个都配可运行的 CSS,再附一张按属性分类的速查表,你写样式时可以一直开着。

px、rem、em 到底是什么

三个单位,三个不同的参照点。整个区别就在这里。

px 是绝对单位。1 个 px 就是 1 个 CSS 像素,无论周围环境如何,它始终保持那个尺寸。border: 1px solid 就是一个像素,没有例外。问题在于「绝对」也意味着它会无视用户的偏好设置,为什么这一点重要,后面再说。

rem 相对于**根(root)**元素的字号。根就是 <html>,浏览器默认把它的字号设为 16px。所以在标准设置下,页面任何地方的 1rem 都等于 16px,无论嵌套多深。这种一致性正是它好用的地方:一个锚点值,没有意外。

em 相对于当前元素的字号(对于非 font-size 属性,则相对于父级)。因为这个参照会随着元素嵌套而改变,所以 em 值会随上下文变化。同一个 1.5em,在一处可能解析成 24px,在另一处却是 30px

要记住的锚点是 16px = 1rem。如果别的都记不住,就记住这一条。当你需要换算某个具体值时,px 转 rem 工具会按你选定的任意基准做除法。

px、rem、em 速查对比

单位相对于谁是否随用户字号缩放?典型用途嵌套时的行为
px什么都不依赖(绝对)边框、阴影偏移、细线始终同一尺寸
rem<html> 字号字号、间距、断点始终同一尺寸
em当前元素的字号绑定到某个组件的局部值会复合,可能漂移

决定大多数争论的是「是否随用户字号缩放」和「嵌套时的行为」这两列。rem 两项都占优:它既尊重读者的偏好,又保持可预测。em 占了第一个好处,却牺牲了第二个。

每个单位怎么计算

数学就是普通算术。把人绊倒的是该除以或乘以哪个数。

rem 用根字号:

rem = px ÷ root-font-size

在默认的 16px 根字号下,24px ÷ 16 = 1.5rem。反过来则乘:1.5rem × 16 = 24px。页面上每个 rem 都用同一个 16(或你给根设的任何值),这正是 rem 可预测的原因。

em 用当前元素自身的字号:

em = px ÷ current-element-font-size

如果某个元素的 font-size20px,那么该元素上的 1em 就是 20px0.5em10px1.5em 的内边距是 30px。改变这个元素的字号,附着在它上面的每个 em 值都会跟着变。这种局部耦合正是 em 的用意,也是它的陷阱。

em 复合陷阱

这是竞品一笔带过的部分。当你嵌套的元素全都用 em 设字号时,这些值会沿着树往下相乘。每一层都继承父级的计算字号,再在上面套上自己的 em 系数。

.menu      { font-size: 1.2em; } /* 父级是 16px → 19.2px */
.menu .item { font-size: 1.2em; } /* 父级是 19.2px → 23.04px */
.menu .item .sub { font-size: 1.2em; } /* 父级是 23.04px → 27.648px */

每一层都是「父级的 120%」,听起来无害。但因为父级本身已经长大,第三层相对于最初的 16px 就是 1.2 × 1.2 × 1.2 = 1.728em,约 27.6px,而不是你单看这条规则可能读出的 19.2px。把列表套进列表、再套进组件里,文字就会以一种很难追踪的方式膨胀。

rem 完全绕开了这个问题。1.2rem 无论位于文档顶层还是十二层深,都是 19.2px,因为它永远以根为基准,从不以父级为基准。当某个值解析出一个你没料到的尺寸时,第一个该问的问题就是:它是 em(相对父级、会复合)还是 rem(相对根、稳定)。如果你在排查一个走偏的 rem,想快速看到它的像素尺寸,rem 转 px 工具能立刻解析出来。

什么时候用 rem

默认就用 rem。它是字号、内边距、外边距、间距、圆角和媒体查询断点的正确单位,凡是读者调整文字大小时应当跟着缩放的东西都该用它。

最后这半句就是无障碍方面的理由,而且它并非空谈。WebAIM 的屏幕阅读器与低视力调查一再发现,相当大比例的用户会调整浏览器或操作系统的默认字号,其中许多人调得远高于标准的 16px。用 rem 设定的布局会尊重这个改动:把默认字号提到 20px,每个基于 rem 的值都会成比例增长。用 px 设定的布局则完全无视它,文字会锁死在硬编码的尺寸上,无论读者多需要把它放大。

:root {
  font-size: 16px; /* 1rem = 16px */
}

h1   { font-size: 2rem;     } /* 32px,随用户偏好缩放 */
p    { font-size: 1rem;     } /* 16px */
.card { padding: 1.5rem;    } /* 24px */
.card { border-radius: 0.5rem; } /* 8px */

因为这里每个值都锚定在同一个根上,只要改动根字号一次,整个界面就会成比例重新缩放。设计系统也靠这一点保持协调:间距与排版一起移动,而不是各自漂移。

62.5% 技巧

有个让 rem 算术变得轻而易举的流行捷径。把根字号设为 62.5%,也就是 62.5% × 16px = 10px

html {
  font-size: 62.5%; /* 现在 1rem = 10px */
}

body {
  font-size: 1.6rem; /* 恢复可读的 16px 正文 */
}

h1 { font-size: 2.4rem; } /* 24px */
p  { font-size: 1.6rem; } /* 16px */

有了 10px 的根字号,心算就简化成「像素值除以 10」:24px → 2.4rem12px → 1.2rem。唯一要处理的小细节是用 body { font-size: 1.6rem } 恢复可读的正文大小,因为光是 10px 的基准会让默认文字太小。用 62.5% 这个百分比而非 10px,能保持它的相对性,这样把浏览器默认值放大的读者依然能得到成比例的增长。如果你采用这个基准,把换算工具的根字号设为 10,让它和你的样式表对上。

什么时候用 em

当你希望某个值随元素自身字号缩放、而不是随根缩放时,就用 em。最典型的例子是按钮:

.btn {
  font-size: 1rem;      /* 以根为基准 */
  padding: 0.75em 1.5em; /* 内边距跟着按钮文字走 */
}

.btn--large {
  font-size: 1.25rem;   /* 一处改动,整体放大 */
}

因为内边距用的是 em.btn--large 修饰符只用一条声明就同时放大了文字和它的内边距,按钮在任何尺寸下都保持比例。同样的逻辑也适用于用 em 设定大小、好与所在文字行匹配的图标,或者应当随字号增长的字间距。

实践中行得通的策略是:全局骨架用 rem,局部比例用 em。字号用 rem,让它听命于根和用户偏好;那少数应当跟着该元素走的值用 em。只要别把 em 用在嵌套很深的地方,否则前面那个复合陷阱又会悄悄回来。

什么时候用 px

有些值确实不该缩放,px 对它们才是对的:1px 的细线边框、精确的 box-shadow 偏移、2px 的焦点环。这些是渲染细节,不是内容。一个在用户放大文字时「缩放」到 1.25px 的边框毫无收益,反而可能渲染成一条模糊的线。px 让它保持清晰。

.divider { border-bottom: 1px solid; }     /* 应保持 1px */
.card    { box-shadow: 0 2px 4px rgba(0,0,0,0.1); } /* 固定偏移 */
.input:focus { outline: 2px solid; }       /* 清晰的焦点环 */

什么时候 px 反而更无障碍

这里是大多数「永远用 rem」的建议跳过的反直觉部分。无障碍的选择不是「处处用 rem」,而是「该缩放的缩放,不该缩放的固定」。

1px 边框是一个固定细节。硬把它塞进 rem、好让它在用户放大文字时增长,并不能帮助可读性,只会把细线弄糊。对这些属性来说,px 之所以是更无障碍的选择,恰恰因为它纹丝不动。

人们实际犯的错误正好相反:把本响应的东西(比如字号和断点)用了 px。那才是 px 损害无障碍的地方。所以规则不在于单位,而在于属性。要问的是:这个值是读者会与之交互的内容(那就缩放它,用 rem),还是固定的渲染细节(那就钉死它,用 px)。单位由答案推出。

媒体查询陷阱

这一条会破坏真实布局,却几乎没有指南提醒过。用 px 写的媒体查询断点,并不会像你期望的那样响应浏览器的字号缩放。

设想一个 width: 600px 的断点,到这里侧边栏会折叠。一位视力受限的用户把浏览器默认字号设为 24px 以便舒适阅读。你的内容现在需要更多横向空间,更大的文字想要更早换行重排。但 px 断点并不知道文字变大了;它依然在视口正好 600px 时翻转,于是布局在错误的时机切换,内容被挤压或重叠。

对比两种做法:

/* px 断点——无视用户的字号偏好 */
@media (min-width: 600px) {
  .sidebar { display: block; }
}

/* em/rem 断点——响应用户的字号 */
@media (min-width: 37.5em) {
  .sidebar { display: block; }
}

37.5em 在默认 16px 下就是 600px600 ÷ 16 = 37.5)。差别在于行为:当用户把默认字号翻倍时,em 断点实际上也翻倍,于是布局会在与文字成比例的视口宽度处切换,恰好在内容需要的时候。px 断点则原地冻结。

有个值得知道的小怪癖:在媒体查询的条件里,emrem 都以浏览器默认字号为基准解析,而不是任何 html 覆盖值,所以二者在这里表现完全一致。两种单位都能修掉这个 bug;只有 px 会引发它。

按属性的决策表

拿不准时,这张表给你答案,省得每次都重新推导逻辑。

属性推荐单位理由
font-sizerem随用户的字号偏好缩放
padding / marginrem间距与文字一起缩放
borderpx细线应保持清晰固定
box-shadow 偏移px是精确的渲染细节,不是内容
border-radiusrem让圆角弧度与缩放成比例
媒体查询em / rem断点必须响应字号缩放
width / max-widthrem(文本常用 ch可缩放的布局宽度;ch 限制行长
line-height无单位无单位的乘数能正确继承

line-height 那一行值得专门说明,因为它是个常见 bug。永远写 line-height: 1.5,不带单位。无单位值是每个元素以自身字号计算的乘数,所以嵌套元素都能保持易读。改写成 line-height: 1.5em24px,被继承的就是那个计算后的长度,意味着字号更大的子元素仍沿用父级的行高,它的文字就会开始挤撞。无单位避开了整个问题。

px 与 rem 互换

只要握住锚点 16px = 1rem,这点算术小到可以心算。除以 16 换成 rem,乘以 16 换回 px。

pxrem(16px 基准)
8px0.5rem
12px0.75rem
16px1rem
24px1.5rem
32px2rem

如果你用 62.5% 技巧,基准就变成 10px,算术更简单,只需除以或乘以 10,于是 24px = 2.4rem。唯一的规则是始终按你样式表实际设定的基准来换算。

其余情况(奇怪的值、自定义根字号,或批量换算一份 Figma 导出)就别心算了,用px 转 rem 工具rem 转 px 工具。两者都允许你设定任意根字号,并实时双向换算。之后如果你要整理一份单位混杂的样式表,CSS 格式化工具会帮你统一空格与缩进。

常见错误

少数几种模式造成了大部分与单位相关的烦恼:

用 px 设根字号。html { font-size: 16px }(而不是保留默认值或用 100% / 百分比)会直接覆盖用户浏览器的字号偏好。rem 值依然以它计算,但读者再也无法缩放整个页面。让根保持默认,或用百分比。

px 与 rem 混用而不成体系。 一些字号用 px、一些用 rem,间距在两者间分裂,结果是用户调整文字时布局缩放得参差不齐。把 rem 选作默认,把 px 留给决策表里那些有意为之的例外。

用 em 做全局间距。 在层层嵌套的容器上用 em 会重新引入复合陷阱,于是树深处的某个 padding 解析成谁都没想要的尺寸。全局间距用 rem;把 em 留给局部、组件范围内的值。

给 line-height 带单位。 如上所述,line-height: 24px1.5em 会作为计算后的长度被继承,在字号不同的元素上出问题。永远用无单位的乘数。

FAQ

rem 比 px 更好吗?

对大多数尺寸而言是的,rem 比 px 更好,因为它会随用户浏览器的字号偏好缩放,而 px 无视这一点。但「更好」取决于属性:对于 1px 边框、应保持清晰的阴影偏移这类固定细节,px 才是对的选择。内容尺寸用 rem,渲染细节用 px。

1rem 等于多少像素?

1rem 等于以像素计的根字号,在几乎所有浏览器中默认是 16px。所以在标准设置下,1rem = 16px1.5rem = 24px2rem = 32px。如果某份样式表覆盖了 html { font-size },比如通过 62.5% 技巧设成 10px,那么 1rem 就等于那个值。

font-size 该用 rem 还是 em?

font-size 几乎在所有情况下都用 rem。rem 以根为基准,所以无论元素嵌套多深都保持可预测。em 以父级字号为基准,会沿树往下复合,让嵌套文字意外膨胀。把 em 留给绑定到单个组件的局部值。

什么时候该用 px 而不是 rem?

对那些不该随用户字号缩放的值用 px:1px 边框、精确的 box-shadow 偏移、焦点环描边,以及其他固定的渲染细节。这些是清晰的设计细节而非内容,所以把它们钉在 px 上才是更无障碍的选择。一切与内容相关的仍应用 rem。

用 px 时媒体查询为什么会出问题?

用 px 写的媒体查询断点不响应浏览器的字号缩放。当用户放大默认字号时,内容需要更多空间,但 px 断点依然在同一视口宽度翻转,于是布局在错误的时机切换。用 emrem 断点,它们会随用户字号缩放。

62.5% 字号技巧是什么?

62.5% 技巧设置 html { font-size: 62.5% },让根字号变成 10px(16 的 62.5%)。有了 10px 基准,rem 算术就变成「除以 10」:24px = 2.4rem12px = 1.2rem。开发者随后设置 body { font-size: 1.6rem } 来恢复可读的 16px 正文。

可以混用 px、rem 和 em 吗?

可以,只要每个单位都跟着它适合的属性走,混用 px、rem、em 就是对的:排版与间距用 rem,固定细节用 px,局部组件范围的值用 em。会出问题的是无体系地混用,比如一些字号用 px、一些用 rem。把 rem 选作默认,把 px 和 em 当作有意为之的例外。

padding 和 margin 该用什么单位?

padding 和 margin 用 rem,这样用户调整字号时间距会与文字一起缩放,让布局保持比例与无障碍。把 em 留给应当跟着元素自身字号走的内边距,比如内边距随文字增长的按钮,并避免在层层嵌套的容器上用 em,因为它会复合。

标签: css rem em frontend accessibility responsive-design