相对单位
::: 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 等于当前元素的字号。
当设置 padding
、height
、width
、border-radius
等属性时,使用 em 会很方便。
示例代码:
<span class="box box-small">Small</span>
<span class="box box-large">Large</span>
.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
的 修饰符,分别指定不同的字号。
这里涉及到两个知识点。
属性是否默认继承:盒子属性默认不继承,所以 HTML 代码中的
class="box box-small"
,并不等同于 CSS 样式中的.box .box-small
选择器,也不存在从.box
到.box-small
的前后继承。HTML 中的class="box box-small"
这种写法,是将 CSS 样式中分别定义的.box
样式和.box-small
样式,合并、层叠之后,共同作用在元素span
身上。修饰符:HTML 中的
class="box box-small"
这种前后两个类叠加的写法,其最佳实践涉及到 CSS 之 BEM 命名规范。BEM 是 Block(块)、Element(元素)、Modifier(修饰符)的简写,是一种组件化的 CSS 命名方法和规范,由俄罗斯 Yandex 团队所提出。使用 BEM 主要是为了将用户界面划分成独立的块,使开发更为简单和快速,有利于团队协作,方便维护。
使用 em 定义字号
如果声明 font-size: 1.2em
,这个 font-size
是根据继承的字号来计算的。
- em 同时用于字号和其他属性
现在已经用 em 定义了字号(基于继承的字号),而且也用 em 定义了其他属性,比如padding
和border-radius
(基于当前元素的字号)。em 的复杂之处在于同时用它指定一个元素的字号和其他属性。这时,浏览器必须先计算字号,然后使用这个计算值去算出其余的属性值。这两类属性可以拥有一样的声明值,但是计算值却不一样。 - 字体缩小的问题
当列表多级嵌套并且给每一级使用 em 定义字号时,设置无序列表的字号为 0.8em,就会发生文字逐级缩小的现象。
em 用在内边距、外边距以及元素大小上很好,但是用在字号上就会很复杂。值得庆幸的是,我们有更好的选择:rem。
使用 rem 设置字号
拿不准的时候,用 rem 设置字号,用 px 设置边框,用 em 设置其他大部分属性。
停止像素思维
在响应式网页中,需要习惯“模糊”值。1.2em 到底是多少像素并不重要,重点是它比继承的字号要稍微大一点。如果在屏幕上的效果不理想,就调整它的值,反复试验。
本章后面的内容主要使用相对单位讨论字号。
设置一个合理的默认字号
浏览器的默认字号是 16px
有两种方法重置根元素的字号:
- 间接方法:先将根元素字号重置成 10px,其他元素的字号再按照精确的像素设定
- 直接方法:将根元素字号设置为想要的值,比如 14px,其他元素的字号使用相对单位设定,只需要相对大小,不需要准确的像素
建议直接重置。间接重置涉及的覆盖代码太多,不利后期维护。
:root {
font-size: 0.875em;
}
接下来,基于根元素重置的 14px 字号,用相对单位来构建一个面板。这个面板,希望在不同尺寸的屏幕下有不同的表现样式。
使用相对单位创建的面板:
.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;
}
构造响应式面板
根据屏幕尺寸,用媒体查询改变根元素的字号:
: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
,这样无论面板位于页面何处,都有一个可预测的字号。
.panel {
font-size: 1rem; /* 给组件设置一个可预测的字号 */
padding: 1em;
border-radius: .5em;
border: 1px solid #999;
}
其次,使用 em 而不是用 rem,重新定义子元素标题的字号,使其相对于刚刚在 1rem 时创建的父元素的字号。
.panel > h2 {
margin-top: 0;
font-size: 0.8rem; /* 用 em 定义其他字号,使其相对于父元素的字号 */
font-weight: bold;
text-transform: uppercase;
}
最后,使用 复合选择器 选中同时拥有 .panel
和 .large
类的元素,覆盖重写字号。
.panel.large {
font-size: 1.2rem;
}
这样,就完成了一个更大面板的定义。因为组件内所有的大小都是相对于父元素的字号,所以 font-size: 1.2rem
覆盖后 font-size: 1rem
,整个面板的大小都会改变,包括字号、边框和边距。
问题:最后一步使用 复合选择器 的做法,为什么不直接使用单独的 .large
类呢?更符合 BEM 规范,代码经测试同样生效。
.large {
font-size: 1.2rem;
}
在本例中,因为样式代码相对简单,使用 复合选择器 或单独的 .large
类,并没什么不同。但是在样式更为复杂的实际项目中,代码的结构就需要更仔细地搭建。
视口的相对单位
视口——浏览器窗口里网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏、状态栏。
❑ vh:视口高度的 1/100
❑ vw:视口宽度的 1/100
❑ vmin:视口宽、高中较小的一方的 1/100
❑ vmax:视口宽、高中较大的一方的 1/100
当一个元素的宽和高为 90vmin 时,不管视口的大小或者方向是什么,总会显示成一个稍小于视口的正方形。
可以在网页里加上 <div class="square">
来看效果。
.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()
定义字号
: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 中使用行高的两种用法。
<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 的行高无法得出明确的 计算值,后代只能继承 声明值:
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,后代元素每行文字之间都会有一个合理的间距。
有单位的行高
使用有单位的值定义行高,将产生文字重叠:
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 的一个新特性:自定义属性