Skip to content

相对单位

::: theorem 本章概要
❑ 相对单位的广泛用途
❑ 使用 em 和 rem
❑ 使用视口的相对单位
❑ 介绍 CSS 变量 :::

相对单位可以为我们所用,用得恰当的话,它们会让代码更简洁、更灵活,也更简单。

相对值的好处

CSS 支持几种绝对长度单位,最常用、最基础的是像素(px)。

  • 1in = 25.4mm = 2.54cm = 6pc = 72pt = 96px
  • 16px = 12pt(16/96×72)

响应式——在 CSS 中指的是样式能够根据浏览器窗口的大小有不同的“响应”。这要求有意地考虑任何尺寸的手机、平板设备,或者桌面屏幕。

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

em 和 rem

em 是最常见的相对长度单位,适合基于特定的字号进行排版。在 CSS 中,1em 等于当前元素的字号。

当设置 paddingheightwidthborder-radius 等属性时,使用 em 会很方便。

示例代码:

HTML
    <span class="box box-small">Small</span>
    <span class="box box-large">Large</span>
CSS
.box {
  padding: 1em;
  border-radius: 1em;
  background-color: lightgray;
}

.box-small {
  font-size: 12px;
}

.box-large {
  font-size: 18px;
}

这段代码用 em 定义了一个盒子 .box,同时定义了一个 .box-small 和一个 .box-large修饰符,分别指定不同的字号。

这里涉及到两个知识点。

  1. 属性是否默认继承:盒子属性默认不继承,所以 HTML 代码中的 class="box box-small",并不等同于 CSS 样式中的 .box .box-small 选择器,也不存在从 .box.box-small 的前后继承。HTML 中的 class="box box-small" 这种写法,是将 CSS 样式中分别定义的 .box 样式和 .box-small 样式,合并、层叠之后,共同作用在元素 span 身上。

  2. 修饰符:HTML 中的 class="box box-small" 这种前后两个类叠加的写法,其最佳实践涉及到 CSS 之 BEM 命名规范。BEM 是 Block(块)、Element(元素)、Modifier(修饰符)的简写,是一种组件化的 CSS 命名方法和规范,由俄罗斯 Yandex 团队所提出。使用 BEM 主要是为了将用户界面划分成独立的块,使开发更为简单和快速,有利于团队协作,方便维护。

使用 em 定义字号

如果声明 font-size: 1.2em,这个 font-size 是根据继承的字号来计算的。

  1. em 同时用于字号和其他属性
    现在已经用 em 定义了字号(基于继承的字号),而且也用 em 定义了其他属性,比如 paddingborder-radius(基于当前元素的字号)。em 的复杂之处在于同时用它指定一个元素的字号和其他属性。这时,浏览器必须先计算字号,然后使用这个计算值去算出其余的属性值。这两类属性可以拥有一样的声明值,但是计算值却不一样。
  2. 字体缩小的问题
    当列表多级嵌套并且给每一级使用 em 定义字号时,设置无序列表的字号为 0.8em,就会发生文字逐级缩小的现象。

em 用在内边距、外边距以及元素大小上很好,但是用在字号上就会很复杂。值得庆幸的是,我们有更好的选择:rem。

使用 rem 设置字号

拿不准的时候,用 rem 设置字号,用 px 设置边框,用 em 设置其他大部分属性。

停止像素思维

在响应式网页中,需要习惯“模糊”值。1.2em 到底是多少像素并不重要,重点是它比继承的字号要稍微大一点。如果在屏幕上的效果不理想,就调整它的值,反复试验。

本章后面的内容主要使用相对单位讨论字号。

设置一个合理的默认字号

浏览器的默认字号是 16px

有两种方法重置根元素的字号:

  • 间接方法:先将根元素字号重置成 10px,其他元素的字号再按照精确的像素设定
  • 直接方法:将根元素字号设置为想要的值,比如 14px,其他元素的字号使用相对单位设定,只需要相对大小,不需要准确的像素

建议直接重置。间接重置涉及的覆盖代码太多,不利后期维护。

CSS
:root {
  font-size: 0.875em;
}

接下来,基于根元素重置的 14px 字号,用相对单位来构建一个面板。这个面板,希望在不同尺寸的屏幕下有不同的表现样式。

使用相对单位创建的面板:

CSS
.panel {
  padding: 1em;
  border-radius: .5em;
  border: 1px solid #999;
}

