相对单位

本章概要

  • 相对单位的广泛用途
  • 驾驭令人头疼的 em 和 rem
  • 使用视口的相对单位
  • 自定义属性简介

说起长度(length)值的设置,CSS 提供了丰富多样的实现方案。其中最熟悉、或许也是最容易掌握的,莫过于像素(pixels)了。像素是一种 绝对 单位;也就是说,5px 无论搁哪儿都是 5 像素大小;而另一些单位,比如 em 和 rem,则不属于绝对单位,而是相对单位。相对单位的值会根据外部因素而改变;例如,2em 的具体含义,就取决于其所对应的元素,有时甚至取决于它所对应的 property 属性。这么一闹,相对单位自然也就更难掌握了。

定义

长度(Length) 是 CSS 表示衡量某段距离大小的样式值的正式称谓。它是一个后面带着单位的数字,例如 5px。百分比取值(Percentages)与长度类似,但严格意义上讲,它并非长度。

即便是经验丰富的 CSS 开发人员,在处理长度时通常也不喜欢使用相对单位——相对单位的取值可能会发生改变,这使得它们看起来比绝对单位更加不可预测,用起来也没有像素那么直观。

本章将为您揭开相对单位的神秘面纱。首先需要正名的,相对单位带给 CSS 的独特价值,并帮助您理解它们;然后剖析相对单位的工作原理,并进一步演示如何驾驭相对单位看似无法预测的本性。使用恰当的话,相对单位也可以为我所用,使代码更简洁、更灵活、也更简单。

2.1 相对单位的威力

CSS 为网页引入了样式 延迟绑定(late-binding)机制:网页内容与对应的样式可以在二者的创作分别完成以后再整合到一起。这无疑增加了设计过程的复杂度——这样的机制在其他类型的图形设计中是不存在的;但这也让 CSS 具备更强大的功能:只需一张样式表,就能同时对成百上千个页面生效。此外,用户还可以直接改变页面渲染的最终效果,例如修改默认字号或者调整浏览器窗口大小。

在早期的计算机应用程序开发(以及传统的出版行业)中,开发人员(或者出版商)预先知晓其媒介的确切参数:一个典型的程序窗口应该宽 800 像素、高 500 像素;一个页面应该有 4 英寸宽、6½ 英寸高等等。这样一来,当开发人员在给应用程序的按钮和文本做布局时,他们可以精确预判各元素在屏幕上的大小,并精确计算需要给其他元素预留多大的空间。而在网页上,情况却并非如此。

2.1.1 响应式设计的兴起

在 Web 环境下,用户可以任意指定浏览器的窗口大小,CSS 也必须适应这些变化的尺寸;此外,在网页打开后,用户还可以进一步缩放页面,此时 CSS 同样需要适应新的约束条件。这就意味着 CSS 样式不能在创建页面时就提前写死;而必须等到页面被渲染到屏幕后,再让浏览器动态计算相应的样式才有意义。

这就为 CSS 平添了一个抽象层出来。人们不能根据理想条件给元素添加样式;而是要让设计的样式规则,能对任意条件下的目标元素始终生效。就目前的 Web 环境而言,一个页面必须既能够在 4 英寸的手机屏幕上渲染良好,也必须在 30 英寸的显示器上呈现出想要的效果。

像素、磅和排版点

在 CSS 支持的几种绝对长度单位中,最常见、最基础的是像素(px)。不常用的绝对单位包括 mm(毫米)、cm(厘米)、Q(quarter-millimeter,¼ 毫米)、in.(英寸)、pt(point,磅,印刷术语,大小为 1/72 英寸)、pc(pica,排版点,印刷术语,大小为 12 磅)。这些单位都可以通过公式相互换算:1in. = 25.4mm = 101.6Q = 2.54cm = 6pc = 72pt = 96px。因此,16px 等同于 12pt(16 / 96 × 72)。印刷设计师经常用磅作单位,而开发人员则更习惯用像素,因此在与设计师交流时需要做一些单位换算。

像素是一个略带误导性的名称,它并不严格等同于显示器上的像素。这在高分辨率显示屏(例如 “retina” 视网膜屏)上尤为明显。尽管 CSS 的测量结果会根据浏览器、操作系统和硬件的情况进行适当缩放,但通常情况下,96px 大概相当于屏幕上 1 物理英寸大小;然而这种换算关系在某些设备或者用户设置的分辨率设置下也可能会有所不同。

由于用户设备和屏幕尺寸的种类繁多,我们必须时刻注意 响应式设计(responsive design)。假如给一个元素设置 800px 的宽度,它在小窗口下会是什么样?如果水平菜单在一行显示上又会是什么样?在写 CSS 的时候,需要同时兼顾特殊性和普遍性。当一个特定问题有多种解决方案时,应该优先考虑在多种不同情况下更通用的解决方案。

响应式设计:: 由 Ethan Marcotte 创造的一个术语,指的是页面样式需要基于不同的浏览器窗口尺寸作出不同的“响应”。这就意味着要有意识地将任意尺寸的移动设备、平板电脑或桌面屏幕纳入考虑范围之内。本书 第七章 会详细介绍响应式设计,但本章会先普及一些重要的基础知识。

相对单位就是 CSS 用来处理这一抽象问题的工具之一。我们可以基于窗口大小来等比例地缩放字号,而不是设为固定值 14px;也可以将网页上的所有元素尺寸都相对于基础字号进行统一设置,这样只用改动一行代码,整个页面就能同步缩放。让我们看看 CSS 是如何实现这些功能的。

2.2 em 与 rem

em 是最常见的相对长度单位,也是排版中用于设置字体大小(即字号)的度量单位。在 CSS 中,1em 表示当前元素的字号,具体大小取决于作用的元素。图 2.1 为一个内边距 padding 为 1em 的 div 元素:

Figure 2.1 One em of padding is equal to the font size (dashed lines added to illustrate padding)
图2.1 1em 内边距等同于当前字号(虚线用于展示内边距大小)

其样式代码如代码清单 2.1 所示。规则集指定字号为 16px,即该元素在本地定义的 1em 的实际大小。随后又用 em 设置了其内边距。将代码清单 2.1 中的样式拷到一个新样式表,并在元素 <div class="padded" 中放一些文字,看看最终效果。

代码清单 2.1 对 padding 使用相对单位 em

