跳到主要内容

CSS 中的单位

为什么要使用相对单位

CSS 带来的抽象性也带来了额外的复杂性。如果给一个元素设置 800px 的宽度,在小窗口下会是什么样?水平菜单如果无法在一行显示会是什么样?在写 CSS 的时候,我们既要考虑整体性,也要考虑差异性。当有很多方法解决同一个问题时,我们要选择能够兼顾更多情况的方法。

相对单位就是 CSS 用来解决这种抽象的一种工具。我们可以基于窗口大小来等比例地缩放字号,而不是固定为 14px,或者将网页上的任何元素的大小都相对于基础字号来设置,然后只用改一行代码就能缩放整个网页。

对于用户体验上也是需要考量的点:有些浏览器给用户提供了两种方式来设置文字大小:缩放操作和设置默认字号。按住 Ctrl +Ctrl - 用户可以缩放网页。这种操作会缩放所有的字和图片,让网页整体放大或者缩小。在某些浏览器中,这种改变只会临时对当前标签页生效,不会将缩放设置带到新的标签页。设置默认字号则不一样。不仅很难找到设置默认字号的地方(通常在浏览器的设置页),而且用这种方式改变字号会永久生效,除非用户再次修改默认值。这种方式的缺点是,它不会影响用 px 或者其他绝对单位设置的字号。由于默认的字号对某些用户而言很重要,尤其是对视力受损的人,所以应该始终用相对单位或者百分比设置字号

绝对长度单位

像素、点、派卡

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 通常等于一个物理英寸的大小。

em 和 rem

一般使用 rem 设置字号,用 px 设置边框,用 em 设置其它大部分属性,尤其是内边距、外边距和圆角(有时可以使用百分比去设置容器宽度)。

使用 em 定义字号

em 是最常见的相对长度单位,适合基于特定的字号进行排版。在 CSS 中,1em 等于当前元素的字号,其准确值取决于作用的元素。下图是一个内边距为 1em 的 div 元素。

它的代码如下所示。规则集指定了字号为 16px,也就是元素局部定义的 1em。然后使用 em 指定了元素的内边距。在 <div class="padded"> 中写一些文字,在浏览器中看看会是什么效果。

这里设置内边距的值为 1em。浏览器将其乘以字号,最终渲染为 16px。这一点很重要:浏览器会根据相对单位的值计算出绝对值,称作计算值(computed value)。

在本例中,设置内边距为 2em,会产生一个 32px 的计算值。如果另一个选择器也命中了相同的元素,并修改了字号,那么就会改变 em 的局部含义,计算出来的内边距也会随之变化。

当设置 padding、height、width、border-radius 等属性时,使用 em 会很方便。这是因为当元素继承了不同的字号,或者用户改变了字体设置时,这些属性会跟着元素均匀地缩放。

下图展示了两个不同大小的盒子,它们的字号、内边距和圆角都会不一样。

在定义这些盒子的样式时,可以用 em 指定内边距和圆角。给每个元素设置 1em 的内边距和圆角,再分别指定不同的字号,那么这些属性会随着字体一起缩放。

如下代码所示,在 HTML 中创建两个盒子。给元素分别添加 box-small 和 box-large 类名,作为大小修饰符。

<span class="box box-small">Small</span>
<span class="box box-large">Large</span>

这就是 em 的好处。可以定义一个元素的大小,然后只需要改变字号就能整体缩放元素。

当用 em 来指定多重嵌套的元素的字号时,就会产生意外的结果。为了算出每个元素的准确值,就需要知道继承的字号,如果这个值是在父元素上用 em 定义的,就需要知道父元素的继承值,以此类推,就会沿着 DOM 树一直往上查找

但是使用 em 给列表元素定义字号并且多级嵌套时,这个问题就显现出来了。绝大部分 Web 开发人员曾遇到过类似于图 2-5 的现象。文字缩小了!正是这种问题让开发人员惧怕使用 em。

当列表多级嵌套并且给每一级使用 em 定义字号时,就会发生文字缩小的现象。代码清单 2-8 的例子里,设置无序列表的字号为 0.8em。选择器选中了网页上每个 <ul> 元素,因此当这些列表从其他列表继承字号时,em 就会逐渐缩小字号。

body {
font-size: 16px;
}

ul {
font-size: .8em;
}

每个列表元素的字号等于 0.8 乘以其父元素的字号。算出来第一级列表的字号为 12.8px,第二级缩小到 10.24px(12.8px × 0.8),第三级缩小到 8.192px,以此类推。同理,如果指定一个大于 1em 的字号,文字会逐渐增大。我们想要的是指定顶部的字号,然后保持子级的字号一致,如图 2-6 所示

实现这种效果的代码如代码清单 2-9 所示。它设置第一级列表的字体为 0.8em。代码清单 2-9 里的第二个选择器选中了嵌套在某个无序列表中的所有无序列表,也就是除了顶级列表以外的其他列表。嵌套列表的字号等于其父级的字号,如图 2-6 所示。

这样确实解决了问题,尽管这个方式不完美。设置一个值,然后马上用另一个规则覆盖。如果不用提升选择器的优先级来覆盖规则,就更好了。

