前段时间试译了 Keith J.Grant 的 CSS 好书《CSS in Depth》,其中的第二章 《Working with relative units》,书中对 relative units 的讲解和举例可以说相当全面,看完之后发现自己并不太懂 CSS 相对单位,也希望分享给大家,所以有了这个译文系列。(若有勘误或翻译建议,欢迎 Github PR ^_^)
《别说你懂 CSS 相对单位》系列译文:
如何更愉快地使用 em [本文]
CSS 自定义属性 本文对应的章节目录:
2.1 相对单位值的魔力
2.2 em 和 rem
CSS 提供了很多种方式去定义一个值。大家最熟悉的可能也是最容易使用的就是像素(pixel),这被称做“绝对单位”。也就是说,5px 在不同的场景下是一样的值。而其他的单位,如 em 和 rem,不是绝对的而是相对的。相对单位的值会根据外部影响因素的变化而变化。例如,2em 的值取决于你在哪个元素使用它(有时甚至是哪个属性)。很自然,相对单位使用起来会比较困难。
开发人员,甚至有经验的 CSS 开发人员,往往不喜欢跟相对单位打交道,其中包括臭名昭著的 em。em 的值可以被改变的方式似乎难以预测,没有 px 那么清晰。在本章中,我将揭开相对单位的神秘面纱。首先,我会解释它们为 CSS 带来的独特价值,然后我会帮助你更好地理解它们。我会解释它们的工作原理,也会告诉你怎么征服它们那看似不可预测的特性。你可以让相对单位为你所用,正确地运行,它们将让你的代码变得更加简单、灵活和容易使用。
CSS 是通过迟绑定(late-binding)的方式把样式渲染到 web 页面上的:内容和它的样式会在各自的渲染完成之后再合并到一起。比起其他类型的图形设计,这给设计过程添加了它们没有的复杂程度,同时也赋予 CSS 更强大的能力 —— 一个样式表可供成百上千个页面使用。此外,用户可以直接改变页面的最终呈现方式。举个例子,用户可以更改默认字号大小或者调整浏览器窗口的大小。
在早期的计算机应用程序开发以及传统出版行业中,开发人员或出版商清楚知道所在的媒介存在哪些限制。对于一个特定的应用程序,窗口可能是 400px 宽,300px 高,或者一个版面可能是 4 英寸宽,6½英寸高。因此,当开发人员布局应用程序的按钮和文本时,他们很清楚这些元素可以做成什么尺寸,以及在屏幕上还有多少空间可以留给他们用来处理其他元素。然而在网页上,情况却不是这样的。
在 web 环境下,用户可以将浏览器窗口设置为任意大小,且 CSS 需要去适应它。另外,用户可以在一个页面打开后,再调整它的大小,CSS 也需要去适应这些新的约束条件。这说明了在你创建页面时样式还没有被调用,而是当页面在屏幕上渲染时,浏览器才会去计算样式的规则。
这给 CSS 增加了一层抽象的概念。我们不应该根据理想的情境来设计元素,而是应该声明一些样式规则,可以让该元素在任何场景下都能跑通。对于现在的互联网,你的页面可能要在一个 4 英寸的手机屏幕上展示,也可能在一个 30 英寸的大屏幕上。
长久以来,设计师大量使用“完美像素”设计,缓解了这个问题带来的复杂性。他们会创建一个有着严格定义的容器,通常是一个大约 800px 宽的居中的纵向列。然后在这些限制下他们再进行设计,这跟他们的前辈在原生应用程序或印刷出版物中做的设计或多或少有点类似。
随着技术的进步和制造商推出更高分辨率的显示器,像素完美的设计方式慢慢开始崩溃。在 21 世纪初期,把页面设计成 1024px 宽还是 800px 宽,哪个是更保险的展示策略?开发者针对这个问题讨论得很多。然后,我们又针对能否改成 1280px 宽有类似的讨论。是时候做个决定了。把我们网站的内容宽度做得宽一点(相对于落伍的小电脑屏幕),还是做得窄一点(相对于新出的大屏幕),哪个选择更好呢?
当智能手机出现的时候,开发人员终于要(被迫)要停止假装每个人都可以在他们的网站上获得相同的体验了。不管我们喜不喜欢,我们都得放弃已知的多栏定宽(px)布局,并开始考虑响应式设计。我们再也不能逃避 CSS 所带来的抽象概念(abstraction),相反,我们要去拥抱这项特性。
响应式 —— 在 CSS 中,这指的针对不同大小的浏览器窗口,用不同的方式响应更新页面的样式。我们要对不同尺寸的手机、平板电脑或桌面显示器多花心思了。我们将在 第 8 章 中详细介绍响应式设计,但在本章中,我会先给大家介绍一些重要的基础概念。
增加的抽象概念意味着额外的复杂性。如果我设定一个宽度为 800px 的元素,那么它在一个更小的窗口中会怎么显示呢?如果一个横向菜单不能全部在一行展示完,它又会怎么展示?在编写 CSS 时,你需要能够同时考虑具体情况以及普适性的问题。如果针对一个特定的问题,你有多种方式可以解决,那么你应该选那个在多种不同场景下更通用的解决方案。
在抽象概念这个问题上,相对单位是 CSS 提供的工具之一。与其把字号大小设置为 14px,你可以把它设置为与窗口大小成比例缩放。或者,你可以设置页面上所有元素是依赖基础字号大小的变化而变化的,然后用一行代码就可以达到调整整个页面的目的。接下来,我们来看看 CSS 提供了哪些方式来实现以上的效果。
像素(pixel)、点(point)和 pc(pica)
CSS 支持一些绝对长度单位,其中最常见也最基本的是像素(px)。较不常见的绝对单位有毫米(mm,millimeter)、厘米(cm,centimeter)、英寸(in.,inch)、点(pt,point,印刷术语,长度为 1/72 inch)以及 pc(pica,印刷术语,长度为 12 points)。如果你想了解其中的计算方式,以上的长度单位都可以直接转换成另一个单位:1 inch = 25.4 mm = 2.54 cm = 6 pc = 72 pt = 96 px。因此,16px 与 12pt(16/96×72)是等价的。设计师通常更熟悉点(point)的使用,而开发人员更习惯于像素,因此在和设计师沟通时,你可能需要在两者之间做一些计算工作。
像素这个名字有点误导性 —— 1 CSS 像素并不严格等同于显示器的 1 像素,在高分辨率显示器(如“Retina 显示屏”)上尤其明显。尽管根据浏览器、操作系统和硬件的不同,CSS 的测量值可能会有细微的差别,但 96px 总是会大致等于屏幕上的物理 1 英寸。(尽管有可能会因某些设备或用户设置而异。)
em 是最常见的相对长度单位,这是排版中使用的一种度量方式,基准值是当前元素的字号大小。在 CSS 中,1em 表示当前元素的字号大小,实际值取决于在哪个元素上应用。图 2.1 展示了一个 padding 为 1em 的 div
。
[ 图 2.1:padding 为 1em 的元素(添加虚线是为了让 padding 更明显)]
模板代码片段如下。这套样式规则定义字号为 16px,也就是元素本身 1em 代表的值,然后再使用 em 来声明元素的 padding。把这段代码添加到一个新的样式表里,在<div class ="padded">
下随手写些文字,然后到浏览器看看效果吧。
代码片段 2.1:在 padding 上使用 em
.padded {
font-size: 16px;
padding: 1em; 1
}
padding
赋值为 1em,乘以字号,得到一个值为 16px 的padding
渲染值。重点来了,使用相对单位声明的值会由浏览器转化为一个绝对值,我们称之为计算值。在这个例子里,将 padding
改为 2em 会生成一个 32px 的计算值。如果同一个元素的另一个选择器,用一个不一样的字号值去覆盖它,这会改变 em 在这个域下的基准值,那么padding
的计算值也会相应变化。
在设置 padding、height、width 或 border-radius
等属性时,使用 em 可能会很方便,因为如果它们继承了不同的字号大小,或者用户更改了字体设置,这些属性会均匀地缩放。
图 2.2 展示了两个不同大小的盒子。盒子内的 font-size
、padding
和border-radius
各不相同。
[ 图 2.2:有相对大小的 padding 和 border-radius 的元素 ]
你可以通过用 em 声明 padding
和border-radius
来给这些盒子声明样式规则。首先给每个元素设定padding
和border-radius
为 1em,然后给每个盒子指定不同的字号,那么其他属性会跟着字号缩放。
在你的 HTML 代码里,创建如下的两个盒子,类名分别是 box-small
和box-large
,代表两个字号修饰符。
[ 代码片段 2.2:在不同元素上使用 em(HTML)]
<span class="box box-small">Small</span>
<span class="box box-large">Large</span>
现在,添加下面的样式到你的样式表。这里使用了 em 声明了一个盒子。还定义了小字号和大字号的修饰符,指定不同的字号大小。
[ 代码片段 2.3:在不同元素上使用 em(CSS)]
.box {
padding: 1em;
border-radius: 1em;
background-color: lightgray;
}
.box-small {
font-size: 12px; 1
}
.box-large {
font-size: 18px; 1
}
当 font-size
使用 em 作为单位时,它的表现会有点不一样。我之前说过,em 是以当前元素的字号大小作为基准值的。但是,如果你把一个元素的字号设为 1.2em 的时候,这是什么意思呢?一个元素的字号大小是不能等于它自己的 1.2 倍的。相反,在font-size
上的 em 会先从继承到的字号大小衍生出来。
举个简单的例子,见图 2.3。以下展示了一些不同字号大小的文字。在代码片段 2.4,你会用到 em 来实现。
[ 图 2.3 以 em 为单位的两种不同的字号大小 ]
在你的页面添加以下代码片段。第一行文字,在 <body>
标签里面,它会按 body 的字号大小渲染。第二部分,口号(slogan),继承父元素的字号大小。
[ 代码片段 2.4 相对 font-size
的模板 ]
<body>
We love coffee
<p class="slogan">We love coffee</p> 1
</body>
body
继承了字号大小。 代码片段中,CSS 代码片段声明了 body
的字号大小。为了更加清晰,在这里我用了 px 来声明。下一步,你可以用em
来放大 slogan 的字号大小。[ 代码片段 2.5:在 font-size 上使用 em ]
body {
font-size: 16px;
}
.slogan { 1
font-size: 1.2em; 1
} 1
提示
如果你已经知道以 px 为单位的基础字号大小,但希望把它改用 em 声明,下面有个简单的计算公式:目标 em 值 = 目标像素值 / 父元素(被继承元素)像素值。举个例子,如果你想要一个 10px 的字号大小,父元素的字号是 12px,10 / 12 = 0.8333em。如果你想要 16px 的字号大小,父元素字号是 12px,那么 16 / 12 = 1.3333em。我们会在这章里多次用到这个计算公式。
有一点对你很有帮助,对于大多数浏览器,默认字号大小是 16px。技术上,关键字 medium 会被计算转换为 16px。
你已经使用过 em 声明 font-size
了(基于一个继承的字号大小值)。以及,你也曾经使用 em 声明其他属性,如padding
和border-radius
(基于当前元素的字号大小值)。当你针对同一个元素使用 em 声明font-size
和其他属性的时候,em 会变得很神奇。此时浏览器必须先计算font-size
,然后基于这个值再去计算其他值。这些属性声明的时候使用的是相同的 em 值,但很可能它们会有不同的计算值。
在之前的例子里,我们计算到字号大小是 19.2px(继承的 16px 乘以 1.2em)。图 2.4 是相同的 slogan 元素,但有额外的 1.2em padding
以及为了让 padding
大小更加明显的灰色背景。可以看出,padding
比font-size
稍微大一些,尽管它俩声明的时候 em 值是一样的。
[ 图 2.4 一个 font-size 为 1.2em 以及 padding 为 1.2em 的元素 ]
现在的情况是,这个段落从 body
继承了 16px 的字号大小,通过计算得到值为 19.2px 的字号计算值。这意味着,19.2px 是 1em 在当前域的基础值,而这个值会被用作计算padding
的值。对应的 CSS 代码在下面,更新你的样式表并查看你的测试页面吧。
[ 代码片段 2.6 在 font-size 和 padding 上使用 em ]
body {
font-size: 16px;
}
.slogan {
font-size: 1.2em; 1
padding: 1.2em; 2
background-color: #ccc;
}
padding
的声明值为 1.2em,乘以 19.2px(当前元素的字号大小),计算出 23.04px。我们可以看到,尽管 font-size
和padding
声明时 em 值是一样的,但它们的计算值是不一样的。当你使用 em 声明多层嵌套的元素字号时,会产生意想不到的效果。要弄清楚每个元素的实际值,首先你需要知道它继承的父元素的字号大小,如果父元素的字号也是用 em 声明的,那么你需要知道它的父元素的字号大小,在 dom 树往上查,以此类推。
当你使用 em 声明列表的字号大小,列表嵌套了好几层,效果就更明显了。大多数 web 开发者会发现在他们的职业生涯里面,图 2.5 的列表嵌套形式有点眼熟。文字在逐步缩小!正是因为 em 带来的这一类烦人的问题,开发者才对 em 避而远之。
[ 图 2.5 嵌套列表中的字号缩小现象 ]
当你多层嵌套列表,而每一层声明的字号大小以 em 为单位,字号收缩现象就会发生。在代码片段 2.7 和 2.8 的例子里,无序列表的字号是 0.8em。这个选择器对页面上所有的 ul 有效,所以当一个列表从另外一个列表继承到字号大小的时候,em 就产生复合效果。
[ 代码片段 2.7 在列表上使用 em ]
body {
font-size: 16px;
}
ul {
font-size: .8em;
}
[ 代码片段 2.8 多层嵌套的列表 ]
<ul>
<li>Top level
<ul> 1
<li>Second level 1
<ul> 2
<li>Third level 2
<ul> 3
<li>Fourth level 3
<ul>
<li>Fifth level</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
[ 图 2.6 字号正常的多层嵌套列表 ]
其中一种实现的方式可以看看代码片段 2.9。第一个列表的字号大小还是 0.8 em(见示例 2.7),第二个选择器对嵌套在无序列表的无序列表有效 —— 也就是除了第一个无序列表以外的所有无序列表。现在嵌套的列表设定了跟父元素一致的字号大小,正如图 2.6 一样。
[ 代码片段 2.9 字号收缩现象的纠正 ]
ul {
font-size: .8em;
}
ul ul { 1
font-size: 1em; 1
}
到现在我们清楚了,如果你不是一个比较小心的人,你应该远离 em。使用 em 作为 padding
、margin
和元素缩放效果的单位挺好的,但当 em 遇上font-size
时,事情可以变得很复杂。感谢上天,我们有个更好的选择 —— rem。
《别说你懂 CSS 相对单位》系列译文:
如何更愉快地使用 em [本文]
CSS 自定义属性 章节:
2.1 相对单位值的魔力
2.2 em 和 rem
2.3 停止使用像素思维去思考
2.4 视口相关单位(viewport-relative units)
2.5 不带单位的数字(unitless number)和行高(line-height)
2.6 自定义属性(也叫“CSS 变量”)
总结
原著版权信息:
作者:Keith J.Grant
书籍:CSS in Depth
章节:Working with relative units
笔者 @Yuying Wu,前端爱好者 / 鼓励师 / 铲屎官。目前就职于某大型电商的 B2B 前端团队。
感谢你读到这里,对上文若有任何疑问或建议,欢迎留言。
如果你和我一样喜欢前端,喜欢捣腾独立博客或者前沿技术,或者有什么职业疑问,欢迎关注我以及各种交流哈。
独立博客:wuyuying.com
知乎 ID:@Yuying Wu
Github:Yuying Wu