.padded {
  font-size: 16px;
  padding: 1em; /* Sets padding on all sides equal to font-size */
}

可以看到 padding 的值为 1em,再乘以字号,则渲染出了大小为 16px 的内边距。重点来了:浏览器根据相对单位声明的值计算出的绝对单位值,称为 计算值(computed value)

若将本例中的 padding 改为 1.5em,则计算值变为 24px;如果另一个选择器也指向该元素并设置了不同的字号,则会修改本地 em 的大小,算出的 padding 值也会随之更新。

使用 em 来设置 padding、height、width 及 border-radius 会非常方便,因为当元素继承了不同的字号,或者用户变更了字体设置,这些样式也会跟随当前元素均匀地缩放。

图 2.2 展示了两个不同大小的盒子,它们的字号、内边距及圆角半径都各不相同:
Figure 2.2 Relatively sized padding and border radius change with the font size.
图 2.2 设置了相对大小的内边距和圆角半径会随字号的改变而同步缩放

为上述盒子定义样式时,可以用 em 指定其内边距及圆角半径,比如设为 1em;此时修改各元素的字号,这些属性就会随着字体一起缩放。

按以下代码更新 HTML 示例文件,并给元素分别添加样式类 box-small、box-large,作为尺寸修饰符:

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

接着,按照代码清单 2.2 所示添加样式。该样式以 em 为单位定义了一个 box 类,同时还指定了上述一大一小两个修饰符,利用不同的字号大小缩放各自所在的元素。

代码清单 2.2 将 em 应用到不同元素

.box {
  padding: 1em;
  border-radius: 1em;
  background-color: lightgray;
}
 
.box-small {
  font-size: 12px;  /* 与 L12 不同的字号,该字号定义了当前元素的 em 大小 */
}
 
.box-large {
  font-size: 18px;  /* 与 L8 不同的字号,该字号定义了当前元素的 em 大小 */
}

这就是 em 的强大之处:定义某个元素的大小后,只需一句变更字号的样式声明,就能缩放整个元素。稍后会再举一例,在此之前,先聊聊 em 和字号设置相关的话题。

2.2.1 使用 em 定义字号

当 em 作用于 font-size 属性(property)时,其表现略有不同。之前讲到,当前元素字号决定了 em 的大小;但如果声明 font-size: 1.2em 会怎样呢?一个字号肯定不能等于自身的 1.2 倍;事实上,font-size 属性上的 em 的大小是基于它继承的字号计算出来的。

举个简单的例子。如图 2.3 所示,有两段字号各异的文字,样式按代码清单 2.3 所示,用 em 进行设置。

Figure 2.3 two different font sizes using ems
图 2.3 使用 em 定义两种不同的字号

按以下代码更新页面。第一行文本在 <body> 标签内,则会按 body 的字号进行渲染;带 slogan 样式类那行,则会继承该字号来渲染:

<body>
  We love coffee
  <p class="slogan">We love coffee</p> <!-- slogan 继承了 <body> 的字号 -->
</body>

代码清单 2.3 中的样式指定了 body 的字号,为便于演示,这里用像素作单位。接着使用 em 来增大 slogan 的字体大小。

代码清单 2.3 使用 em 定义 font-size

body {
  font-size: 16px;
}
.slogan {
  font-size: 1.2em; /* 经计算,字号为该元素继承字号的 1.2 倍 */
}

此时 slogan 的字号为 1.2em。要拿到计算出的像素值,需要参考继承来的大小为 16px 的字体。由于 16 × 1.2 = 19.2,所以算出的实际字体大小为 19.2px。

提示
如果已知字号的像素值,但是想声明为 em 的形式,则用这个简单公式换算:目标像素值 ÷ 父元素(继承)字号的像素值。比如,目标字号为 10px,该元素继承的字号为 12px,转成 em 则为 10 / 12 = 0.8333em;目标字号 16px、父级字号 12px,转成 em 则为 16 / 12 = 1.3333em。本章还会进行几次类似的计算。

大多数浏览器的默认字号均为 16px,记住这些知识将大有好处。用专业的话来讲,关键字 medium 的值经计算为 16px 大小。

  1. em 同时用于字号和其他属性
    至此,我们已经用 em 定义了字号(基于继承的字号),并且通过 em 定义了其他属性,比如 padding 和 border-radius(基于当前元素的字号)。当使用 em 给同一个元素同时设置字号和其他属性时,情况就变得复杂多了。此时,浏览器必须先计算字号,然后再利用算出的结果进一步算出其余属性的具体取值。这两类属性的声明值可能相同,但计算值却未必相等。

前面的示例中,带有 slogan 样式类的元素最终字号为 19.2px(=继承字号 16px × 1.2em)。如图 2.4 所示,元素还是 slogan 不变,只是内边距 padding 调大到 1.2em。背景设为灰色以便观察内边距的实际效果。可以看到内边距比字号还要偏大一些,尽管二者的声明值都相同。

Figure 2.4 A font size in ems differs from padding in ems
图 2.4 em 定义的字号有别于同样用 em 定义的内边距

这是因为段落标签从 body 元素继承了 16px 的字号,实际字号变为了 19.2px。此时 19.2px 即为该段落元素 1em 的最终大小,padding 的具体大小也是基于这个值进行计算的。相应的 CSS 代码如下所示,更新到示例页查看最终效果:

代码清单 2.4 使用 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 的声明值相同,但计算值却不相等。

  1. 字体缩小的问题
    当 em 用于设置具有多级嵌套结构的元素字体时,也会产生意想不到的结果。为了算出每个元素的具体大小,就得知道它们继承的字号是多少;如果父元素碰巧也是用 em 来定义的,就要看该父元素的继承值是多少,以此类推,一直沿着 DOM 树向上考察。

当使用 em 给列表元素定义字号、列表又嵌套了多级子列表时,问题很快就显现出来了。几乎每一位 Web 开发人员在职业生涯的某个阶段加载这样的页面都会碰到类似图 2.5 所示的情况。文字在逐级缩小!正是这样的问题让广大开发人员对 em 敬而远之。

Figure 2.5 Font size of 0.8 em causing shrinking text when applied to nested lists
图 2.5 字号设为 0.8em 导致嵌套列表中的文字逐级缩小

