相对单位
1 相对值的好处
CSS为网页带来了后期绑定(late-binding)的样式:直到内容和样式都完成了,二者才会结合起来、
1.1 那些年追求的像素级完美
在很长时间里,网页设计者通过聚焦到“像素级完美”的设计来降低这种复杂性。他们会创建一个紧凑的容器,通常是居中的一栏,大约800px宽。然后再像之前的本地应用程序或者印刷出版物那样,在这些限制里面进行设计。
1.2 像素级完美的时代终结了
等到智能手机出现后,开发人员再也无法假装每个用户访问网站的体验都能一样。不管我们喜欢与否,都得抛弃以前那种固定宽度的栏目设计,开始考虑响应式设计。我们无法逃避CSS带来的抽象性。我们得拥抱它。
响应式——在CSS中指的是样式能够根据浏览器窗口的大小有不同的“响应”。这要求有意地考虑任何尺寸的手机、平板设备,或者桌面屏幕
像素、点、派卡
CSS支持几种绝对长度单位,最常用、最基础的是像素(px)。不常用的绝对单位是mm(毫米)、cm(厘米)、in(英寸)、pt(点,印刷术语,1/72英寸)、pc(派卡,印刷术语,12点)。这些单位都可以通过公式互相换算:1in = 25.4mm = 2.54cm = 6pc = 72pt = 96px。因此,16px等于12pt(16/96×72)。设计师经常用点作为单位,开发人员则习惯用像素。因此跟设计师沟通的时候需要做一些换算。
像素是一个具有误导性的名称,CSS像素并不严格等于显示器的像素,尤其在高清屏(视网膜屏)下。尽管CSS单位会根据浏览器、操作系统或者硬件适当缩放,在某些设备或者用户的分辨率设置下也会发生变化,但是96px通常等于一个物理英寸的大小。
2 em和rem
2.1 em
2.1.1 其它属性
em是最常见的相对长度单位,适合基于特定的字号进行排版。
在CSS中,1em等于当前元素的字号
,其准确值取决于作用的元素。图2-1是一个内边距为1em的div元素。
图2-1 内边距为1em的元素(虚线用于展示内边距)
它的代码如代码清单2-1所示。规则集指定了字号为16px,也就是元素局部定义的1em。然后使用em指定了元素的内边距。将代码清单2-1加入一个新的样式表,在<div class="padded">
中写一些文字,在浏览器中看看会是什么效果。
代码清单2-1 用em单位设置内边距
.padded {
font-size: 16px;
padding: 1em; ←---- 设置四个内边距为font-size
}
这里设置内边距的值为1em。浏览器将其乘以字号,最终渲染为16px。这一点很重要:浏览器会根据相对单位的值计算出绝对值,称作计算值(computed value)。
在本例中,设置内边距为2em,会产生一个32px的计算值。如果另一个选择器也命中了相同的元素,并修改了字号,那么就会改变em的局部含义,计算出来的内边距也会随之变化。
当设置padding、height、width、border-radius等属性时,使用em会很方便。这是因为当元素继承了不同的字号,或者用户改变了字体设置时,这些属性会跟着元素均匀地缩放。
图2-2展示了两个不同大小的盒子,它们的字号、内边距和圆角都会不一样。
元素的内边距和圆角都是相对值
在定义这些盒子的样式时,可以用em指定内边距和圆角。给每个元素设置1em的内边距和圆角,再分别指定不同的字号,那么这些属性会随着字体一起缩放。
如代码清单2-2所示,在HTML中创建两个盒子。给元素分别添加box-small和box-large类名,作为大小修饰符。
代码清单2-2 给不同的元素加上em(HTML)
<span class="box box-small">Small</span>
<span class="box box-large">Large</span>
现在将代码清单2-3加到样式表中。这段代码用em定义了一个盒子,同时定义了一个small和一个large的修饰符,分别指定不同的字号。
代码清单2-3 将em应用于不同的元素(CSS)
在这里插入代码片.box {
padding: 1em;
border-radius: 1em;
background-color: lightgray;
}
.box-small {
font-size: 12px; (以下5行)不同的字号,可以决定元素的em值
}
.box-large {
font-size: 18px;
}
这就是em的好处。可以定义一个元素的大小,然后只需要改变字号就能整体缩放元素。稍后会再举一个例子,在此之前,我们先说说em和字号。
2.1.2 使用em定义字号
谈到font-size属性时,em表现得不太一样。之前提到过,当前元素的字号决定了em。但是,如果声明font-size: 1.2em,会发生什么呢?一个字号当然不能等于自己的1.2倍。实际上,这个font-size是根据继承的字号来计算的。
举个简单的例子。如图2-3所示,有两段文字,分别有不同的字号。可以像代码清单2-4那样定义元素,然后使用em定义字号。
图2-3 使用em定义两种不同的字号
按照代码清单2-4修改网页。第一行文字在标签中,因此它会按照body的字号来渲染。第二段的slogan继承了这个字号。
代码清单2-4 使用相对font-size的标记
<body>
We love coffee
<p class="slogan">We love coffee</p> ←---- slogan继承了<body>的字号
</body>
代码清单2-5指定了元素的字号。简单起见,这里用像素单位。接下来使用em来放大slogan的字号。
代码清单2-5 使用em定义font-size
body {
font-size: 16px;
}
.slogan { (以下3行)计算结果为元素继承的字号的1.2倍
font-size: 1.2em;
}
slogan的指定字号是1.2em。为了得到计算的像素值,需要参考继承的字号,即16px。因为16×1.2 = 19.2,所以计算值为19.2px。
提示 如果知道字号的像素值,但是想用em声明,可以用一个简单的公式换算:用想要的像素大小除以父级(继承)的像素字号。比如,想要一个10px的字体,元素继承的字体是12px,则计算结果是10/12 = 0.8333em。如果想要一个16px的字体,父级字号为12px,则计算结果是16/12 = 1.3333em。在本章我们还会进行几次这样的计算。
了解这些非常有用。对大多数浏览器来说,默认的字号为16px。准确地说,medium关键字的值是16px。
01 em同时用于字号和其他属性
现在你已经用em定义了字号(基于继承的字号),而且也用em定义了其他属性,比如padding和border-radius(基于当前元素的字号)。em的复杂之处在于同时用它指定一个元素的字号和其他属性。这时,浏览器必须先计算字号,然后使用这个计算值去算出其余的属性值。这两类属性可以拥有一样的声明值,但是计算值不一样。
在前面的例子里,字号的计算值为19.2px(继承值16px乘以1.2em)。图2-4展示了相同的slogan元素,但是内边距为1.2em,背景为灰色,这样能明显地看到内边距的大小。内边距比字号稍微大一些,尽管它们的声明值相同。
图2-4 字号为1.2em和内边距为1.2em的元素
这是因为该段落从body继承了16px的字号,最终字号的计算值为19.2px。因此19.2px是em的局部值,用于计算内边距。按照代码清单2-6更新测试页面的样式表。
代码清单2-6 使用em定义font-size和padding
body {
font-size: 16px;
}
.slogan {
font-size: 1.2em; ←---- 计算值为19.2px
padding: 1.2em; ←---- 计算值为23.04px
background-color: #ccc;
}
在这个例子里,padding的声明值为1.2em,乘以19.2px(当前元素的字号),得到计算值为23.04px。尽管font-size和padding的声明值相同,计算值却不一样。
02 字体缩小的问题
当用em来指定多重嵌套的元素的字号时,就会产生意外的结果。为了算出每个元素的准确值,就需要知道继承的字号,如果这个值是在父元素上用em定义的,就需要知道父元素的继承值,以此类推,就会沿着DOM树一直往上查找。
当使用em给列表元素定义字号并且多级嵌套时,这个问题就显现出来了。绝大部分Web开发人员曾遇到过类似于图2-5的现象。文字缩小了!正是这种问题让开发人员惧怕使用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> (以下2行)这个列表嵌套在第一个列表中,继承它的字号
<li>Second level
<ul> (以下2行)这个嵌套在上一个列表中,继承第二个列表的字号
<li>Third level
<ul> (以下2行)以此类推
<li>Fourth level
<ul>
<li>Fifth level</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
每个列表元素的字号等于0.8乘以其父元素的字号。算出来第一级列表的字号为12.8px,第二级缩小到10.24px(12.8px × 0.8),第三级缩小到8.192px,以此类推。同理,如果指定一个大于1em的字号,文字会逐渐增大。我们想要的是指定顶部的字号,然后保持子级的字号一致,如图2-6所示。
实现这种效果的代码如代码清单2-9所示。它设置第一级列表的字体为0.8em(跟代码清单2-7一致)。代码清单2-9里的第二个选择器选中了嵌套在某个无序列表中的所有无序列表,也就是除了*列表以外的其他列表。嵌套列表的字号等于其父级的字号,如图2-6所示。
代码清单2-9 纠正文字缩小的问题
ul {
font-size: 0.8em;
}
ul ul { (以下3行)嵌套的列表应当跟其父级的字号一致
font-size: 1em;
}
这样确实解决了问题,尽管这个方式不完美。设置一个值,然后马上用另一个规则覆盖。如果不用提升选择器的优先级来覆盖规则,就更好了。
这些例子告诉我们,如果不小心的话,em就会变得难以驾驭。em用在内边距、外边距以及元素大小上很好,但是用在字号上就会很复杂。值得庆幸的是,我们有更好的选择:rem。
2.2 使用rem设置字号
当浏览器解析HTML文档时,会在内存里将页面的所有元素表示为DOM(文档对象模型)。它是一个树结构,其中每个元素都由一个节点表示。<html>
元素是*(根)节点。它下面是子节点,<head>
和<body>
。再下面是逐级嵌套的后代节点。
在文档中,根节点是所有其他元素的祖先节点。根节点有一个伪类选择器(:root),可以用来选中它自己。这等价于类型选择器
html,但是html的优先级相当于一个类名,而不是一个标签。
rem是root em的缩写。rem不是相对于当前元素,而是相对于根元素的单位。不管在文档的什么位置使用rem,1.2rem都会有相同的计算值:1.2乘以根元素的字号。代码清单2-10先指定了根元素的字号,然后用rem定义了无序列表的相对字号。
代码清单2-10 使用rem指定字号
:root { ←---- :root伪类等价于类型选择器html
font-size: 1em; ←---- 使用浏览器的默认字号(16px)
}
ul {
font-size: .8rem;
}
在这个例子里,根元素的字号为浏览器默认的字号16px(根元素上的em是相对于浏览器默认值的)。无序列表的字号设置为0.8rem,计算值为12.8px。因为相对根元素,所以所有字号始终一致,就算是嵌套列表也一样。
可访问性:对字号使用相对单位
有些浏览器给用户提供了两种方式来设置文字大小:缩放操作和设置默认字号。按住Ctrl+或Ctrl-,用户可以缩放网页。这种操作会缩放所有的字和图片,让网页整体放大或者缩小。在某些浏览器中,这种改变只会临时对当前标签页生效,不会将缩放设置带到新的标签页。
设置默认字号则不一样。不仅很难找到设置默认字号的地方(通常在浏览器的设置页),而且用这种方式改变字号会永久生效,除非用户再次修改默认值。这种方式的缺点是,它不会影响用px或者其他绝对单位设置的字号。由于默认的字号对某些用户而言很重要,尤其是对视力受损的人,所以应该始终用相对单位或者百分比设置字号。
与em相比,rem降低了复杂性。实际上,rem结合了px和em的优点,既保留了相对单位的优势,又简单易用。那是不是应该全用rem,抛弃其他选择呢?答案是否定的。
在CSS里,答案通常是“看情况”。rem只是你工具包中的一种工具。掌握CSS很重要的一点是学会在适当的场景使用适当的工具。我一般会用rem设置字号,用px设置边框,用em设置其他大部分属性,尤其是内边距、外边距和圆角(不过我有时用百分比设置容器宽度)。
这样字号是可预测的,同时还能在其他因素改变元素字号时,借助em缩放内外边距。用px定义边框也很好用,尤其是想要一个好看又精致的线时。这些是我在设置各种属性时常用的单位,但它们仅仅是工具,在某些情况下,用其他工具会更好。
提示 拿不准的时候,用rem设置字号,用px设置边框,用em设置其他大部分属性。