CSS 的顶层样式表由两种规则组成的规则列表构成,一种被称为 at-rule,也就是 at 规则,另一种是 qualified rule,也就是普通规则。
at-rule 由一个 @ 关键字和后续的一个区块组成,如果没有区块,则以分号结束。这些 at-rule 在开发中使用机会远远小于普通的规则,所以它的大部分内容,你可能会感觉很陌生。
这些 at 规则正是掌握 CSS 的一些高级特性所必须的内容。qualified rule 则是指普通的 CSS 规则,也就是我们所熟识的,由选择器和属性指定构成的规则。
at 规则
普通规则
qualified rule 主要是由选择器和声明区块构成。声明区块又由属性和值构成。我在下面的列表中,介绍了这部分语法的组成要点。
选择器
我们从语法结构可以看出,任何选择器,都是由几个符号结构连接的:空格、大于号、加号、波浪线、双竖线,这里需要注意一下,空格,即为后代选择器的优先级较低。
然后对每一个选择器来说,如果它不是伪元素的话,由几个可选的部分组成,标签类型选择器,id、class、属性和伪类,它们中只要出现一个,就构成了选择器。
如果它是伪元素,则在这个结构之后追加伪元素。只有伪类可以出现在伪元素之后。我在下面用一个列表(不太严谨地)整理了选择器的语法结构:
我们在这里可以参考一个示例图:
声明:属性和值
声明部分是一个由“属性: 值”组成的序列。
属性是由中划线、下划线、字母等组成的标识符,CSS 还支持使用反斜杠转义。我们需要注意的是:属性不允许使用连续的两个中划线开头,这样的属性会被认为是 CSS 变量。
以双中划线开头的属性被当作变量,与之配合的则是 var 函数:
:root {
--main-color: #06c;
--accent-color: #006;
}
/* The rest of the CSS file */
#foo h1 {
color: var(--main-color);
}
值的部分,主要在标准 CSS Values and Unit,根据每个 CSS 属性可以取到不同的值,这里的值可能是字符串、标识符。
CSS 属性值可能是以下类型。
这里我们要重点介绍一下函数。一些属性会要求产生函数类型的值,比如 easing-function 会要求 cubic-bezier() 函数的值:
CSS 支持一批特定的计算型函数:
calc()函数是基本的表达式计算,它支持加减乘除四则运算。在针对维度进行计算时,calc() 函数允许不同单位混合运算,这非常的有用。
section {
float: left;
margin: 1em; border: solid 1px;
width: calc(100%/3 - 2*1em - 2*1px);
}
max()、min() 和 clamp() 则是一些比较大小的函数,max() 表示取两数中较大的一个,min() 表示取两数之中较小的一个,clamp() 则是给一个值限定一个范围,超出范围外则使用范围的最大或者最小值。
toggle() 函数在规则选中多于一个元素时生效,它会在几个值之间来回切换,比如我们要让一个列表项的样式圆点和方点间隔出现,可以使用下面代码:
ul { list-style-type: toggle(circle, square); }
attr() 函数允许 CSS 接受属性值的控制。
我先来讲讲选择器是什么,选择器是由 CSS 最先引入的一个机制(但随着 document.querySelector 等 API 的加入,选择器已经不仅仅是 CSS 的一部分了)。我们今天这一课,就重点讲讲 CSS 选择器的一些机制。
选择器的基本意义是:根据一些特征,选中元素树上的一批元素。
选择器是由简单选择器逐级组合而成的结构。
简单选择器
类型选择器和全体选择器
根据一个元素的标签名来选中元素。
这看上去非常简单,但是实际上,我们还必须要考虑 html 或者 xml 元素的命名空间问题。
id 选择器与 class 选择器
id 选择器和 class 选择器都是针对特定属性的选择器。id 选择器是“#”号后面跟随 id 名,class 选择器是“.”后面跟随 class 名。
属性选择器
属性选择器根据 HTML 元素的属性来选中元素。属性选择器有四种形态。
直接在方括号中放入属性名,是检查元素是否具有这个属性,只要元素有这个属性,不论属性是什么值,都可以被选中。
精确匹配,检查一个元素属性的值是否是 val。
多种匹配,检查一个元素的值是否是若干值之一,这里的 val 不是一个单一的值了,可以是用空格分隔的一个序列。
开头匹配,检查一个元素的值是否是以 val 开头,它跟精确匹配的区别是属性只要以 val 开头即可,后面内容不管。
有些 HTML 属性含有特殊字符,这个时候,可以把 val 用引号括起来,形成一个 CSS 字符串。CSS 字符串允许使用单双引号来规避特殊字符,也可以用反斜杠转义,这样,就可以表示出任意属性值啦。
伪类选择器
伪类选择器是一系列由 CSS 规定好的选择器,它们以冒号开头。伪类选择器有普通型和函数型两种。
:root 伪类表示树的根元素,在选择器是针对完整的 html 文档情况,我们一般用 html 标签即可选中根元素。但是随着 scoped css 和 shadow root 等场景出现,选择器可以针对某一子树来选择,这时候就很需要 root 伪类了。
:empty 伪类表示没有子节点的元素,这里有个例外就是子节点为空白文本节点的情况。
:nth-child 和 :nth-last-child 这是两个函数型的伪类,CSS 的 An+B 语法设计的是比较复杂的,我们这里仅仅介绍基本用法。我们还是看几个例子:
:nth-last-child 的区别仅仅是从后往前数。
:first-child 表示第一个元素。
:last-child 表示最后一个元素。
:only-child 按字面意思理解即可,选中唯一一个子元素。
of-type 系列,是一个变形的语法糖,S:nth-of-type(An+B) 是:nth-child(|An+B| of S) 的另一种写法。
以此类推,还有 nth-last-of-type、first-of-type、last-of-type、only-of-type。
链接与行为伪类选择器
在 Selector Level 4 草案中,还引入了 target-within、focus-within 等伪类,用于表示 target 或者 focus 的父容器。
逻辑伪类选择器
我们这里介绍一个逻辑伪类 —— :not 伪类。
这个伪类是个函数型伪类,它的作用时选中内部的简单选择器命中的元素。
其它伪类选择器
dir | lang
play | pause
current | past | future
nth-col | nth-last-col
伪类是很大的一类简单选择器,它是选择器能力的一种补充。在实际使用中,我还是建议你尽量通过合适的 id 和 class 来标识元素,约束伪类的使用。最好只在不得不使用伪类的场景使用伪类,这对于 CSS 代码的性能和可读性都有好处。
结语
选择器的组合
在 CSS 规则中,选择器部分是一个选择器列表。
选择器列表是用逗号分隔的复杂选择器序列;复杂选择器则是用空格、大于号、波浪线等符号连接的复合选择器;复合选择器则是连写的简单选择器组合。
根据选择器列表的语法,选择器的连接方式可以理解为像四则运算一样有优先级。
复合选择器表示简单选择器中“且”的关系,例如,例子中的“ .b.d ”,表示选中的元素必须同时具有 b 和 d 两个 class。
复杂选择器是针对节点关系的选择,它规定了五种连接符号。
我们在实际使用时,比较常用的连接方式是“空格”和“>”。
工程实践中一般会采用设置合理的 class 的方式,来避免过于复杂的选择器结构,这样更有利于维护和性能。
逗号表示“或”的关系,实际上,可以把它理解为“两条内容一样的 CSS 规则”的一种简写。
选择器的优先级
CSS 标准用一个三元组 (a, b, c) 来构成一个复杂选择器的优先级。
CSS 标准建议用一个足够大的进制,获取“ a-b-c ”来表示选择器优先级。
行内属性的优先级永远高于 CSS 规则,浏览器提供了一个“口子”,就是在选择器前加上“!import”。这个用法非常危险,因为它相当于一个新的优先级,而且此优先级会高于行内属性。
同一优先级的选择器遵循“后面的覆盖前面的”原则,我们可以看一个例子:
伪元素
::first-line 和 ::first-letter 是比较类似的伪元素,其中一个表示元素的第一行,一个表示元素的第一个字母。
CSS 标准只要求 ::first-line 和 ::first-letter 实现有限的几个 CSS 属性,都是文本相关,这些属性是下面这些。
接下来我们说说 ::before 和 ::after 伪元素。
这两个伪元素跟前面两个不同的是,它不是把已有的内容套上一个元素,而是真正的无中生有,造出一个元素。
::before 表示在元素内容之前插入一个虚拟的元素。
::after 则表示在元素内容之后插入一个虚拟的元素。
这两个伪元素所在的 CSS 规则必须指定 content 属性才会生效。
::before 和 ::after 中支持所有的 CSS 属性。实际开发中,这两个伪元素非常有用,有了这两个伪元素,一些修饰性元素,可以使用纯粹的 CSS 代码添加进去,这能够很好地保持 HTML 代码中的语义,既完成了显示效果,又不会让 DOM 中出现很多无语义的空元素。
总结
这一课,我们讲了 CSS 选择器的三种机制:选择器的组合、选择器优先级、以及伪元素。
在选择器组合这一部分,我们讲到了,选择器的连接方式像四则运算一样有优先级,
第一优先级是无连接符号;第二优先级是:“空格”、“~”、“+”、“>”、“||”;第三优先级是“,”。
正常流的行为
我们可以用一句话来描述正常流的排版行为,那就是:依次排列,排不下了换行。
在正常流基础上,我们有 float 相关规则,使得一些盒占据了正常流需要的空间,我们可以把 float 理解为“文字环绕”。
我们还有 vertical-align 相关规则规定了如何在垂直方向对齐盒。vertical-align 相关规则看起来复杂,但是实际上,基线、文字顶 / 底、行顶 / 底都是我们正常书写文字时需要用到的概念,只是我们平时不一定会总结它们。
下图展示了在不同的 vertical-align 设置时,盒与文字是如何混合排版的。为了方便你理解,我们用代码给大家标注了基线、文字顶 / 底、行顶 / 底等概念
除此之外,margin 折叠是很多人非常不理解的一种设计,但是实际上我们可以把 margin 理解为“一个元素规定了自身周围至少需要的空间”,这样,我们就非常容易理解为什么 margin 需要折叠了。
正常流的原理
在 CSS 标准中,规定了如何排布每一个文字或者盒的算法,这个算法依赖一个排版的“当前状态”,CSS 把这个当前状态称为“格式化上下文(formatting context)”。
我们可以认为排版过程是这样的:
格式化上下文 + 盒 / 文字 = 位置
我们需要排版的盒,是分为块级盒和行内级盒的,所以排版需要分别为它们规定了块级格式化上下文和行内级格式化上下文。
块级格式化上下文顺次排列元素:
行内级格式化上下文顺次排列元素:
当我们要把正常流中的一个盒或者文字排版,需要分成三种情况处理。
我们以上讲的都是一个块级格式化上下文中的排版规则,实际上,页面中的布局没有那么简单,一些元素会在其内部创建新的块级格式化上下文,这些元素有:
正常流的使用技巧
结语
用一句话来描述正常流的排版行为,那就是:依次排列,排不下了换行。
我们将正常流的知识分成了三个部分。
Flex 的设计
Flex 在英文中是可伸缩的意思,一些翻译会把它译作弹性
Flex 排版的核心是 display:flex 和 flex 属性,它们配合使用。具有 display:flex 的元素我们称为 flex 容器,它的子元素或者盒被称作 flex 项。
flex 项如果有 flex 属性,会根据 flex 方向代替宽 / 高属性,形成“填补剩余尺寸”的特性,这是一种典型的“根据外部容器决定内部尺寸”的思路,也是我们最常用的 Windows 和 Apple 窗口系统的设计思路。
Flex 的原理
Flex 的应用
接下来我们来尝试用 flex 排版来解决一下当年的 CSS 三大经典问题(简直易如反掌)。
总结
我们先从感性的角度,介绍了 flex 的设计,flex 的设计是一种不同于流布局的,自外而内的设计思路。
接下来我们讲解了 flex 的实现原理,也就是具体的排版算法。要想理解 flex 排版的原理,主轴和交叉轴是非常重要的抽象,flex 排版三个步骤:分行、计算主轴、计算交叉轴。
最后我们给出了几个例子,解决了旧时代的 CSS 三大经典问题。
CSS 中跟动画相关的属性有两个:animation 和 transition。
animation 属性
@keyframes mykf
{
from {background: red;}
to {background: yellow;}
}
div
{
animation:mykf 5s infinite;
}
这里展示了 animation 的基本用法,实际上 animation 分成六个部分:
transition 属性
接下来我们来介绍一下 transition。transition 与 animation 相比来说,是简单得多的一个属性。
这里的四个部分,可以重复多次,指定多个属性的变换规则。
实际上,有时候我们会把 transition 和 animation 组合,抛弃 animation 的 timing-function,以编排不同段用不同的曲线。
@keyframes mykf {
from { top: 0; transition:top ease}
50% { top: 30px;transition:top ease-in }
75% { top: 10px;transition:top ease-out }
to { top: 0; transition:top linear}
}
在这个例子中,在 keyframes 中定义了 transition 属性,以达到各段曲线都不同的效果。
三次贝塞尔曲线
我们在这里首先要了解一下贝塞尔曲线,贝塞尔曲线是一种差值曲线,它描述了两个点之间差值来形成连续的曲线形状的规则。
贝塞尔曲线拟合
理论上,贝塞尔曲线可以通过分段的方式拟合任意曲线,但是有一些特殊的曲线,是可以用贝塞尔曲线完美拟合的,比如抛物线。
总结
我们今天的课程,重点介绍了动画和它背后的一些机制。
CSS 用 transition 和 animation 两个属性来实现动画,这两个属性的基本用法很简单,我们今天还介绍了它们背后的原理:贝塞尔曲线。
我们中介绍了贝塞尔曲线的实现原理和贝塞尔曲线的拟合技巧。
颜色的原理
我们在计算机中,最常见的颜色表示法是 RGB 颜色,它符合光谱三原色理论:红、绿、蓝三种颜色的光可以构成所有的颜色。
品红、黄、青、黑
我们刚才讲的颜色是从人类的视觉原理建模,应该说是十分科学了。但是,人类对颜色的认识却并非来自自己的神经系统,当我们把阳光散射,可以得到七色光:红橙黄绿蓝靛紫,实际上,阳光接近白光,它包含了各种颜色的光,它散射之后,应该是个基本连续的。这说明对人的感知来说,颜色远远大于红、绿、蓝。
色相(H)。加上颜色的纯度(S)和明度(L),就构成了一种颜色的表示
其它颜色
接下来我们讲一讲 RGBA,RGBA 是代表 Red(红色)、Green(绿色)、Blue(蓝色)和 Alpha 的色彩空间。RGBA 颜色被用来表示带透明度的颜色,实际上,Alpha 通道类似一种颜色值的保留字。在 CSS 中,Alpha 通道被用于透明度,所以我们的颜色表示被称作 RGBA,而不是 RGBO(Opacity)。
为了方便使用,CSS 还规定了名称型的颜色,它内置了大量(140 种)的颜色名称。不过这里我要挑出两个颜色来讲一讲:金(gold)和银(silver)。
渐变
在 CSS 中,background-image 这样的属性,可以设为渐变。CSS 中支持两种渐变,一种是线性渐变,一种是放射性渐变,我们先了解一下它们的基本用法:
线性渐变的写法是:
linear-gradient(direction, color-stop1, color-stop2, ...);
放射性渐变需要一个中心点和若干个颜色:
radial-gradient(shape size at position, start-color, ..., last-color);
形状
CSS 中的很多属性还会产生形状,比如我们常见的属性:
这些产生形状的属性非常有趣,我们也能看到很多利用它们来产生的 CSS 黑魔法。然而,这里我有一个相反的建议,我们仅仅把它们用于基本的用途,把 border 用于边框、把 box-shadow 用于阴影,把 border-radius 用于圆角,所有其它的场景,都有一个更好的替代品:datauri+svg。
总结
今天我们介绍了 CSS 中渲染相关的属性:颜色和形状。
我们重点介绍了 CSS 的颜色系统,从颜色基本原理讲解了 RGB 颜色、CMYK 颜色和 HSV 颜色,我们还讲解了 Alpha 通道。
接下来我们又讲了颜色的一个重要应用:渐变,我们可以把渐变看作是一个更复杂的颜色,它非常实用,能够用渐变绘制很多的图像。
最后我们讲解了形状相关的属性,以及 SVG 应用的一个小技巧。
浏览器中已经实现的属性
Object.keys(document.body.style).filter(e => !e.match(/^webkit/))
这段代码思路非常简单,就是枚举 document.body.style 上的所有属性,并且去掉 webkit 前缀的私有属性。
小实验:找出 W3C 标准中的 CSS 属性
我们这个爬虫的思路是:用 iframe 来加载所有标准的网页,然后用 JavaScript 找出它们中间定义的属性。