当列表又嵌套了多级子列表、并且给列表逐级设置基于 em 的相对字号时,就会发生文字缩小的现象(译注:其值须小于 1 才会缩小,大于 1 则为逐级放大)。如代码清单 2.5 所示,将无序列表的字号设为 0.8em 后,由于该选择器会对页面上每一个 <ul> 元素生效,从而让内层元素逐级继承外层元素的 em 字号,字体的坍缩幅度也随之逐级叠加。

代码清单 2.5 使用 em 指定无序列表的字号

body {
  font-size: 16px;
}
ul {
  font-size: 0.8em;
}

若将上述样式应用到代码清单 2.6 所示的 HTML 中,就会出问题。每一个 <ul> 都从父列表继承字号,这些 em 值只会让字体逐渐缩小:

代码清单 2.6 嵌套列表 HTML

<ul>
  <li>Top level
    <ul> <!-- 该列表嵌套在第一个列表中,继承第一个列表的字号 -->
      <li>Second level
        <ul> <!-- 该列表嵌套在上一个列表中,继承第二个列表的字号 -->
          <li>Third level
            <ul> <!-- 依此类推 -->
              <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 所示。

Figure 2.6 Using a font size of 1 em for nested lists keeps the text size consistent.
图 2.6 文字大小正常的嵌套列表

实现上述效果的一种解决方案如代码清单 2.7 所示。先设置一级列表的字号为 0.8em(同代码清单 2.5);再用第二个选择器选中除最顶层外、所有无序列表下的所有后代列表,并设置字号等于其父级字号,最后得到如图 2.6 所示的效果。

代码清单 2.7 更正文字缩小问题

ul {
  font-size: 0.8em;
}
ul ul {               
  font-size: 1em; /* 嵌套列表的字号应与其父级字号一致 */
}

问题倒是解决了,尽管不是很理想——定好一个字号,立马又用另一个字号去覆盖掉。如果不用提高选择器的优先级来覆盖规则,就再好不过了。

至此,各位也该心里有数了:使用 em 稍有不慎就会变得难以驾驭。em 用在内边距、外边距以及元素尺寸上时挺省心的;可一旦用到字号上,省心就容易变成闹心。好在 CSS 还有一个更好的方案—— rem。

2.2.2 使用 rem 设置字号

当浏览器解析 HTML 文档时,会在内存里创建一个包含页面所有元素的模型结构,称为 DOM(Document Object Model,即文档对象模型)。它是一个树形结构,其中的每个元素都由一个对应的节点表示。<html> 元素是顶级(根)节点。在它下面是子节点 <head><body>。再往下是它们的子节点,以及子节点的子节点,逐级嵌套,依此类推。

根节点是 HTML 文档中所有其他元素的祖先节点。根节点有一个伪类选择器(:root)以供选中。它等价于类型选择器(type selector)html,但 :root 的优先级相当于一个类选择器,而非标签选择器。

rem 是“root em”的缩写。rem 不是基于当前元素,而是一个相对于根元素的单位制。无论在文档的任意位置使用 rem,1.2rem 都只有一个相同的计算值:1.2 倍根元素的字号。代码清单 2.8 设置了根节点的字号,并通过 rem 定义了无序列表的相对字号。

代码清单 2.8 使用 rem 指定字号

:root { /* 伪类 :root 在效果上相当于标签选择器 HTML */
  font-size: 1em; /* 使用浏览器的默认字号 16px */
}
ul {
  font-size: 0.8rem;
}

本例中,根节点的字号为浏览器默认的 16px(根元素上的一个单位 em 是相对于浏览器的默认值字号而言的)。无序列表的字号设为 0.8rem,其计算机则为 12.8px。因为相对根节点,所有字号将始终保持一致,嵌套列表也不例外。

rem 大大降低了因使用 em 引入的复杂度。实际上,rem 综合了像素 px 和相对单位 em 的优点,既发挥出了相对单位的优势,又简单易用。如此说来,是不是可以全用 rem 而抛弃其他单位呢?答案显然是否定的。

别忘了,CSS 的答案通常都是“看情况”。rem 也只是工具包中的一种工具而已。掌握 CSS 很重要的一点是学会因地制宜地使用适当的工具(when to use which tool)。我一般会用 rem 设置字号,用像素设置边框,再用 em 和 rem 设置其他大部分属性,尤其是 padding(内边距)、margin(外边距)以及圆角半径(border radius)。

这样一来,字号就是可预测的,就算其他因素改变了元素的字号,仍然可以借助 em 的强大威力实现内外边距的同步缩放。用像素设置的边框也特别好用,尤其是想要一个好看又精致的直线效果时。这些是我在设计各种样式时的处理单位问题的首选方案——尽管我的个人偏好也随时间及项目而变化。然而它们仅仅是工具,在某些情况下,换个工具可能效果会更好。

小贴士

拿不准的时候,就用 rem 设置字号,用 px 设置边框,再用 em 或 rem 设置其他大部分样式属性(property)。

可访问性:对字号使用相对单位

一些浏览器为用户提供了两种方式来设置字体大小:页面缩放和设置浏览器默认字号。按住 Ctrl + +Ctrl + - 或者在苹果 Mac 电脑使用 Cmd + +/- 就能缩放网页。该操作会缩放所有文字与图片,从而让网页整体放大或缩小。而在另一些浏览器中,这些变更只会临时对当前标签页生效,不会将其同步到新的标签页。

而第二种方式——设置默认字号则不太一样。默认字号通常是在浏览器的设置页操作,这样设置的字号会永久生效,除非用户再次进行修改。这种方式的缺点是,对于用 px 或其他绝对单位设置字号的网页,用户手动设置的浏览器默认字号将会失效。由于默认字号对于某些用户而言至关重要,尤其是视力受损人士,因此应该始终优先考虑通过相对单位或百分比来设置字号。

刚开始使用像素单位可能更容易上手,也更好理解;但如果花时间熟悉相对单位 rem,您就会拥有更广泛更丰富的工具选择,同时也可以创建出可访问性更好的无障碍页面。

2 .3 告别像素思维

过去常见的一种设计样式的做法(pattern),更准确地说是反常规的做法(antipattern),是将页面根元素的字号设为 0.625em 或 62.5%;于是浏览器的默认字号就从 16px 缩小到了 10px。这种做法看似简化了数学运算——当设计师说字号为 14px,您只要在心中轻松除以 10,再让字号为 1.4rem 即可,顺便还兼顾了相对单位,例如:

html {
  font-size: 0.625em;
}

