相对单位
说起给属性指定值,CSS提供了很多选项。人们最熟悉同时也最简单的应该是像素单位(px)。它是绝对单位,即 5px 放在哪里都一样大。
而其他单位,如 em 和 rem,就不是绝对单位,而是相对单位。相对单位的值会根据外部因素发生变化。
比如,2em 的具体值会根据它作用到的元素(有时甚至是根据属性)而变化。因此相对单位的用法更难掌握。
开发人员,即便是经验丰富的 CSS 开发人员,通常也不愿意使用相对单位,包括经常提到的 em。
em 值变化的方式使其难以预测,不如像素简单明了。
相对单位可以为我们所用,用得恰当的话,它们会让代码更简洁、更灵活,也更简单。
相对值的好处
CSS为网页带来了后期绑定(late-binding)的样式:直到内容和样式都完成了,二者才会结合起来。
这会给设计流程增加复杂性,而这在其他类型的图形设计中是不存在的。不过这也带来了好处,即一个样式表可以作用于成百上千个网页。
此外,用户还能直接改变最终的渲染效果,比如用户可以改变默认字号或者缩放浏览器窗口。
在早期的计算机应用开发程序(以及传统的出版行业)中,开发人员(或者出版商)明确知道其媒介的限制。
一个典型的程序窗口可能宽 400px、高 300px,一个页面可能是宽 4 英寸(1 英寸约合 2.54 厘米)、高 6.5 英寸。
因此,当开发人员设置应用程序的按钮和文字布局时,他们能精确地知道元素在屏幕上的大小和留给其他元素的空间。在网页上,一切都变了。
那些年追求的像素级完美
在Web环境下,用户可以设置浏览器窗口的大小,而CSS必须适应这种窗口大小。此外,当网页打开后,用户还可以缩放网页,CSS还需要适应新的限制。
也就是说,不能在刚创建网页时就应用样式,而是等到要将网页渲染到屏幕上时,才能去计算样式。
这给 CSS 增加了一个抽象层。我们无法根据理想的条件给元素添加样式,而是要设置无论元素处于任意条件,都能够生效的规则。
现在的 Web 环境下,网页需要既可以在 4 英寸的手机屏幕上渲染,也可以在 30 英寸的大屏幕上渲染。
在很长时间里,网页设计者通过聚焦到“像素级完美”的设计来降低这种复杂性。
他们会创建一个紧凑的容器,通常是居中的一栏,大约 800px 宽。然后再像之前的本地应用程序或者印刷出版物那样,在这些限制里面进行设计。
像素级完美的时代终结了
随着技术的发展,加上制造商推出高清显示器,像素级完美的方式逐渐走向了终点。
在21世纪初,很多人开始讨论是否可以安全地将网页宽度设计成 1024px,而不是 800px。
随后,人们又开始讨论同样的话题,是否要将网页宽度设计成 1280px。当时我们得做出选择,到底是让网页宽于旧计算机,还是窄于新计算机。
等到智能手机出现后,开发人员再也无法假装每个用户访问网站的体验都能一样。
不管我们喜欢与否,都得抛弃以前那种固定宽度的栏目设计,开始考虑响应式设计。我们无法逃避CSS带来的抽象性。我们得拥抱它。
响应式——在 CSS 中指的是样式能够根据浏览器窗口的大小有不同的“响应”。
这要求有意地考虑任何尺寸的手机、平板设备,或者桌面屏幕。
CSS带来的抽象性也带来了额外的复杂性。如果给一个元素设置800px的宽度,在小窗口下会是什么样?
水平菜单如果无法在一行显示会是什么样?在写CSS的时候,我们既要考虑整体性,也要考虑差异性。
当有很多方法解决同一个问题时,我们要选择能够兼顾更多情况的方法。
相对单位就是CSS用来解决这种抽象的一种工具。
我们可以基于窗口大小来等比例地缩放字号,而不是固定为14px,或者将网页上的任何元素的大小都相对于基础字号来设置,
然后只用改一行代码就能缩放整个网页。
下面来看看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通常等于一个物理英寸的大小。
em 和 rem
em是最常见的相对长度单位,适合基于特定的字号进行排版。
在 CSS 中,1em 等于当前元素的字号,其准确值取决于作用的元素。下图是一个内边距为 1em 的 div 元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
规则集指定了字号为16px,也就是元素局部定义的1em。然后使用em指定了元素的内边距。
这里设置内边距的值为1em。浏览器将其乘以字号,最终渲染为16px。
这一点很重要:浏览器会根据相对单位的值计算出绝对值,称作计算值(computed value)。
在本例中,设置内边距为2em,会产生一个32px的计算值。
如果另一个选择器也命中了相同的元素,并修改了字号,那么就会改变 em 的局部含义,计算出来的内边距也会随之变化。
当设置 padding、height、width、border-radius 等属性时,使用 em 会很方便。
这是因为当元素继承了不同的字号,或者用户改变了字体设置时,这些属性会跟着元素均匀地缩放。
下图展示了两个不同大小的盒子,它们的字号、内边距和圆角都会不一样。
在定义这些盒子的样式时,可以用em指定内边距和圆角。
给每个元素设置1em的内边距和圆角,再分别指定不同的字号,那么这些属性会随着字体一起缩放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
这段代码用em定义了一个盒子,同时定义了一个small和一个large的修饰符,分别指定不同的字号。
这就是em的好处。可以定义一个元素的大小,然后只需要改变字号就能整体缩放元素。
稍后会再举一个例子,在此之前,我们先说说em和字号。
使用 em 定义字号
谈到font-size属性时,em表现得不太一样。
之前提到过,当前元素的字号决定了em。但是,如果声明 font-size: 1.2em
,会发生什么呢?
一个字号当然不能等于自己的1.2倍。实际上,这个 font-size
是根据继承的字号来计算的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
第一行文字在<body>
标签中,因此它会按照body的字号来渲染。第二段的slogan继承了这个字号。
简单起见,这里用像素单位。接下来使用em来放大slogan的字号。
slogan的指定字号是 1.2em。为了得到计算的像素值,需要参考继承的字号,即 16px。因为 16×1.2 = 19.2
,所以计算值为 19.2 px。
提示
如果知道字号的像素值,但是想用em声明,可以用一个简单的公式换算:用想要的像素大小除以父级(继承)的像素字号。
比如,想要一个 10px 的字体,元素继承的字体是 12px,则计算结果是 10/12 = 0.8333em
。
如果想要一个 16px 的字体,父级字号为 12px,则计算结果是 16/12 = 1.3333em
。
了解这些非常有用。对大多数浏览器来说,默认的字号为 16px。准确地说,medium 关键字的值是 16px。
em 同时用于字号和其他属性
现在你已经用em定义了字号(基于继承的字号),而且也用 em 定义了其他属性,比如 padding 和 border-radius(基于当前元素的字号)。
em的复杂之处在于同时用它指定一个元素的字号和其他属性。这时,浏览器必须先计算字号,然后使用这个计算值去算出其余的属性值。
这两类属性可以拥有一样的声明值,但是计算值不一样。
在前面的例子里,字号的计算值为 19.2px(继承值 16px 乘以 1.2em)。
下图展示了相同的 slogan 元素,但是内边距为 1.2em,背景为灰色,这样能明显地看到内边距的大小。
内边距比字号稍微大一些,尽管它们的声明值相同。
这是因为该段落从 body 继承了16px的字号,最终字号的计算值为 19.2px
。因此 19.2px
是em的局部值,用于计算内边距
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
在这个例子里,padding 的声明值为 1.2em,乘以 19.2px(当前元素的字号),得到计算值为 23.04px。
尽管 font-size 和 padding 的声明值相同,计算值却不一样。
字体缩小的问题
当用 em 来指定多重嵌套的元素的字号时,就会产生意外的结果。
为了算出每个元素的准确值,就需要知道继承的字号,如果这个值是在父元素上用em定义的,就需要知道父元素的继承值,以此类推,就会沿着DOM树一直往上查找。
当使用em给列表元素定义字号并且多级嵌套时,这个问题就显现出来了。
绝大部分Web开发人员曾遇到过类似于下图的现象。文字缩小了!正是这种问题让开发人员惧怕使用 em。
当列表多级嵌套并且给每一级使用em定义字号时,就会发生文字缩小的现象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
设置无序列表的字号为 0.8em。选择器选中了网页上每个<ul>
元素,因此当这些列表从其他列表继承字号时,em就会逐渐缩小字号。
每个列表元素的字号等于 0.8
乘以其父元素的字号。
算出来第一级列表的字号为 12.8px
,第二级缩小到 10.24px(12.8px × 0.8)
,第三级缩小到 8.192px
,以此类推。
同理,如果指定一个大于1em的字号,文字会逐渐增大。
我们想要的是指定顶部的字号,然后保持子级的字号一致
1 2 3 4 5 6 7 8 |
|
第二个选择器选中了嵌套在某个无序列表中的所有无序列表,也就是除了顶级列表以外的其他列表。嵌套列表的字号等于其父级的字号
这样确实解决了问题,尽管这个方式不完美。
设置一个值,然后马上用另一个规则覆盖。如果不用提升选择器的优先级来覆盖规则,就更好了。
这些例子告诉我们,如果不小心的话,em就会变得难以驾驭。
em用在内边距、外边距以及元素大小上很好,但是用在字号上就会很复杂。值得庆幸的是,我们有更好的选择:rem。
使用 rem 设置字号
当浏览器解析HTML文档时,会在内存里将页面的所有元素表示为DOM(文档对象模型)。
它是一个树结构,其中每个元素都由一个节点表示。<html>
元素是顶级(根)节点。它下面是子节点,<head>
和 <body>
。
再下面是逐级嵌套的后代节点。
在文档中,根节点是所有其他元素的祖先节点。
根节点有一个伪类选择器(:root
),可以用来选中它自己。这等价于类型选择器html,但是html的优先级相当于一个类名,而不是一个标签。
rem 是 root em
的缩写。rem不是相对于当前元素,而是相对于根元素的单位。
不管在文档的什么位置使用 rem, 1.2rem 都会有相同的计算值:1.2 乘以根元素的字号
。
下面代码先指定了根元素的字号,然后用 rem 定义了无序列表的相对字号。
1 2 3 4 5 6 7 8 |
|
在这个例子里,根元素的字号为浏览器默认的字号 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设置其他大部分属性。
停止像素思维
过去几年有一个常见的模式,更准确地说是反模式,就是将网页根元素的字号设置为 0.625em
或者 62.5%
。
1 2 3 |
|
我不推荐这样写。将浏览器的默认字号 16px 缩小为 10px。
这的确能简化计算:如果设计师希望字号为 14px,那么只需要默默在心里除以 10,写上 1.4rem 就可以了,而且还使用了相对单位。
一开始,这会很方便,但是这样有两个缺点。
第一,我们被迫写很多重复的代码。
10px对大部分文字来说太小了,所以需要覆盖它,最后就得给段落设置1.4rem,给侧边栏设置1.4rem,给导航链接设置1.4rem,等等。
这样一来,代码容易出错的地方更多;当需要修改代码时,要改动的地方更多;样式表的体积也更大。
第二,这种做法的本质还是像素思维。
虽然在代码里写的是1.4rem,但是在心里仍然想着“14像素”。在响应式网页中,需要习惯“模糊”值。
1.2em到底是多少像素并不重要,重点是它比继承的字号要稍微大一点。如果在屏幕上的效果不理想,就调整它的值,反复试验。
这种方式同样适用于像素值。
使用em时,很容易陷入沉思:到底计算出来的像素值是多少,尤其是用em定义字号时。你会不停地做乘法和除法来计算em的值,直到抓狂。
相反,我建议先适应使用em。如果已经习惯了像素,那么使用em可能需要反复练习,但这一切是值得的。
这并不意味着永远不能用像素。如果是跟设计师沟通,可能就需要讨论具体的像素值,这没问题。
在项目之初,需要确定基本的字号(通常是标题和脚注的常用字号)。讨论大小的时候用绝对值更简单。
将大小转换成rem需要计算,记得随手带一个计算器。
给根元素设置了字号后,就定义了一个rem。在这之后,应该只在少数特殊情况下使用像素,而不能经常使用。
设置一个合理的默认字号
如果你希望默认字号为 14px,那么不要将默认字体设置为10px然后再覆盖一遍,而应该直接将根元素字号设置为想要的值。
将想要的值除以继承值(在这种情况下为浏览器默认值)是 14/16
,等于 0.875
。
1 2 3 |
|
现在你已经给网页设置了想要的字号,不用在其他地方再指定一遍了。你只需要相对它去修改其他元素(比如标题)的字号。
接下来创建一个如图所示的面板。基于根元素的14px字号,用相对单位来构建这个面板。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
这里用em设置内边距和圆角,用rem设置标题的字号,用px设置边框。
代码给面板的四周加上了细边,并给标题指定了样式。
我创建了一个较小的标题,同时将字体加粗,全大写。(如果设计需要的话,可以改成更大的字号,或者其他字体。)
第二个选择器里的 >
是一个直接后代组合器。它选中了 .panel
元素的一个 h2 子元素。
给面板主体添加 panel-body 类只是为了明确含义,在 CSS 中并未用到。
因为这个元素已经继承了根元素的字号,所以它看起来就是理想的样子,不需要覆盖。
构造响应式面板
更进一步地说,我们甚至可以根据屏幕尺寸,用媒体查询改变根元素的字号。
这样就能够基于不同用户的屏幕尺寸,渲染出不同大小的面板
不同屏幕尺寸下的响应式面板: 300px(左上)、800px(右上)、1440px(下):
媒体查询,即 @media
规则,可以指定某种屏幕尺寸或者媒体类型(比如,打印机或者屏幕)下的样式。
这是响应式设计的关键部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
第一个规则集指定了一个较小的默认字号,这是希望在小屏幕上显示的字号。
然后使用媒体查询覆盖该值,在800px、1200px以及更大的屏幕上逐渐增大字号。
通过给页面根元素设置不同字号,我们响应式地重新定义了整个网页的em和rem。
也就是说,即使不直接修改面板的样式,它也是响应式的。
在小屏上,比如智能手机上,字体会较小(12px),内边距和圆角也相应较小。
在大于800px和1200px的大屏上,组件会相应地分别放大到14px和16px的字号。缩放浏览器窗口可以看到这些变化
如果你足够严格,整个网页的样式都像这样使用相对单位定义,那么网页就会根据视口大小整体缩放。这是响应式策略中很重要的一部分。
靠近样式表顶部的两个媒体查询可以极大减少后续CSS代码中媒体查询的数量。如果用像素的话,就没有这么容易。
同样,如果老板或者客户觉得网页的字体太大或者太小,只需要改一行代码就能改变整体的字号,进而不费吹灰之力影响整个网页。
缩放单个组件
有时,需要让同一个组件在页面的某些部分显示不同的大小,你可以用em来单独缩放一个组件。
拿之前的面板举例。首先给面板加上一个large类:<div class="panel large">
。
下图展示了普通面板和大面板的区别。效果类似于响应式面板,但是同一个页面可以同时存在两种大小。
我们仍然使用相对单位,但是需要改变它相对的对象。
首先,给每个面板添加父元素声明 font-size: 1rem
,这样无论面板位于页面何处,都有一个可预测的字号。
其次,改用em而不是用rem,重新定义标题的字号,使其相对于刚刚在 1rem
时创建的父元素的字号。
这次修改并不会影响面板的样式,但是它为创建更大的面板做好了准备:只需要加一行CSS代码,即覆盖父元素的1rem。
因为组件内所有的大小都是相对于父元素的字号,所以覆盖后,整个面板的大小都会改变。
现在对普通面板使用 class="panel"
,对大面板使用 class="panel large"
。
同理,可以设置一个更小的字号来定义一个小面板。如果面板是一个更复杂的组件,有多个字号和内边距,仍然只需要一个声明就能缩放它,只要内部的样式都使用em定义即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
视口的相对单位
前面介绍的 em 和 rem 都是相对于 font-size 定义的,但 CSS 里不止有这一种相对单位。
还有相对于浏览器视口定义长度的视口的相对单位。
视口 —— 浏览器窗口里网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏、状态栏。
如果你不熟悉视口的相对单位,请先看下面的简单介绍。
- vh:视口高度的1/100。
- vw:视口宽度的1/100。
- vmin:视口宽、高中较小的一方的1/100
- vmax:视口宽、高中较大的一方的1/100
比如,50vw 等于视口宽度的一半,25vh 等于视口高度的 25%
。
vmin取决于宽和高中较小的一方,这可以保证元素在屏幕方向变化时适应屏幕。
在横屏时,vmin取决于高度;在竖屏时,则取决于宽度。
下图展示了一个正方形元素在不同屏幕尺寸的视口中的样子。
它的宽度和高度都是 90vmin,等于宽高的较小边的 90%
,即横屏高度的 90%
,或者竖屏宽度的 90%
。
当一个元素的宽和高为90vmin时,不管视口的大小或者方向是什么,总会显示成一个稍小于视口的正方形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
视口相对长度非常适合展示一个填满屏幕的大图。
我们可以将图片放在一个很长的容器里,然后设置图片的高度为100vh,让它等于视口的高度。
提示:
相对视口的单位对大部分浏览器而言是较新的特性,因此当你将它跟其他样式结合使用时,会有一些奇怪的bug。
可在 Can I Use
网站中检索 Viewport units: vw, vh, vmin, vmax
中的 “KnownIssues”。
CSS3
有些单位类型在CSS早期版本中没有(尤其是rem和视口的相对单位),它们是在CSS发展过程中加进来的,也就是通常所说的CSS3。
20世纪90年代末到21世纪初,在完成CSS规范的初始工作之后的很长一段时间,CSS几乎没有什么大的改变。
1998年5月,W3C(万维网联盟)发布了CSS2规范。紧接着开始制定2.1版本,对CSS2的问题和bug进行修正。
CSS2.1的制定工作持续了许多年,仍然没有增加重大的新特性,直到2011年4月,才作为提案推荐标准(Proposed Recommendation)发布。
此时,浏览器已经实现了CSS2.1的大部分特性,并且还以CSS3的名义增加了更多特性。
“3”是一个非正式的版本号,其实并没有CSS3规范,而是CSS规范分成了单独的模块,每个模块单独管理版本。
如今背景和边框的规范脱离了盒模型模块以及层叠和继承模块。这样W3C就能够制定CSS某个领域的新版本,而不需要更新其他没变的领域。
很多规范仍然停留在版本3(现在称作level 3),但是有一些规范已经处于level 4,比如选择器规范。
还有一些规范处于level 1,比如Flexbox。
随着这些变化的出现,我们发现从2009年到2013年,新特性呈现了爆炸式发展。
在此期间值得关注的新特性包括rem、视口的相对单位,以及新的选择器、媒体查询、Web字体、圆角边框、动画、过渡、变形、指定颜色的不同方式等。
现在每年还在不断地涌现新特性。
这也意味着我们不再只针对一个特定版本的CSS开发了。
CSS现在是一个活的标准(living standard)。每个浏览器在持续地增加对新特性的支持。
开发人员使用这些新特性,并且适应了这些变化。未来不会有CSS4了,除非人们拿它作为一个更通用的市场术语。
使用 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)。
下面代码用 calc() 结合了em和vw两种单位。删除之前样式表的基础字号(以及相关的媒体查询),换成如下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
现在打开网页,慢慢缩放浏览器,字体会平滑地缩放。
0.5em保证了最小字号,1vw则确保了字体会随着视口缩放。
这段代码保证基础字号从 iPhone 6 里的11.75px一直过渡到1200px的浏览器窗口里的20px。可以按照自己的喜好调整这个值。
我们不用媒体查询就实现了大部分的响应式策略。省掉三四个硬编码的断点,网页上的内容也能根据视口流畅地缩放。
无单位的数值和行高
有些属性允许无单位的值(即一个不指定单位的数)。
支持这种值的属性包括 line-height、z-index、font-weight(700等于bold,400等于normal,等等)。
任何长度单位(如px、em、rem)都可以用无单位的值0,因为这些情况下单位不影响计算值,即 0px、0%、0em 均相等。
警告
一个无单位的0只能用于长度值和百分比,比如内边距、边框和宽度等,而不能用于角度值,比如度,或者时间相关的值,比如秒。
line-height 属性比较特殊,它的值既可以有单位也可以无单位。
通常我们应该使用无单位的数值,因为它们继承的方式不一样。
我们在网页中加上一些文字,看看无单位的行高会如何影响样式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
这个段落继承了行高1.2。因为段落字号是32px(2em × 16px,浏览器默认字号),所以此时行高的计算值为 38.4px(32px × 1.2)
。每行文字之间都会有一个合理的间距。
如果用有单位的值定义行高,可能会产生意想不到的结果,如图所示。每行文字会重叠。
1 2 3 4 5 6 7 |
|
这些结果源于继承的一个怪异特性:当一个元素的值定义为长度(px、em、rem,等等)时,子元素会继承它的计算值。
当使用em等单位定义行高时,它们的值是计算值,传递到了任何继承子元素上。
如果子元素有不同的字号,并且继承了line-height属性,就会造成意想不到的结果,比如文字重叠。
长度 —— 一种用于测量距离的CSS值的正式称谓。
它由一个数值和一个单位组成,比如 5px
。长度有两种类型:绝对长度和相对长度。百分比类似于长度,但是严格来讲,它不是长度。
使用无单位的数值时,继承的是声明值,即在每个继承子元素上会重新算它的计算值。这样得到的结果几乎总是我们想要的。
我们可以用一个无单位的数值给body设置行高,之后就不用修改了,除非有些地方想要不一样的行高。
自定义属性(即 CSS 变量)
2015年,一个期盼已久的CSS规范作为候选推荐标准问世了,叫作层叠变量的自定义属性(Custom Properties for Cascading Variables)。
这个规范给CSS引进了变量的概念,开启了一种全新的基于上下文的动态样式。
你可以声明一个变量,为它赋一个值,然后在样式表的其他地方引用这个值。这样不仅能减少样式表中的重复,而且还有其他好处。
要了解更新各大主流浏览器的支持的情况,请在Can I Use网站中检索“CSS Variables”。
说明
如果刚好用了内置变量功能的CSS预处理器,比如Sass或者Less,你可能就不太想用CSS变量了。
千万别这样。新规范里的CSS变量有本质上的区别,它比任何一款预处理器的变量功能都多。
因此我倾向于称其为“自定义属性”,而不是变量,以强调它跟预处理器变量的区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
上面的代码定义了一个名叫 --main-font
的变量。将其值设置为一些常见的sans-serif字体。
变量名前面必须有两个连字符(--
),用来跟CSS属性区分,剩下的部分可以随意命名。
变量必须在一个声明块内声明。这里使用了 :root
选择器,因此该变量可以在整个网页使用
变量声明本身什么也没做,我们使用时才能看到效果。
调用函数var()
就能使用该变量。利用该函数引用前面定义的变量--main-font
。
在样式表某处为自定义属性定义一个值,作为“单一数据源”,然后在其他地方复用它。
这种方式特别适合反复出现的值,比如颜色值。
在样式表中可以多次使用这个变量,当你想要改变这个颜色值时,只需要在一个地方修改即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
var()函数接受第二个参数,它指定了备用值。如果第一个参数指定的变量未定义,那么就会使用第二个值。
1 2 3 4 5 6 7 8 9 |
|
说明
如果var()函数算出来的是一个非法值,对应的属性就会设置为其初始值。
比如,如果在 padding: var(--brand-color)
中的变量算出来是一个颜色,它就是一个非法的内边距值。
这种情况下,内边距会设置为0。
动态改变自定义属性
在前面的示例中,自定义属性只不过为减少重复代码提供了一种便捷方式,
但是它真正的意义在于,自定义属性的声明能够层叠和继承:可以在多个选择器中定义相同的变量,这个变量在网页的不同地方有不同的值。
例如,可以定义一个变量为黑色,然后在某个容器中重新将其定义为白色。
那么基于该变量的任何样式,在容器外部会动态解析为黑色,在容器内部会动态解析为白色。接下来用这种特性来实现如下图所示的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
|
可以看到第二个面板有深色背景和白色文字。
这是因为面板使用了这些变量,它们会解析成深色容器内定义的值,而不是根元素内定义的值。
注意,这里并没有重新定义面板样式,或者给面板加上额外的类。
在本例中,总共定义了自定义属性两次:第一次在根元素上(--main-color
为黑色),第二次在深色容器上(--main-color
为白色)。
自定义属性就像作用域变量一样,因为它的值会被后代元素继承。
在深色容器中,--main-color为白色,在页面其他地方,则是黑色。
使用 JavaScript 改变自定义属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
|
如果运行以上脚本,所有继承了--main-bg
属性的元素都会更新,使用新的值。
在网页中,第一个面板的背景色会改为浅蓝色。第二个面板保持不变,因为它依然继承了深色容器里的属性。
利用这种技术,就可以用JavaScript实时切换网站主题,或者在网页中突出显示某些元素,或者实时改变任意多个元素。
只需要几行 JavaScript 代码,就可以进行更改,从而影响网页上的大量元素。
探索自定义属性
自定义属性是CSS中一个全新的领域,开发人员刚刚开始探索。因为浏览器支持有限,所以还没有出现“典型”的用法。
我相信假以时日,会出现各种最佳实践和新的用法。这需要你持续关注。继续使用自定义属性,看看能用它做出什么效果。
值得注意的是,在不支持自定义属性的浏览器上,任何使用var()的声明都会被忽略。请尽量为这些浏览器提供回退方案。
1 2 |
|
然而这种做法不是万能的,比如当用到自定义属性的动态特性时,就很难有备用方案。关注Can I Use网站,查看最新的浏览器支持情况。
总结
- 拥抱相对单位,让网页的结构决定样式的含义。
- 建议用rem设置字号,但是有选择地用em实现网页组件的简单缩放。
- 不用媒体查询也能让整个网页响应式缩放。
- 使用无单位的值设置行高。
- 请开始熟悉CSS的一个新特性:自定义属性。