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-size 是 20px,那么该元素上的 1em 就是 20px,0.5em 是 10px,1.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.4rem、12px → 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 下就是 600px(600 ÷ 16 = 37.5)。差别在于行为:当用户把默认字号翻倍时,em 断点实际上也翻倍,于是布局会在与文字成比例的视口宽度处切换,恰好在内容需要的时候。px 断点则原地冻结。
有个值得知道的小怪癖:在媒体查询的条件里,em 和 rem 都以浏览器默认字号为基准解析,而不是任何 html 覆盖值,所以二者在这里表现完全一致。两种单位都能修掉这个 bug;只有 px 会引发它。
按属性的决策表
拿不准时,这张表给你答案,省得每次都重新推导逻辑。
| 属性 | 推荐单位 | 理由 |
|---|---|---|
font-size | rem | 随用户的字号偏好缩放 |
padding / margin | rem | 间距与文字一起缩放 |
border | px | 细线应保持清晰固定 |
box-shadow 偏移 | px | 是精确的渲染细节,不是内容 |
border-radius | rem | 让圆角弧度与缩放成比例 |
| 媒体查询 | em / rem | 断点必须响应字号缩放 |
width / max-width | rem(文本常用 ch) | 可缩放的布局宽度;ch 限制行长 |
line-height | 无单位 | 无单位的乘数能正确继承 |
line-height 那一行值得专门说明,因为它是个常见 bug。永远写 line-height: 1.5,不带单位。无单位值是每个元素以自身字号计算的乘数,所以嵌套元素都能保持易读。改写成 line-height: 1.5em 或 24px,被继承的就是那个计算后的长度,意味着字号更大的子元素仍沿用父级的行高,它的文字就会开始挤撞。无单位避开了整个问题。
px 与 rem 互换
只要握住锚点 16px = 1rem,这点算术小到可以心算。除以 16 换成 rem,乘以 16 换回 px。
| px | rem(16px 基准) |
|---|---|
| 8px | 0.5rem |
| 12px | 0.75rem |
| 16px | 1rem |
| 24px | 1.5rem |
| 32px | 2rem |
如果你用 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: 24px 或 1.5em 会作为计算后的长度被继承,在字号不同的元素上出问题。永远用无单位的乘数。
FAQ
rem 比 px 更好吗?
对大多数尺寸而言是的,rem 比 px 更好,因为它会随用户浏览器的字号偏好缩放,而 px 无视这一点。但「更好」取决于属性:对于 1px 边框、应保持清晰的阴影偏移这类固定细节,px 才是对的选择。内容尺寸用 rem,渲染细节用 px。
1rem 等于多少像素?
1rem 等于以像素计的根字号,在几乎所有浏览器中默认是 16px。所以在标准设置下,1rem = 16px、1.5rem = 24px、2rem = 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 断点依然在同一视口宽度翻转,于是布局在错误的时机切换。用 em 或 rem 断点,它们会随用户字号缩放。
62.5% 字号技巧是什么?
62.5% 技巧设置 html { font-size: 62.5% },让根字号变成 10px(16 的 62.5%)。有了 10px 基准,rem 算术就变成「除以 10」:24px = 2.4rem、12px = 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,因为它会复合。