但我不推荐这样做。

这么做一开始可能很方便,但存在两个缺点。首先是会导致大量冗余的样式代码。10px 用于文字还是太小了,因此只好在页面各处做补救:段落看着小,改到 1.6rem;侧边栏感觉也小,就再来个 1.6rem;貌似超链接也要 1.6rem……这样一来,代码出错的概率就更大、今后更新样式要改的地方就更多、样式表也会变得更臃肿。

第二,这么做本质上还是像素思维。虽说代码写的是 1.6rem,但脑子里仍然想的是 16px。在响应式网页上,您需要习惯“模糊(fuzzy)”值:1.2em 究竟是多少像素并不重要;重要的是它比继承的字号大那么一点(即 20%);就算不是想要的大小,改改就是了,反复试错,直到满意为止。其实用像素的话也是这么个过程。

使用 em 时很容易陷入具体尺寸的泥沼,纠结于元素计算后的具体像素,尤其像字号这类;为了揪出 em 值的精确大小,您很可能会在无穷无尽的乘法、除法间反复横跳,直到抓狂。与其如此狼狈,不如听我一句劝,首先养成使用 em 的习惯。如果用惯了像素,切换到 em 肯定需要反复练习,但这一切都是值得的。

这并不是说您今后再也不能使用像素了。在和设计师打交道时,适当用一些具体的像素值进行交流也是无可厚非的;在项目初期,也需要确定基本的字号(通常是些标题和脚注的常用字号)。这时用绝对数值来讨论尺寸大小也更通俗易懂。

将大小转换为 rem 值需要做算术题,记得随手备个计算器。给根节点一个字号,也就定义了单位 rem 的大小。自此之后,像素应该仅限于在少数特殊情况下使用,而不能随处可见。

本章还会继续提到像素,这不仅有利于演示相对单位的行为方式,同时也可以帮您习惯 em 的计算。本章过后,将主要使用相对单位来讨论字号。

2.3.1 设置一个合理的默认字号

假设您希望默认字号为 14px,那么就不必先给整个页面一个 10px 的默认字号然后再覆盖掉;直接将根元素的字号设为 14px 即可。期望的值除以继承值(此时为浏览器的默认字号)为 14/16,等于 0.875。在这里使用 em 既调整了默认字号,同时又尊重了用户的字体设置。

将以下代码添加到新样式表的顶部,以便在它基础上设计样式。这样就设置了根节点(<html>)的默认字体。

代码清单 2.9 设置真正的默认字号

:root {  /* 或使用 HTML 选择器 */
  font-size: 0.875em; /* 14/16 (期望的px / 继承的px) = 0.875 */
}

现在页面上已经有了想要的字号,就不必在其他地方画蛇添足了;要改也只改和设计的默认字号不一致的地方,例如标题。

接下来创建一个如图 2.7 所示的面板。需要基于 14px 的字号,利用相对单位来实现。

图 2.7 使用相对单位和继承的字体大小创建的示例面板

该面板的 HTML 标记如下。添加到示例页:

代码清单 2.10 面板的 HTML 标记

<div class="panel">
  <h2>Single-origin</h2>
  <div class="panel-body">
    We have built partnerships with small farms around the world to
    hand-select beans at the peak of season. We then carefully roast
    in <a href="/batch-size">small batches</a> to maximize their
    potential.
  </div>
</div>

以下是相应的样式代码:用 em 设置内边距和圆角半径,用 rem 设置标题字号,再用 px 设置边框。更新到您的示例样式表中:

代码清单 2.11 使用相对单位的面板样式

.panel {
  /* 用 em 设置内边距和圆角 */
  padding: 1em;
  border-radius: 0.5em;
  /* 用 1px 添加一个细边框 */
  border: 1px solid #999; 
}
.panel > h2 {
  margin-top: 0;  /* 移除面板顶部的多余空间,后续第3章详述 */
  font-size: 0.8rem;  /* 用 rem 设置标题字号 */
  font-weight: bold;
  text-transform: uppercase;
}

代码清单 2.14 给面板四周添加了一个细边框,并给标题指定了样式。该标题虽然字号偏小、但做了字体加粗和内容全大写处理。(如果设计需要的话,可以改为更大的字号,或者换用其他字体)。

第二个选择器中的 > 是一个 直接后代组合器(direct descendant combinator);它表示选择器中的两元素间存在直接父子关系。此时,该选择器将选中任意做 .panel 元素直接子元素的 h2 元素。(有关选择器和组合器的完整参考资料,参见 附录 A。)

在代码清单 2.10 中,给面板主体添加 panel-body 类只是为了明确含义,在 CSS 中并未用到。因为该元素已经继承了根元素的字号,渲染出来就是期望的效果,无需再变更。

2.3.2 构造响应式面板

更进一步地说,我们甚至可以基于屏幕尺寸,用 媒体查询 来改变根元素的字号。

媒体查询要用到 @media 规则,用于设置仅满足特定屏幕尺寸或媒体类型(例如打印机或屏幕)下的样式。它是响应式设计的核心要素。面板将根据用户屏幕的大小渲染出不同的尺寸(如图 2.8 所示)。

Figure 2.8 A responsive panel as seen on different screen sizes: 300 px (top left), 800 px (top right), and 1,440 px (bottom)
图 2.8 不同屏幕尺寸下的响应式面板:300px(左上)、800px(右上)、1440px(底部)

要查看页面效果,按如下代码更新样式:

代码清单 2.12 响应式的基础字号

:root {/* 作用于所有屏幕,但在会被更大屏幕的样式覆盖 */
  font-size: 0.85em;
}
@media (min-width: 800px) {/* 仅适用于 800px 及以上屏幕,并覆盖之前的值 */
  :root {
    font-size: 1em;
  }
}
@media (min-width: 1200px) {/* 仅适用于 1200px 及以上的屏幕,覆盖前面的两个值 */
  :root {
    font-size: 1.15em;
  }
}

上述代码的第一个规则集指定了一个较小的默认字号,这也是我们希望在小屏幕上显示的字号;然后利用媒体查询技术,分别在 800px 和 1200px 及以上 的宽屏上依次逐渐增大字号来覆盖掉默认的字号值。

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