.panel > h2 {
  margin-top: 0;
  font-size: 0.8rem;
  font-weight: bold;
  text-transform: uppercase;
}

构造响应式面板

根据屏幕尺寸,用媒体查询改变根元素的字号:

CSS
:root {
  font-size: 0.75em;
}

@media (min-width: 800px) {
  :root {
    font-size: 0.875em;
  }
}

@media (min-width: 1200px) {
  :root {
    font-size: 1em;
  }
}
  • 在小屏上,比如智能手机上,组件字号是 12px,内边距和圆角也相应较小
  • 在大于 800px 的大屏上,组件会相应地放大到 14px 的字号
  • 在大于 800px 和 1200px 的大屏上,组件会相应地放大到 16px 的字号

好处:整个网页的样式都像这样使用相对单位定义,那么网页就会根据视口大小整体缩放。如果网页的字体太大或者太小,只需要改一行代码就能改变整体的字号。这就是响应式的设计策略。

缩放单个组件

有时,需要让同一个组件在页面的某些部分显示不同的大小,你可以用 em 来单独缩放一个组件。拿之前的面板举例。首先给面板加上一个 large 类:<div class="panel large">

首先,给每个面板添加父元素声明 font-size: 1rem,这样无论面板位于页面何处,都有一个可预测的字号。

CSS
.panel {
  font-size: 1rem;  /* 给组件设置一个可预测的字号  */
  padding: 1em;
  border-radius: .5em;
  border: 1px solid #999;
}

其次,使用 em 而不是用 rem,重新定义子元素标题的字号,使其相对于刚刚在 1rem 时创建的父元素的字号。

CSS
.panel > h2 {
  margin-top: 0;
  font-size: 0.8rem;  /* 用 em 定义其他字号,使其相对于父元素的字号  */
  font-weight: bold;
  text-transform: uppercase;
}

最后,使用 复合选择器 选中同时拥有 .panel.large 类的元素,覆盖重写字号。

CSS
.panel.large {
  font-size: 1.2rem;
}

这样,就完成了一个更大面板的定义。因为组件内所有的大小都是相对于父元素的字号,所以 font-size: 1.2rem 覆盖后 font-size: 1rem,整个面板的大小都会改变,包括字号、边框和边距。

问题:最后一步使用 复合选择器 的做法,为什么不直接使用单独的 .large 类呢?更符合 BEM 规范,代码经测试同样生效。

CSS
.large {
  font-size: 1.2rem;
}

在本例中,因为样式代码相对简单,使用 复合选择器 或单独的 .large 类,并没什么不同。但是在样式更为复杂的实际项目中,代码的结构就需要更仔细地搭建。

视口的相对单位

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

❑ vh:视口高度的 1/100
❑ vw:视口宽度的 1/100
❑ vmin:视口宽、高中较小的一方的 1/100
❑ vmax:视口宽、高中较大的一方的 1/100

当一个元素的宽和高为 90vmin 时,不管视口的大小或者方向是什么,总会显示成一个稍小于视口的正方形。

可以在网页里加上 <div class="square"> 来看效果。

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

使用 vw 定义字号

如果给一个元素加上 font-size: 2vw 会发生什么?

  • 在一个 1200px 的桌面显示器上,计算值为 24px(1200 的 2%)
  • 在一个 768px 宽的平板上,计算值约为 15px(768 的 2%)
  • 在一个 375px 宽的 iPhone 6 上会缩小到只有 7.5px

不幸的是,24px 在大屏上来说太大了,7.5px 在 iPhone 6 上太小了。CSS 的 calc() 函数可以提供帮助。

使用 calc() 定义字号

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

0.5em 保证了最小字号,1vw 则确保了字体会随着视口缩放。这段代码保证基础字号从 iPhone 6 里的 11.75px 一直过渡到 1200px 的浏览器窗口里的 20px。

  • 0.5 x 16px + 0.01 x 375px = 11.75px
  • 0.5 x 16px + 0.01 x 768px = 15.68px
  • 0.5 x 16px + 0.01 x 1200px = 20px

我们不用媒体查询就实现了大部分的响应式策略。省掉三四个硬编码的断点,网页上的内容也能根据视口流畅地缩放。可以按照自己的喜好调整这个值。

无单位的数值和行高

line-height 属性比较特殊,它的值既可以有单位也可以无单位。通常我们应该使用无单位的数值,因为它们继承的方式不一样。

下面使用相同的一段 HTML 代码、对比 CSS 中使用行高的两种用法。