这些例子告诉我们,如果不小心的话,em 就会变得难以驾驭。em 用在内边距、外边距以及元素大小上很好,但是用在字号上就会很复杂。值得庆幸的是,我们有更好的选择:rem。

使用 rem 设置字号

rem 是 root em 的缩写。rem 不是相对于当前元素,而是相对于根元素的单位。不管在文档的什么位置使用 rem,1.2rem 都会有相同的计算值:1.2 乘以根元素的字号。

如下代码先指定了根元素的字号,然后用 rem 定义了无序列表的相对字号。

提示

在文档中,根节点是所有其他元素的祖先节点。根节点有一个伪类选择器(:root),可以用来选中它自己。这等价于类型选择器 html,但是 :root 的优先级相当于一个类名,而不是一个标签。

在这个例子里,根元素的字号为浏览器默认的字号 16px(根元素上的 em 是相对于浏览器默认值的)。无序列表的字号设置为 0.8rem,计算值为 12.8px。因为相对根元素,所以所有字号始终一致,就算是嵌套列表也一样。

设置一个合理的默认字号

如果你希望默认字号为 14px,那么不要将默认字体设置为 10px 然后再覆盖一遍,而应该直接将根元素字号设置为想要的值。将想要的值除以继承值(在这种情况下为浏览器默认值)是 14/16,等于 0.875。即,后续设置字号就不要再重新指定字号了,而是使用相对值,如下代码所示

第一个规则集指定了一个较小的默认字号,这是希望在小屏幕上显示的字号。然后使用媒体查询覆盖该值,在 800px、1200px 以及更大的屏幕上逐渐增大字号。

通过给页面根元素设置不同字号,我们响应式地重新定义了整个网页的 em 和 rem。也就是说,即使不直接修改面板的样式,它也是响应式的。在小屏上,比如智能手机上,字体会较小 (12px),内边距和圆角也相应较小。在大于 800px 和 1200px 的大屏上,组件会相应地分别放大到 14px 和 16px 的字号。缩放浏览器窗口可以看到这些变化。

构造响应式网页

更进一步地说,我们甚至可以根据屏幕尺寸,用媒体查询改变根元素的字号。这样就能够基于不同用户的屏幕尺寸,渲染出不同大小的面板

视口的相对单位 vw

前面介绍的 em 和 rem 都是相对于 font-size 定义的,但 CSS 里不止有这一种相对单位。还有相对于浏览器视口定义长度的视口的相对单位

视口:浏览器窗口里网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏、状态栏。

  • vh:视口高度的 1/100。
  • vw:视口宽度的 1/100。
  • vmin:视口宽、高中较小的一方的 1/100(IE9 中叫 vm,而不是 vmin)。
  • vmax:视口宽、高中较大的一方的 1/100

比如,50vw 等于视口宽度的一半,25vh 等于视口高度的 25%。vmin 取决于宽和高中较小的一方,这可以保证元素在屏幕方向变化时适应屏幕。在横屏时,vmin 取决于高度;在竖屏时,则取决于宽度。

图 2-10 展示了一个正方形元素在不同屏幕尺寸的视口中的样子。它的宽度和高度都是 90vmin,等于宽高的较小边的 90%,即横屏高度的 90%,或者竖屏宽度的 90%

.square {
width: 90vmin;
height: 90vmin;
background-color: #369;
}

视口相对长度非常适合展示一个填满屏幕的大图。我们可以将图片放在一个很长的容器里,然后设置图片的高度为 100vh,让它等于视口的高度。

使用 vw 定义字号

相对视口单位有一个不起眼的用途,就是设置字号,但我发现它比用 vh 和 vw 设置元素的宽和高还要实用。

如果给一个元素加上 font-size: 2vw 会发生什么?在一个 1200px 的桌面显示器上,计算值为 24px(1200 的 2%)。在一个 768px 宽的平板上,计算值约为 15px(768 的 2%)。这样做的好处在于元素能够在这两种大小之间平滑地过渡,这意味着不会在某个断点突然改变。当视口大小改变时,元素会逐渐过渡。

不幸的是,24px 在大屏上来说太大了。更糟糕的是,在 iPhone 6 上会缩小到只有 7.5px。如果能够保留这种缩放的能力,但是让极端情况缓和一些就更棒了。CSS 的 calc() 函数可以提供帮助。

使用 calc() 定义字号

calc() 函数内可以对两个及其以上的值进行基本运算。当要结合不同单位的值时,calc() 特别实用。它支持的运算包括:加(+)、减(−)、乘(×)、除(÷)。加号和减号两边必须有空白,因此我建议大家养成在每个操作符前后都加上一个空格的习惯,比如 calc(1em + 10px)

:root {
font-size: calc(0.5em + 1vw);
}

现在打开网页,慢慢缩放浏览器,字体会平滑地缩放。0.5em 保证了最小字号,1vw 则确保了字体会随着视口缩放。这段代码保证基础字号从 iPhone 6 里的 11.75px 一直过渡到 1200px 的浏览器窗口里的 20px。可以按照自己的喜好调整这个值

References

  • 《深入解析 CSS》