如果足够严谨,整个页面的样式都像这样使用相对单位来定义,那么页面就会根据用户浏览器窗口的大小整体缩放。这可以成为设计响应式策略的重要组成部分。靠近样式表顶部的这两个媒体查询,可以极大减少后续 CSS 代码中媒体查询的数量。如果是用像素来定义的,就没那么容易实现了。

同样,如果后来发觉网站上的字体太小或太大,这时仅需改动一行代码就能整体切换字号,进而不费吹灰之力影响整个页面。

2.3.3 缩放单个组件

您还可以通过 em 来单独缩放某个页面组件,比如有时可能需要让同一个组件在页面的某些位置渲染出一个更大的版本。还是用之前的面板来举例说明。首先给面板添加一个 large 类:<div class="panel large">

图 2.9 展示了普通面板和大尺寸面板的区别。效果类似于响应式面板,但这两种尺寸都可以同时在同一页面中渲染出来:

图 2.9 ems 定义的面板可以通过增大字号来放大

下面对定义面板字号的方法略作修改。还是使用相对单位,只不过要改一下它们的参照对象。首先,在每个面板的父元素中添加声明 font-size: 1rem,这样每个面板无论在页面哪个位置,都有一个可预测的字号。

其次,重新定义标题字号,改成 em 单位制而非 rem,使其相对于新改好的父元素上的 1rem 字号。按代码清单 2.13 更新样式:

代码清单 2.13 创建一个大面板

.panel {
  font-size: 1rem; /* 给组件设置一个可预测的字号 */
  padding: 1em;
  border: 1px solid #999;
  border-radius: 0.5em;
}
.panel > h2 {
  margin-top: 0;
  font-size: 0.8em;  /* 用 em 定义其他字号,使其相对于父元素字号 */
  font-weight: bold;
  text-transform: uppercase;
}

本次修改并不会影响面板的外观,但它可以通过仅新增单个声明来放大面板:用另一个样式值来覆盖父元素上的 1rem 字号即可。由于所有组件的测量值都是基于它的,覆盖后势必影响整个面板的大小。可以通过以下代码定义一个更大的面板:

.panel.large { /* 复合选择器选中同时带 panel 和 large 样式类的元素 */
  font-size: 1.2rem;
}

至此,用 class="panel" 将得到一个普通面板;而用 class="panel large" 则将得到一个更大的面板。同理,可以通过设置较小的字号来缩小面板。如果该面板是一个更复杂的组件,并具有多个字号或内边距,只要其内部样式都是用 em 定义的,就仍然可以仅通过一个声明来独立完成缩放。

您无需完全照搬我演示的内容来操作。em 和 rem 的动态特性是一个强有力的工具,可以结合特定需求进行调整。例如,如果不想让圆角半径同步缩放,将其改成 rem 即可,并在希望同步缩放的属性上使用 em 就行了。花时间熟悉这些相对单位,将使您拥有远比纯像素思维更大且更丰富的选择空间。

CSS:一个生机勃勃的活标准

CSS 由大量 W3C 规范定义而成。最初的 CSS 是一个带版本号的单一规范,但在 2.1 版之后,情况发生了变化。

从那以后,CSS 规范分解为独立的模块,每个模块都有各自独立的版本号。背景和边框的规范脱离了盒模型(box model)模块,以及层叠和继承(cascading and inheritance)模块。这样 W3C 就能够制定 CSS 某个细分领域的新版本,而无需更新其他没发生变化的领域。其中部分规范仍然停留在第 3 版(现称第 3 级,即 level 3);但其他规范已经处于第 4 级或更高级别了,如选择器规范(selectors specification);还有一些规范,如弹性盒子(Flexbox)规范,还处于第 1 级。

也就是说,我们将不再使用某个特定版本的 CSS。它是一个不断发展的活标准(living standard)。每个浏览器都在持续增加对新特性的支持;开发人员也在不断使用这些新特性,并适应这些变化。您可能还会听到“CSS3”这个称谓,但严格来讲,它并不是指某个 CSS 规范的 3.0 版本了,而是指 2010 年代初在短期内发布的一系列规范。

CSS 工作组直到最近才决定支持“CSS4”和“CSS5”的定义。它们不会是规范的具体版本,而是成为一个特性功能的集合(assembly):一个由稳定的、业已构成该语言基本组成部分的特性功能(CSS4)、与目前采用率正稳步攀升的新功能特性(CSS5)的集合。本书不会使用这些称谓,因为它们尚未完全定义;但我会介绍这两个类别的功能,甚至一些最终有望被视为部分 CSS6 的全新功能。

em 和 rem 是最常用的相对单位,但它们并不是唯一可用的相对单位类型。在了解了它们提供的灵活性后,下面再来看看另一种重要的相对单位。

2.4 视口的相对单位

前面介绍过的 em 和 rem 是相对于 font-size 定义的,但相对单位并非只有这一类情况;还有一类 视口相对单位(viewport-relative units),用于定义相对于浏览器视口的长度。

视口(viewport):: 是浏览器窗口中网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏和(可能显示的)状态栏。

最近 CSS 语言对于视口相对单位又增加了一些新内容。目前 CSS 中总共有 24 个视口相对单元。这听起来可能让人难以接受(overwhelming),但它们都是由一些相对简单的概念通过多种方式的混搭而衍生出来的。本节将从最基本的知识点开始介绍。以下是最初添加到 CSS 语言中的四个基础视口相对单位:

vh —— 视口高度的 1%;
vw —— 视口宽度的 1%;
vmin —— 视口宽、高中较小的一方的 1%;
vmax —— 视口宽、高中较大的一方的 1%。

例如,50vw 等于视口宽度的一半,25vh 等于视口高度的 25%。vmin 取决于宽或高中尺寸较小的一方。这样就保证了元素始终都能适应屏幕尺寸,而与设备方向无关:横屏时 vmin 取决于视口高度;竖屏时则取决于视口宽度。

图 2.10 展示了一个正方形元素在不同屏幕尺寸的视口中的显示效果。它的宽高均为 90vmin,也就是两个维度中较小一方的 90%,即横屏时高度的 90%,或竖屏时高度的 90%。

Figure 2.10 An element with a height and width of 90 vmin will always display as a square a little smaller than the viewport, regardless of its size or orientation.
图 2.10 宽高均为 90vmin 的元素,无论视口大小或方向如何,都会显示成一个稍小于视口的正方形

代码清单 2.14 为该元素的样式代码。无论浏览器如何缩放,最终都能得到一个适应视口的大正方形。在页面中添加一段 <div class= "square"> 就能看到效果。