HTML
<body>
  <p class="about-us">Lorem ipsum dolor sit amet consectetur adipisicing elit. Sit inventore, et accusantium adipisci optio dolores, at vero quas voluptates consequatur saepe nemo fugiat ducimus eos neque excepturi, blanditiis iste doloremque!</p>
</body>

无单位的行高

用无单位的数值定义的行高,因未使用单位,body 的行高无法得出明确的 计算值,后代只能继承 声明值

CSS
body {
  line-height: 1.2;  /* 后代元素继承了声明值 */
}

.about-us {
  font-size: 2em;
}

首先,给 body 元素指定一个行高,它被将那些未显式指定行高的后代元素继承。
第二,p 元素的字号计算为 2 x 16px = 32px。
第三,p 元素中未显示指定 line-height,默认继承 声明值 line-height: 1.2,计算值为 1.2 x 32px = 38.4px。

当无单位指定行高并被后代默认继承时,38.4px > 32px,后代元素每行文字之间都会有一个合理的间距。

有单位的行高

使用有单位的值定义行高,将产生文字重叠:

CSS
body {
  line-height: 1.2em;  /* 后代元素继承了计算值 19.2px */
}

.about-us {
  font-size: 2em;  /* 计算值 32px */
}

首先,给 body 元素指定一个行高,因为使用了单位,body 的行高可以明确得出 计算值 2 x 16px = 19.2px。该 计算值 被将那些未显式指定行高的后代元素继承。
第二,p 元素的字号计算为 2 x 16px = 32px。
第三,p 元素中未显示指定 line-height,默认继承祖先元素的 计算值 19.2px。

当使用单位指定行高并被后代默认继承时,19.2px < 32px,后代元素的多行文字就会产生重叠。

对比小结:

当一个元素的值定义为长度(px、em、rem,等等)时,子元素会继承它的 计算值。当使用 em 等单位定义行高时,它们的值是 计算值,传递到了任何继承子元素上。如果子元素有不同的字号,并且继承了 line-height 属性,就会造成意想不到的结果,比如文字重叠。子元素有了更大的字号,而继承的行高却保持恒定,这是文字重叠的发生前提。

使用无单位的数值时,继承的是 声明值,即在每个继承子元素上会重新算它的 计算值。这通常是我们想要的结果。

自定义属性(即 CSS 变量)

变量名前面必须有两个连字符(--),用来跟 CSS 属性区分,剩下的部分可以随意命名。

在样式表某处为自定义属性定义一个值,作为“单一数据源”,然后在其他地方复用它。这种方式特别适合反复出现的值,比如颜色值。

var() 函数接受第二个参数,它指定了备用值。如果第一个参数指定的变量未定义,那么就会使用第二个值。

如果 var() 函数算出来的是一个非法值,对应的属性就会设置为其初始值。比如,如果在 padding: var(--brand-color) 中的变量算出来是一个颜色,它就是一个非法的内边距值。这种情况下,内边距会设置为 0。

动态改变自定义属性

  • 自定义属性为减少重复代码提供了一种便捷方式
  • 自定义属性的声明能够层叠和继承:可以在多个选择器中定义相同的变量,这个变量在网页的不同地方有不同的值

:root 选择器的规则集中定义变量。这很重要,如此一来这些值就可以提供给根元素(整个网页)下的任何元素。当根元素的后代元素使用这个变量时,就会解析这里的值。

在本例中,总共定义了自定义属性两次:第一次在根元素上(--main-color 为黑色),第二次在深色容器上(--main-color 为白色)。自定义属性就像作用域变量一样,因为它的值会被后代元素继承。在深色容器中,--main-color 为白色,在页面其他地方,则是黑色。

使用 JavaScript 改变自定义属性

还可以使用 JavaScript 在浏览器中实时访问和修改自定义属性。

利用这种技术,就可以用 JavaScript 实时切换网站主题,或者在网页中突出显示某些元素,或者实时改变任意多个元素。只需要几行 JavaScript 代码,就可以进行更改,从而影响网页上的大量元素。

总结

❑ 拥抱相对单位,让网页的结构决定样式的含义
❑ 建议用 rem 设置字号,但是有选择地用 em 实现网页组件的简单缩放
❑ 不用媒体查询也能让整个网页响应式缩放
❑ 使用无单位的值设置行高
❑ 请开始熟悉 CSS 的一个新特性:自定义属性