用一个嵌套示例看懂 rem 和 em
rem 相对于根元素的字号(即 <html> 元素,默认 16px)。em 相对于当前字号,对 font-size 来说就是父元素的字号。把同一个类嵌套三层,区别就显现出来了:
打印出来的尺寸是浏览器计算得到的结果。每个 1.25em 都会乘以父元素的字号:20px、25px、31.25px。每个 1.25rem 只看根元素:20px、20px、20px。正是这种层层叠加,让 em 字号在嵌套的列表和菜单中失控,这也是大多数网站用 rem 设置字号的原因。
每种单位相对于什么
| 单位 | 相对于 | 默认时 1 个单位等于 |
|---|---|---|
px | 不相对于任何东西(一个 CSS 像素) | 1px |
rem | 根元素(html)的字号 | 16px |
em | 元素的字号;对 font-size 本身来说是父元素的字号 | 顶层为 16px |
% | 取决于属性(见下文) | |
vw / vh | 视口宽度 / 高度的 1% | |
dvh、svh、lvh | 动态、小或大视口高度的 1% | |
vmin / vmax | 视口较短 / 较长边的 1% | |
ch | 当前字体中“0”的宽度 | 大约半个 em |
ex | 当前字体的 x 字高 | 大约半个 em |
CSS 像素不是设备像素:在 3 倍屏的手机上,1px 有 3 个设备像素宽。它在不同屏幕上的视觉大小大致相同,所以边框和阴影用 px 仍然没问题。
用 em 设置随文字缩放的间距
em 不只用于字号。用在 padding、margin、width 或 border-radius 上时,它的意思是“这个元素字号的多少倍”。这样一个按钮类就能适用于任何尺寸:
每个按钮的垂直内边距是字号的一半,水平内边距等于一个字号,所以三个按钮看起来就是同一个按钮的三种尺寸。如果用 px 设置内边距,每种尺寸都得单独写一条内边距规则。
注意,按钮默认不会继承页面的字号(浏览器给表单控件设置了自己的字号),所以这里中间的按钮在 Chrome 中是 13.33px,而不是 16px。想让按钮和输入框与页面一致时,给它们加上 font: inherit。
百分比:取决于属性
% 没有统一的含义。每个属性各自规定它是谁的百分比:
| 属性 | % 相对于 |
|---|---|
width、max-width、left、right | 父元素(包含块)的宽度 |
height、top、bottom | 父元素的高度,并且只有父元素设置了高度时才有效 |
padding、margin(四个方向) | 父元素的宽度 |
font-size | 父元素的字号(150% = 1.5em) |
line-height | 元素自身的字号 |
transform: translate() | 元素自身的尺寸 |
width: 50% 是 200px,即父元素的一半。padding-top: 5% 是 20px:父元素宽度的 5%,而不是高度的 5%。而 height: 50% 不起作用,因为父元素没有设置高度,所以子元素的高度只等于它的文字加上那段内边距。宽度和高度页面详细讲解了这条规则。
vw、vh 与手机地址栏
1vw 是视口宽度的 1%,1vh 是视口高度的 1%。它们适合用来做全屏区块和流式尺寸(常放在 clamp() 里)。在手机上,vh 有个坑:滚动时浏览器的地址栏会滑入滑出,而 100vh 是不含地址栏时的高度,所以地址栏显示时,height: 100vh 的首屏区块底部会被截掉。较新的单位解决了这个问题:
| 单位 | 使用的高度 |
|---|---|
100vh | 在大多数移动浏览器上是大视口(工具栏隐藏) |
100svh | 小视口(工具栏显示):始终放得下 |
100lvh | 明确指定大视口 |
100dvh | 动态视口:随工具栏移动而变化 |
.hero {
min-height: 100vh; /* fallback for older browsers */
min-height: 100svh; /* fits the screen even with the address bar visible */
}
vw 也有自己的坑:当垂直滚动条占据空间时(比如在 Windows 上),100vw 包含滚动条的宽度,所以 width: 100vw 可能比页面更宽,从而出现水平滚动条。要表示“父元素的完整宽度”,请用 width: 100%。
用 ch 控制易读的行长
ch 是字符“0”的宽度,所以 max-width: 65ch 能把段落限制在每行大约 65 个字符,无论字号多大,都是舒适的阅读长度:
该用哪种单位
| 用途 | 使用 | 原因 |
|---|---|---|
| 字号 | rem | 处处一致,尊重读者的字号设置 |
| 组件中的内边距和间隙 | rem 或 em | 需要随组件自身文字变大时用 em |
| 边框、细线、阴影 | px | 在任何字号下都应保持纤细 |
| 布局宽度 | %、fr,以及用 rem 或 ch 设置的 max-width | 跟随容器,限制行长 |
| 全屏区块 | svh / dvh(加上 vh 作为后备) | 应对手机地址栏 |
| 流式字号 | 混合 rem 和 vw 的 clamp() | 在限定范围内随屏幕变大 |
为什么 rem 尊重读者而 px 不尊重
浏览器有一个默认字号设置,需要更大文字的人会用到它。它会改变根元素的字号,所以所有用 rem 和 em 设置的内容都会随之变大。而 font-size: 14px 无论读者选了什么都保持 14px。浏览器缩放也会缩放 px,但很多读者依赖的是字号设置。这就是用 rem 设置字号的主要原因。
有些网站会设置 html { font-size: 62.5%; },让 1rem 等于 10px,方便计算。它仍然尊重读者的设置(读者所选字号的 62.5%),但记得在 body 上设置一个易读的 font-size,否则所有没有单独设置字号的元素都会显示 10px 的文字。
常见错误
- 在嵌套元素中用
em设置字号。它会在每一层相乘。字号请用rem。 height: 50%不起作用。父元素需要设置高度,百分比高度才会生效。- 以为
padding-top: 10%跟随父元素的高度。垂直方向的内边距和外边距百分比使用的是父元素的宽度。 width: 100vw导致出现水平滚动条。它包含了滚动条的宽度。改用100%。height: 100vh在手机上被截断。改用100svh或100dvh。- 设置
html { font-size: 12px }。根元素上的 px 值会覆盖读者对整个页面的字号设置。不要改根元素字号,或者使用百分比。
常见问题
rem 和 em 有什么区别?
rem 相对于根元素(<html>)的字号,所以 1.5rem 在页面上任何地方都一样大。em 相对于元素自身的字号(对 font-size 来说是父元素的字号),所以元素嵌套时它会层层叠加。
1rem 等于多少像素?
默认是 16px,因为浏览器把根元素字号设为 16px。如果用户在浏览器设置里调大了默认字号,或者你设置了 html { font-size: ... },1rem 也会随之改变。
字号应该用 rem 还是 px?
字号用 rem。它会尊重读者在浏览器中设置的默认字号,而 px 会忽略这个设置。浏览器缩放对两者都有效,但不是每个读者都会使用缩放。
什么时候应该用 em 而不是 rem?
当间距需要随元素自身的文字缩放时用 em,比如按钮的内边距,或文字旁边图标的尺寸。这样同一个类在任何字号下都显示得恰到好处。字号和页面级间距用 rem,这样就不会层层叠加。
100vh 和 100dvh 有什么区别?
在移动浏览器上,100vh 是地址栏隐藏时的高度,所以地址栏显示时,100vh 的区块比可见屏幕更高。100dvh(动态视口高度)会随地址栏的出现和消失而变化,而 100svh 始终是较小的那个高度。