代码清单 2.14 用 vmin 定义正方形元素的尺寸

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

视口相对长度非常适合展示一个填满屏幕的大图。也可以将图片放在一个长容器内,给定高度为 100vh,理论上就能使其与视口高度完全一致。

当这些相对单位开始用于往视口填充大图后,人们发现一个问题:在移动设备上,视口的尺寸会动态变化。为了让屏幕可用尺寸最大化,移动端浏览器设计了这样一个功能:当用户向下滑动页面时,一些与用户体验相关的控件,如屏幕顶部的地址栏以及底部的导航按钮,会滑出视窗;而当用户向上滑动页面时,这些控件又会恢复显示。

这些动态变化会导致视口大小的缩放,进而改变页面上使用 vh 作单位的元素尺寸,并最终导致下方内容在屏幕上跳动。想象一下这样的场景:在用户向下滑过多个 100vh 的方盒后,又稍微向上翻了一下。这将导致视图中的所有内容猛地上窜几百个像素,带来极度不协调的阅读体验;况且还有性能问题——因为浏览器必须重新计算页面布局(通常称之为 布局抖动(layout thrashing))。

最终,大多数移动端浏览器都根据视口能够达到的最大尺寸重新解释了 vh,并忽略地址栏对视口尺寸的影响,从而阻止了该抖动行为。但有时开发人员可能并不想这样处理,因此 CSS 规范中又补充了几个相对单位。

2.4.1 从新的视口单位遴选

为了解决布局抖动的问题,CSS 引入了大视口(large viewports)和小视口(small viewports)的概念。大视口是在浏览器隐藏所有用户体验元素时的最大视口;而小视口则恰恰相反,是这些元素都正常显示时的浏览器最小视口(如图 2.11 所示)。

Figure 2.11 Comparison of the large and small viewports on a mobile device

图 2.11 移动设备大小视口对比

与普通的视口单位一样,大视口单位也可用于设置宽度、高度、最小值和最大值。使用大视口单位,需在前面加上字母 l(即 large 的首字母小写):lvw、lvh、lvmin、lvmax;同理,前面加上字母 s(即 small 的首字母小写)则表示使用小视口单位:svw、svh、svmin、svmax 。

有了这些新单元,开发人员就能根据当前场景遴选出更重要的行为模式:满屏高度的报头(masthead)是否必须占满整个屏幕,即便超出浏览器底部一点点也无所谓吗?如果是的话,就用 height: 100lvh;还是说,整个报头保持可见更重要,就算报头下方、屏幕底部会空出一点空间也无伤大雅?如果是的话,就用 height: 100svh。

此外,以下注意事项也要牢牢记住:

视口单位制没有考虑滚动条:也就是说,当存在垂直滚动条时,宽度为 100svw 的元素会产生水平滚动。
当存在屏幕键盘时,关于小视口单位是否应该缩小屏幕尺寸,CSS 规范尚未明确规定。目前一些安卓浏览器是这样处理的,但 iOS 浏览器不是;未来可能还会变动。
在绝大多数浏览器中,原始视口单位往往表现为大视口单位,但事无绝对。
这就又留下一个悬而未决的问题:某些情况下可能需要视口单位的原始行为,即当浏览器的用户体验元素出现或隐藏时,视口单位会动态切换。对于这种行为,CSS 提供了第三类视口相对单元:动态视口(dynamic viewport)。启用它们,只需在视口单位前加上字母 d(即 dynamic 的首字母小写)即可:dvw、dvh、dvmin、dvmax。当视口较小时,其行为类似小视口单位;而当视口较大时,又类似大视口单元。

使用动态视口单位时务必慎之又慎——当用户在移动设备上下滚动页面时,一旦元素高度发生动态变化,很可能会出现布局抖动。

表 2.1 列出了所有可用的视口相对单位。为了完整起见,还包括一组额外的单位类型:内联(inline)与块(block)。这两种类型被称为“逻辑属性”,其行为分别类似于宽度和高度,但对于日语等垂直书写的语言,则需要对调一下。本书第 3 章还将深入介绍逻辑属性。

表 2.1 视口相对单位一览表

未指定视口(原始单位)大视口小视口动态视口
宽/高vwlvwsvwdvw
vhlvhsvhdvh
最小/最大vminlvmixsvmindvmin
vmaxlvmaxsvmaxdvmax
内联/块vilvisvidvi
vblvbsvbdvb

除了上面提过的报头及其他类似的全屏或半屏元素之外,应该并没有充分的理由断言某种视口单位一定优于另一种视口单位。但由于可供选择的视口相对单元如此之多,可能还会让人有些无从下手。这种情况下,我会选择小视口单位。

2.4.2 使用视口单位定义字号

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

试想,给一个元素加上 font-size: 2svw 会发生什么?在一个 1200px 的台式机显示器上,字号的计算值为 24px(即 1200 的 2%);而在一个 768px 的平板设备上,计算值则为 15px(即 768 的 2%)。这么做的好处在于,该元素在两种尺寸之间可以平滑地过渡。这意味着字号不会在某个断点发生突变,而是随着视口尺寸的变化而逐渐过渡。

唯一美中不足的是,24px 的字号对台式屏幕太大了;更有甚者换到 iPhone SE 上,字号又会一直缩到仅有 7.5px 左右。如果能让字号保留这种缩放的能力,同时又能避免走极端就好了。为此,CSS 的 calc() 或 clamp() 函数可以助您一臂之力。

1 利用 CALC() 函数实现响应式
calc() 函数可以对两个及以上的值进行基本四则运算。当涉及不同单位的值时,calc() 特别实用。其支持的运算包括:加(+)、减(-)、乘(*)、除(/)。其中加号和减号两边必须留有空白,因此建议大家养成在每个操作符前后都加上一个空格的习惯,比如 calc(1em + 10px)。

以下代码利用 calc() 函数将 em 与 svw 单位相结合。从样式表中删除之前的基本字号,以及相关的媒体查询。换成以下样式代码:

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

现在打开页面,慢慢缩放浏览器,字体也会平滑地缩放。 0.5em 保证了最小字号,而 1svw 则引入了一个响应式的标量。这样,基础字号就可以从 iPhone SE 上的 11.75px 一直过渡到 1200px 的浏览器窗口中的 20px。

