网页字体排版的最佳实践之一就是使用相对单位,如rem
和em
.
问题是,你应该使用哪一个呢?一直以来,rem
支持者和em
支持者之间都存在着争辩,认为应该使用自己支持的那个。
在这篇文章中,你会找到我如何在rem
和em
之间做抉择.你也将了解rem
和em
到底是什么以及如何使用它们来构建模块化组件。
什么是EM?
EM 是字体排印的一个单位,等同于当前指定的point-size。-维基百科
此语句在网页上并不能说得过去,因为我们不使用point-size
.如果我们用point-size
取代font-size
的话,这句话就完全行得通。
意思就是,如果存在一个选择器的font-size
属性的值为 20px
,那么1em=20px
h1 { font-size: 20px } /* 1em = 20px */
p { font-size: 16px } /* 1em = 16px */
em
单位可以被用来声明字体的大小。实际上,最佳做法是使用相对单位,如用em
指定font-size
。
考虑如下代码:
h1 { font-size: 2em }
这里h1
选择器的真正大小是多少呢?
我们要根据<h1>
的父元素来计算font-size
。它的父元素是<html>
,并且它的font-size
被设置为16px
。
通过这种方式,我们可以计算出h1
的值为32px
,或者说2 * 16px
.
html { font-size: 16px }
h1 { font-size: 2em } /* 16px * 2 = 32px */
虽然也可以实现,但是这并不被认为是一个好主意,通过在<html>
中设置font-size
的像素值将影响用户浏览器所设置的值。
取而代之,你可以使用percentage
值,或者完全摒弃font-size
。
注意:如果你完全摒弃font-size
,它的值将被默认为100%
.
html { font-size: 100% } /*这里的意思是默认值为16px*/
对于大多数的用户(和浏览器),font-size
的值为100%
,就会默认为16px
,除非用户通过浏览器设置来改变font-size
的默认值。但是很少有人这么做。
好了,让我们回到em
。
em
也可以用来指定除了font-size
的其它属性值。margin
和padding
属性也经常用em
设置大小。
这里是很多人开始对em
的值产生困惑的地方。
考虑下面的代码。<h1>
和<p>
元素的margin-bottom
的值应该是多少?(假设<html>
的font-size
被设置为100%
).
h1 {
font-size: 2em; /* 1em = 16px */
margin-bottom: 1em; /* 1em = 32px */
}
p {
font-size: 1em; /* 1em = 16px */
margin-bottom: 1em; /* 1em = 16px */
}
你是不是很吃惊两种状况下的margin-bottom
的1em
值不同?
这种现象的发生在于1em
等同于它当前的font-size
。因为<h1>
中的font-size
被设置为了2em
。其他用在<h1>
内的em
来计算的属性,就为1em = 32px
。
在不同的代码中,1em
就会有不同的取值,这就是经常迷惑人们的地方。如果你也是刚刚接触em
,你也会产生迷惑。
不管怎么说,这就是em
.接下来,让我们认识rem
。
什么是REM?
rem
指根em
。它的产生是为了帮助人们解决em
所带来的计算问题。
它是字体排版的一个单位,等同于根font-size
。这意味着1rem
等同于<html>
中的font-size
。
考虑相同的用rem
表示的代码。现在margin-bottom
计算出来的值为多少呢?
h1 {
font-size: 2rem;
margin-bottom: 1rem; /* 1rem = 16px */
}
p {
font-size: 1rem;
margin-bottom: 1rem; /* 1rem = 16px */
}
正如您看到的,无论您在哪里设置它,1rem
的取值均为16px
。
这是十分可靠的,很容易去理解。
这就是rem
。一旦你知道了什么是em
,就很容易去理解,你是不是也同意这个观点?
现在让我们步入这篇文章的正题,rem
还是 em
?
REMs or EMs?
这是极具争议的问题。
一些开发人员完全避免使用rem
,声称使用rem
会使他们的组件缺少模块化。另外一些人则什么都使用rem
,因为喜欢rem
所带来的便捷。
奇怪的是,在我的职业生涯中,我掉进了在不同的地方是使用rem
或者em
的陷阱中。我喜欢em
帮助我完成模块化组件,但是讨厌它所带来的代码复杂性。我喜欢rem
计算的便捷,但是讨厌他是我模块化组件的阻碍。
事实证明。rem
和 em
均有各自的优缺点。应给根据实际情况来判断其使用方式。
这里我有两个简单的规则:
- 如果这个属性根据它的
font-size
进行测量,则使用em
- 其他的一切事物均使用
rem
.
有点太简单了么?让我们考虑用rem
还是em
,来书写一个简单的组件(头部元素),此过程中你就会发现这两个规则发挥的良好作用。
仅使用rem
来制作标题元素
你有一个看起来如下图所示的标题元素(<h2>
):
如果你用rem
设置一切属性的大小,标题元素的样式应该和上图相似:
.header {
font-size: 1rem;
padding: 0.5rem 0.75rem;
background: #7F7CFF;
}
目前为止,一切都好。
既然在相同的网页上会有不同大小的元素,接下来让我们创建一个稍微大的头部元素。这里,我们尽可能继承样式。
更大元素的标记可能如下所示:
<a href="#" class="header header--large">header!</a>
css样式如下:
.header {
font-size: 1rem;
padding: 0.5rem 0.75rem;
background: #7F7CFF;
}
.header--large {
font-size: 2rem;
}
不幸的是,这个代码并不能实现效果。你会发现在.header--larger
下,文本和边缘的空间太小。
如果你坚持使用rem
,唯一的办法就是使用padding
重新声明大标题:
.header {
font-size: 1rem;
padding: 0.5rem 0.75rem;
background: #7F7CFF;
}
.header--large {
font-size: 2rem;
padding: 1rem 1.5rem;
}
有没有注意到这里的模式?.header--larger
的font-size
是header
的两倍大。因此,.header--large
的padding
是.header
的两倍大小。
如果我们有更多大小的标题,或者改变标题大小时,又会发生什么呢?你已经可以看到如果使用rem
对整个网站进行编码所带来的重复性以及复杂性。
我们可以同时使用rem
和em
简化代码,这样就不需要对.header-larger
的padding
进行声明。
.header {
font-size: 1rem;
padding: 0.5em 0.75em;
background: #7F7CFF;
}
.header--large {
font-size: 2rem;
}
正如你所看到的,当有属性需要用它的字体大小(font-size
)进行大小声明时,这时em
就相当有用。这就是第一条原则产生的地方。
接下来让我们看看,如何只使用em
来创建相同的标题。
只使用 EMs 制作标题元素
只使用em
来制作标头元素和只使用rem
制作的代码差不多,只需要将rem
该更为em
.
.header {
font-size: 1em;
padding: 0.5em 0.75em;
background: #7F7CFF;
}
.header--large {
font-size: 2em;
}
.header
和.header--larger
的效果看起来和rem
实现效果一样。
不是吗?
不是的!
你的网页只包含一个标题元素是极不可能的。我们必须考虑标题与页面其他元素的交互。
在标题之前或之后看见其他元素是十分正常的,如下图所示:
对于这个元素集的标记如下:
<div class="header header--large">A Header Element</div>
<p>A paragraph of text</p>
<p>A paragraph of text</p>
<div class="header">A Header Element</div>
<p>A paragraph of text</p>
对于样式,我们需要对p
标签的左边和右边添加一点外边距:
p {
margin-left: 0.75em;
margin-right: 0.75em;
}
不不不! :(
.header--larger
左边和右边的padding
太大了!
如果你坚持只使用em
来解决这个问题,唯一的方法就是重新声明大标头的padding-left
和pading-right
.
.header {
font-size: 1em;
padding: 0.5em 0.75em;
/* Other styles */
}
.header--large {
font-size: 2em;
padding-left: 0.375em;
padding-right: 0.375em;
margin: 0.75em 0;
}
注意到这里的模式了么?.header--larger
的font-size
是.header
的font-size
的两倍大小,然而.header--larger
的padding-left
和padding-right
是.header
的一半。
在上述的案例中,如果我们可以同时使用em
和rem
就可以简化代码。具体来说就是,em
负责padding
的左边和右边,rem
负责padding
的顶部和底部。
.header {
padding: 0.5em 0.75rem;
font-size: 1em;
background: #7F7CFF;
}
.header--large {
font-size: 2em;
}
正如你所看到的,当一个元素用font-size
声明大小时,em
单位是十分有用的。但是,当你需要根据根font-size
来设置属性大小时,你就会陷入麻烦之中。
在一个组件之中看rem
和em
如何配合工作,不是更加清楚么?
现在,让我们提高一个档次,看标题和段落之间如何在网格中进行交互。
网格组件
在这之前,让我们先把标题和段落组成一个组件:
<div class="component">
<div class="component__header">A header element</div>
<p>Some paragraph text</p>
</div>
组件的基本样式为:
.component {
background: white;
border: 2px solid #7F7CFF;
}
.component__header {
font-size: 2em;
padding: 0.5em 1.5rem;
background: #7F7CFF;
margin: 0;
}
.component p {
padding-left: 1.5rem;
padding-right: 1.5rem;
margin: 1.5rem 0;
}
目前为止,一切都好。这都是我们在前面的章节所涵盖的。
我们可以在不同的网页中找到此组件,可能的领域包括:
- 主要内容区域
- 侧边栏
- 在一个
1/3
的网格布局中 - …
当组件被放置在一个较小的位置时,如侧边栏。这时,标题元素就会使用一个较小的font-size
来呈现。
通过修改组件的类,我们可以创建变量。标记将如下所示:
<div class="component component--small">
<!-- Contents of the component. -->
</div>
变量的样式如下:
.component--small .component__header {
font-size: 1em;
}
现在,关于组件的样式,两条规则同样适用:
- 如果这个属性根据它的
font-size
进行测量,则使用em
- 其他的一切事物均使用
rem
通过标题元素,我们了解到,是否使用em
进行大小声明,只需判断该属性是否与页面其他元素进行交互。这里有两种不同的方式来思考如何构建此组件:
- 所有内联元素缩放都根据组件的
font-size
决定。 - 部分内联元素缩放根据组件的
font-size
决定。
让我们用两种方式构建组件,你就会明白我的意思。
案例1: 所有内联元素缩放都根据组件的font-size
决定
我们通过一个例子来看这样子的组件是什么样子:
注意当组件的大小改变时,同一时间内font-size
、margin
和padding
的变化。
当组件大小变化时,如果您想要这样的效果,那么就需要使用em
进行大小声明:
.component {
background: white;
border: 2px solid #7F7CFF;
}
.component__header {
font-size: 2em;
padding: 0.5em 0.75em; /* Changed padding into em */
background: #7F7CFF;
margin: 0;
}
.component p {
padding-left: 1.5em; /* Changed padding into em */
padding-right: 1.5em; /* Changed padding into em */
margin: 1.5em 0; /* Changed margin into em */
}
// Small variant
.component--small .component__header {
font-size: 1em;
padding-left: 1.5em; /* Added em-sized padding */
padding-right: 1.5em; /* Added em-sized padding */
}
然后,要激活变化大小,你不得不更改组件的font-size
属性。
.component {
// Other styles
@media (min-width: 800px) {
font-size: 1.5em;
}
}
目前为止,一切都好。
现在,让我们开始复杂部分。
想象一下你是否有一个这样子的网格。每个网格项之间的垂直和水平空间在不同设备上需要保持一致(良好的美学需要).
网格标记为:
<div class="grid">
<div class="grid-item">
<div class="component"> <!-- component --> </div>
</div>
<div class="grid-item">
<div class="component component--small"> <!-- A --> </div>
<div class="component component--small"> <!-- B --> </div>
</div>
</div>
在根font-size
大小为16px
的情况下,我已经将每个网格项之间的间距设置为2em
。换句话说,间距计算的宽度为32px
。
在这个网格之中的挑战就是,小组件A和小组件B之间要保持32px
的外边距。开始的时候我们可以将组件B的margin-top
设置为2em
。
.component {
/* Other styles */
@media (min-width: 800px) {
font-size: 1.25em;
}
}
.component + .component {
margin-top: 2em;
}
不幸的是,这效果并不是很好。小组件A和小组件B之间的margin
的大小在视觉上远远大于视窗宽度800px
时的列间距。
这种情况的发生是因为当视窗大于800px
时,组件的font-size
大小为1.5em
(24px
)。因为font-size
为24px
,所以2em
就是48px
,这不同于我们所期待的32px
.
幸运的是,当我们知道是根据font-size
来设置列间距大小时,我们就可以用rem
轻易地解决这个问题。
.component + .component {
margin-top: 2rem;
}
这里有一个Codepen,你可以尝试一下。
注意:你需要用Flexbox来创建此网格。这里我将不再过多的解释,因为不仅仅是代码问题。如果你想了解更多关于Flebox的知识,你可以参考这里的文章。
顺便说一下,我没有赶上这一技术。Chris Coyier(他是一个天才)在一年之前就写了它。
不管怎样,我们就到目前为止好吗?如果好的话,让我们转向案例2。如果不好的话,有时间就留下评论,我会想办法作进一步解释。
案例2:部分内联元素缩放根据组件的font-size
决定
案例1很容易理解。缺点是很难保持模块化缩放,主要是垂直方向间距(vertical rhythms)并且在同一组件内保持大小一致(特别是建设响应式网站)。
有时候你只需要调整组件的一部分,而不是一次性调整所有部分。例如,您可能仅仅想要更改一个较大视区标题的font-size
。
在这种情况下,让我们先看看如何书写基本样式:
.component {
background: white;
border: 2px solid #7F7CFF;
}
.component__header {
font-size: 2em;
padding: 0.5em 1.5rem;
background: #7F7CFF;
margin: 0;
}
.component p {
padding-left: 1.5rem;
padding-right: 1.5rem;
margin: 1.5rem 0;
}
.component--small .component__header {
font-size: 1em;
}
既然我们只是更改标题的font-size
为1200px
,我们可以使用rem
控制一切属性的大小(不包括标题的padding-top
和padding-bottom
属性)
.component {
background: white;
border: 2px solid #7F7CFF;
}
.component__header {
font-size: 2rem; /* Sized in rem instead */
padding: 0.5em 1.5rem; /* Sized in rem instead */
background: #7F7CFF;
}
.component p {
padding-left: 1.5rem; /* Sized in rem instead */
padding-right: 1.5rem; /* Sized in rem instead */
margin: 1.5rem 0; /* Sized in rem instead */
}
.component--small .component__header {
font-size: 1rem; /* Sized in rem instead */
}
然后你就可以通过在不同视区下增加一个媒体特性,来更改标题的font-size
.
.component__header {
font-size: 2rem;
@media (min-width: 1200px) {
font-size: 3rem
}
}
.component--small .component__header {
font-size: 1rem;
@media (min-width: 1200px) {
font-size: 1.5rem
}
}
注意,当我们改变浏览器大小时,标题font-size
是如何改变的。这就是我们创建的案例2:)
还有一件事。
因为最好只使用少量的字体大小,我经常从组件上发现抽象的font-size
属性。这种方式可以轻松的实现在所有组件上保持字体大小。
h2 {
font-size: 2rem;
@media (min-width: 1200px) {
font-size: 3rem
}
}
h3 {
font-size: 1rem;
@media (min-width: 1200px) {
font-size: 1.5rem
}
}
.component__header { @extend h2; }
.component--small .component__header { @extend h3; }
这就是案例2!这里有一个Codepen供你去尝试:
这里你可能会问一个问题,“你应该使用哪种方法呢?”所以我还是先回答吧。
我会回答,这取决于您的设计。
就我个人而言,自从喜欢上自由文字排版模式,我使用案例2的时候相对多一点。
总结
所以,你应该使用rem
或者em
吗?我想这并不是一个很好的问题。rem
和em
都各有优缺点,应该综合使用它们以帮助您更简单的建立模块化组件。
扩展阅读
本文根据@Zell的《REM vs EM – The Great Debate》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://zellwk.com/blog/rem-vs-em/。
如需转载,烦请注明出处:http://www.w3cplus.com/css/rem-vs-em.html