警告
在使用视口单位来计算字号时,一定要确保算式中包含 em 或 rem 单位值,以提高页面的可访问性。这样一来,最终渲染出的字号就能兼顾用户自定义的字体设置。

您可以根据自己的喜好调整这些值,但要找到一个在小视口中不会太小、并在大视口中不会太大的理想值,可能还是有点困难。

2 利用 CLAMP() 函数加以完善
上面介绍的这种响应式字号很有用,但一个更新的 CSS 函数 clamp() 可以提供更精细的控制。clamp() 接受三个参数:最小值、以表达式形式给出的首选值、以及最大值。再次按以下样式更新页面:

:root {
  font-size: clamp(0.9rem, 0.6rem + 0.5svw, 1.5rem);
}

此时指定的字号为 0.6rem + 0.5svw ,但 clamp() 函数确保了最终结果不会小于 0.9rem,也不会大于 1.5rem。这样,即便遇到非常大或非常小的视口,也不会出现字号越界的情况。

既然字号是响应式的,那么页面上使用 em 或 rem 定义的其他尺寸也可以响应式地同步缩放。这样,无需使用任何媒体查询,就完成了响应式策略的绝大部分工作。页面上的所有内容都将根据视口进行流畅缩放,而无需设置三四个硬编码的断点。响应式设计的内容远不止这些,后续章节还会进行更深入的探讨,但掌握这些知识必将为后续的设计工作开一个好头。

提示
min() 和 max() 这两个相关函数可能时常也会用到。min() 函数表示给定值中的最小值(如 width: min(200px, 20svw););max() 则表示给定值中的最大值(如 min-height: max(200px, 20svw);)。

2.5 无单位的数值与行高

有些属性允许使用无单位的数值(unitless values,即没有指定单位的数字)。支持无单位值的属性(properties)包括 line-height、z-index 和 font-weight(700 相当于 bold 粗体字;400 相当于 normal 常规大小等等);此外,还可以在任何设置长度单位(如 px、em 或 rem)的地方使用无单位数值 0,因为在这些情况下单位并不重要——0px、0%、0em 均等效。

警告

无单位的 0 只能用于长度值和百分比,例如内边距(paddings)、边框(borders)和宽度(widths);0 不可用于角度值,例如度,或者与时间相关的值,例如秒。

line-height 属性比较特殊,其属性值既可以是带单位的值,也可以不带单位。通常应该使用无单位的值,因为二者的继承方式有所不同。让我们在页面中输入文字,看看不带单位的行高是什么效果。按如下代码更新页面:

代码清单 2.15 继承 line-height 的 HTML

<body>
  <p class="about-us">
    We have built partnerships with small farms around the world to
    hand-select beans at the peak of season. We then carefully roast in
    small batches to maximize their potential.
  </p>
</body>

接下来给 body 正文元素指定行高,并让页面其他元素继承该行高。可以看到,无论怎么调整页面字号,行高都会正常显示(如图 2.12 所示):

图 2.12 图 为每个后代元素重新计算无单位的行高,往往会产生间距适中的文本行

按代码清单 2.16 更新样式表。该段落的行高为继承过来的 1.2。由于字号为 32px(2em × 16px,浏览器默认值),因此本地计算的行高为 38.4px(32px × 1.2)。这将在行与行之间留出适当的空间。

代码清单 2.16 无单位的数值定义的行高

body {
  line-height: 1.2;  /* 后代元素继承了无单位的值 */
}
.about-us {
  font-size: 2em;
}

如果用带单位的值来设置行高,则可能产生意想不到的结果,如图 2.13 所示,每行文字会相互重叠。代码清单 2.17 为对应的 CSS 样式。

图 2.13 使用带单位的行高在继承给子元素时间距可能达不到预期

代码清单 2.17 用带单位的值定义行高,产生了意想不到的结果

body {
  line-height: 1.2em; /* 后代元素继承了计算值(19.2px) */
}
.about-us {
  font-size: 2em; /* 计算值为 32px */
}

这些效果是由于继承的一个怪异特性所造成的:当一个元素的值是用长度(px、em、rem 等)定义时,其 计算值 会被子元素继承。当使用 em 等相对单位设置行高时,它们的具体大小会被先计算出来,然后再将该计算值继承给子元素。对于行高 line-height 属性,如果子元素的字号与该计算值对应的字号不一致,就会导致像文字重叠这样意想不到的结果。

而当使用的是无单位数值时,被继承的是该声明值,也就是说其计算值将在每个继承的子元素中重新计算。这样得出的结果几乎总是我们所希望的。我们可以用一个无单位的数值给 body 设置行高,之后就不用修改了,除非个别地方需要其他不一样的行高。

2.6 自定义属性(即 CSS 变量)

自定义属性(Custom properties) 可以实现更高水准的基于上下文的动态样式设计。自定义属性的功能在很多方面都与变量(variables)相似;CSS 可以声明一个变量并赋值,然后在整个样式表中引用该值。这样不仅能减少样式表中的重复,而且还有其他好处,稍后会举例说明。

注意
如果用了 CSS 预处理器(如 Sass 或 Less)自带的变量,那么您可能会忽略 CSS 变量。千万别这么干。由于 CSS 变量有着本质上的区别,它比任何一款预处理器具备的变量功能都要强大得多。因此我更倾向于称其为“自定义属性”,而不是变量,以强调它们和预处理器变量的不同。

定义一个自定义属性,需要像其它 CSS 属性一样进行声明,如下方代码所示。新建一个示例页和样式表,并添加如下 CSS:

:root {
  --main-font: Helvetica, Arial, sans-serif;
}

该代码定义了一个名为 --main-font 的变量,并将变量的值设为一组常见的无衬线字体(sans-serif)。变量名须以两个连字符(--)开头,以便与其他 CSS 属性作区分;然后再加上要声明的任何合法名称。

CSS 变量必须在声明块中声明。这里用到了 :root 选择器,表示为整个页面设置了该变量,原因稍后解释。

该变量声明只有在被调用时才会在页面看到效果。将它应用到某个段落,则效果如图 2.14 所示:

图 2.14 使用变量设置无衬线字体的普通段落

调用函数 var() 就能使用该变量。使用该函数引用刚才定义的 --main-font 变量。按如下代码更新样式,将变量用起来:

代码清单 2.18 使用自定义属性

:root {
  --main-font: Helvetica, Arial, sans-serif;
}
p { /* 将段落字体设置为 Helvetica, Arial, sans-serif */
  font-family: var(--main-font); 
}

自定义属性可以将值定义到某处作为“单一数据源”,然后在整个样式表中复用该值。这种方式特别适合反复出现在页面上的值,比如颜色值。代码清单 2.19 添加了一个名为 brand-color 的自定义属性,之后整个样式表都能多次调用该变量;后续即便要改,也只需改动这一个地方即可。

代码清单 2.19 使用自定义颜色属性

:root {
  --main-font: Helvetica, Arial, sans-serif;
  --brand-color: #369; /* 定义一个蓝色的 brand-color 变量 */
}
p {
  font-family: var(--main-font);
  color: var(--brand-color);
}

var() 函数还能接受一个非必填的第二参数作备用值。如果第一参数设置的变量未定义,则启用第二个备用值。

代码清单 2.20 提供备用值

:root {
  --main-font: Helvetica, Arial, sans-serif;
  --brand-color: #369;
}
p {
  /* 指定备用值为 sans-serif */
  font-family: var(--main-font, sans-serif);
  /* 变量 secondary-color 未定义,因此启用备用值 blue */
  color: var(--secondary-color, blue);    
}

上述样式代码在两个不同的声明中指定了备用值。在第一个声明中,--main-font 被定义为 Helvetica, Arial, sans-serif,因此使用该值;而第二个声明中,变量 --secondary-color 未定义,因此启用备用值 blue。

注意

如果 var() 函数算出的结果为无效值(invalid value),该属性将被设置为初始值(initial value)。例如,如果 padding: var(--brand-color) 中的变量值为一个颜色值,对于内边距 padding 而言就是一个无效值。此时 padding 的值将被设置为 0。

2 .6.1 动态变更自定义属性

在前面的示例中,自定义属性仅仅是为减少大量冗余代码提供了一种便捷方式;而它真正的意义在于,自定义属性的声明也适用 CSS 层叠与继承规则:只要在多个选择器中定义相同的变量,就能让该变量在页面不同位置拥有不同的取值。

例如,将一个变量定义为黑色,然后在特定容器内将其重新定义为白色。这样依赖,任何基于该变量的样式在容器外都将被动态解析为黑色,而容器内则为白色,如图 2.15 所示。

图 2.15 自定义属性会根据本地变量值生成不同颜色的面板

该示例与之前创建的面板效果类似,只是多了一个深色版本。其 HTML 结构如代码清单 2.21 所示。它有两个面板实例:一个在 body 元素内,另一个在深色的 section 元素内。按如下代码更新示例页。

代码清单 2.21 同一页面不同环境下的两个面板

<body>
  <div class="panel"> <!-- 网页中的一个普通面板 -->
    <h2>Single-origin</h2>
    <div class="body">
      We have built partnerships with small farms
      around the world to hand-select beans at the
      peak of season. We then careful roast in
      small batches to maximize their potential.
    </div>
  </div>
 
  <aside class="dark">
    <div class="panel"> <!-- 深色容器内的另一个面板 -->
      <h2>Single-origin</h2>
      <div class="body">
        We have built partnerships with small farms
        around the world to hand-select beans at the
        peak of season. We then careful roast in
        small batches to maximize their potential.
      </div>
    </div>
  </aside>
</body>

接下来,通过变量来设置文字颜色与背景色,从而重新定义面板样式。在样式表中添加如下代码,将背景色设置为白色,文字设置为黑色。具体工作原理待面板变为深色版本后再作解释。

代码清单 2.22 使用变量定义面板颜色

:root { /* 分别将背景色和文字颜色变量定义为白色和黑色 */
  --main-bg: #fff;
  --main-color: #000;
}
.panel {
  font-size: 1rem;
  padding: 1em;
  border: 1px solid #999;
  border-radius: 0.5em;
  /* 在面板样式中使用变量 */
  background-color: var(--main-bg);
  color: var(--main-color);
}
.panel > h2 {
  margin-top: 0;
  font-size: 0.8em;
  font-weight: bold;
  text-transform: uppercase;
}

这一次同样使用 :root 选择器定义变量——这一点很重要——它意味着这些变量值是为根元素(即整个页面)中的所有内容设置的。当根元素的后代元素调用它们时,浏览器也会解析到这些值。

两个面板准备就绪,尽管看上去效果是一样的。接下来再次定义变量,但选择器和刚才不一样了。以下代码为深色容器设置了深灰色背景,以及少量的内外边距(padding 和 margin)。此外还重新定义了两个变量。将这些代码更新到样式表中:

代码清单 2.23 深色容器的样式

.dark {
  margin-top: 2em; /* 给深色容器与前面的面板之间加上外边距 */
  padding: 1em;
  background-color: #999; /* 给深色容器加上深灰色背景 */
  /* 在容器内重新定义变量的取值: */
  --main-bg: #333;
  --main-color: #fff;
}

重新加载页面后,第二个面板变为深色背景、白色文字。这是因为当面板使用这些自定义属性时,浏览器会解析在深色容器上定义的变量值,而非根元素上的值。注意,这里无需重新设计面板样式,也不用借助任何其他的样式类。

本例定义了两次自定义属性:先是根元素(此时 --main-color 为黑色),然后是深色容器(此时 --main-color 为白色)。自定义属性是一种带作用域的变量,其值会被后代元素继承。在深色容器中,--main-color 解析为白色;而在页面其他位置,则解析为黑色。

自定义属性是一种用途极为广泛的工具,其用途不胜枚举。本书后续章节还将继续介绍自定义属性的各种应用。

(第一版中曾介绍的第二小节内容:使用 JavaScript 改变自定义属性,在新版中被舍弃了)

2.7 本章小结

相对单位可用于指定与字号或视口大小相适应的尺寸。
em 单位制基于该元素字号来定义长度;若用于指定字号本身,则基于该元素继承的字号来定义长度。
rem 单位制基于 <html> 根元素上设置的字号来定义长度。
在响应式设计中缩放根元素字号,则页面上用 em 和 rem 定义的元素也会同步缩放。
视口相对单位基于视口的宽度或高度来定义长度。
用无单位数值定义的行高,在继承到子元素时其可预测性更强。
自定义属性的工作原理虽然与变量类似,但可以通过层叠和继承规则实现动态调整。