WebKist Inside: CSS 样式表的组成

发布时间 2023-10-01 01:10:54作者: chaoguo1234

StyleSheet

一张 StyleSheet 由一系列 Rules 组成,这些 Rules 可以分成 2 大类: Style Rule 和 At-Rule。下面的例子展示了 Style Rule 和 At-Rule:

// Style Rule
div {
    background-color: red;
    font-size: 12px;
}

// At-Rule
@media print {
    body {
        font-size: 10pt;
     }
}

上面代码的第 1 个 Rule 是 Style Rule,表示 div 标签的背景色是红色,字号是 12px。

代码的第 2 个 Rule 是 At-Rule,At-Rule 都以 '@' 字符开始。@media 表示如果打印 HTML,HTML 中的字号使用 10pt。

Style Rule

Style Rule 由两部分组成: selector list 和声明块,如下图所示:

 selector list 定义一系列 selector,表示声明块应该应用在哪些 HTML 标签上。比如上图中的 selector list 就表示,background-color 和 font-size 应用在 div 和 p 标签上。

有关 Style Rule 的 selector,后面会有更详细的介绍。

Qualified Rule

从定义上讲,Style Rule 只是 Qualified Rule 的一种特殊形式,Qualified Rule 的组成如下图所示:

 从图上可以看到,Qualifed Rule 由 prelude 和声明块两部分组成。如果 prelude 是 selector list,那么此时的 Qualified Rule 就是 Style Rule。

那么什么时候会使用非 Style Rule 的 Qualified Rule 呢?一个场景是 At-Rule @keyframe 定义的动画:

div {
  // 1. 使用 @keyframe 定义的 slide-right 动画
  animation-name: slide-right;
  animation-duration: 2s;
}

// 2. 使用 @keyframe 定义 slide-right 动画
@keyframes slide-right {

    // 3. from 50% to 就是一个 Qualified Rule,但是不是 Style Rule
    from {
    margin-left: 0px;
    }

    50% {
    margin-left: 110px;
    opacity: 1;
    }

    50% {
    opacity: 0.9;
    }

    to {
    margin-left: 200px;
    }
}

上面代码注释 1 处 div 标签使用了 @keyframe 定义的一个动画 slide-right。

代码注释 2 处就是动画的定义,在定义内部,from、50%、to 处的 Rule 就是一个 Qualified Rule,但是不是 Style Rule,如注释 3 所示。

非 Style Rule 的 Qualifed Rule 不能作为 CSS 样式表的顶层 Rule,CSS 样式表的顶层 Rule 只能是 Style Rule 或者 At-Rule。

声明块

声明块中的声明(Declaration)分成两类: 属性声明(property declaration)和描述符声明(descriptor delcaration)。

在 Qualifed Rule 中的声明称为属性声明,而在 At-Rule 中的声明称为描述符声明。属性声明比较常见,描述符声明的例子如下所示:

@font-face {
  // 描述符声明
  font-family: overridden-font;
}

本质上来看,属性声明和描述符声明没有什么区别,只是所在的位置不一样。

无论是属性声明,还是描述符声明,都由声明名和声明值组成。

At-Rule

At-Rule 以 '@' 字符开头。

At-Rule 分成两大类: 声明式 At-Rule(statement At-Rule) 和块式 At-Rule(Block At-Rule)。

声明式 At-Rule 就像一条语句一样,以分号结尾。@cahrset 就是一个声明式 At-Rule:

@charset "utf-8";

块式 At-Rule 由 {} 包围,上面的 @media 就是一个块式 At-Rule。

块式 At-Rule 里面可以包含描述符声明,比如:

@font-face {
  // 描述符声明
  font-family: overridden-font;
}

可以包含 Qualifed Rule,比如:

@media print {
    // Qualifed Rule,也是 Style Rule
    body {
        font-size: 10pt;
     }
}

可以包含其他 At-Rule,比如:

@supports (display: flex) {
  @media screen and (min-width: 900px) {
    article {
      display: flex;
    }
  }
}

Selector

Style Rule 中的 Selector 用来匹配 HTML 中的标签,以便决定声明块中的声明运用在哪些 HTML 标签上,比如:

div {
    background-color: red;
}

表示 HTML 中的所有 div 标签背景色都要是红色。

Selector 分为 5 类: 简单 Selector(Simple Selector)、复合 Selector(Compound Selector)、组合 Selector(Complex Selector)、Selector List。

Simple Selector

简单 Selector 分为 7 种情形:

1 类型 Selector (Type Selector)

2 通用 Selector (Universal Selector)

3 属性 Selector (Attribute Selector)

4 类 Selector (Class Selector)

5 类型 Selector (Type Selector)

6 ID Selector

7 伪类 Selector (Pseudo-Class Selector)

类型 Selector

类型 Selector 就是 HTML 中的标签名,比如:

div {
    background-color: red;
}

中的 div 就是类型 Selector,匹配 HTML 中的所有 <div> 标签。

通用 Selector

通用 Selector 是一种特殊的类型 Selector,它匹配任意一个 HTML 标签,使用字符 '*' 表示,比如:

* {
    font-size: 13px;
}

表示 HTML 中每一个标签的字号都是 13px。

如果通用 Selector 和其他简单 Selector 组成复合 Selector,那么通用 Selector 不起作用,也就是说:

*.news <===> 等价于 .news
*#item <===> 等价于 #item

命名空间

@namespace 定义一个命名空间,相关语法如下:

@namespace <namespace-prefix>? [<string>|<url>]

其中,namespace-prefix 可以省略,如果省略了 namespace-prefix,那么就是定义了一个默认命名空间。

定义命名空间的例子如下:

// 定义了默认命名空间
@namespace "http://www.w3.org/1999/xhtml";

// 定义了一个命名空间,namespace-prefix 是 svg
@namespace svg "http://www.w3.org/2000/svg";

// 以字符串定义命名空间
@namespace  xml "XML-namespace-URL";

对于定义在一张 CSS 样式表里面的 namespace-prefix,只在当前 CSS 样式表可见。

The namespace prefix is declared only within the style sheet in which its @namespace rule appears. It is not declared in any style sheets importing or imported by that style sheet, nor in any other style sheets applying to the document.

如果一张样式表里面声明了多个命名空间,只有最后一个命名空间有效。

If a namespace prefix or default namespace is declared more than once only the last declaration shall be used.

类型 Selector 和通用 Selector 可以结合命名空间使用,这样类型 Selector 和通用 Selector 只匹配位于指定命名空间的 HTML 标签。

相关语法如下:

// ns 表示命名空间
// E 表示类型 Selector 或者通用 Selector

// 匹配位于命名空间 ns 的 HTML 标签 E
ns|E

// 匹配位于任意命名空间的 HTML 标签 E,如果 E 不属于任何命名空间,也会匹配上
*|E

// 匹配不属于任何命名空间的 HTML 标签 E
|E

// 如果没有默认命名空间,这个语法等价于 *|E
// 如果有默认命名空间,这个语法等价于 ns|E
E

下面看一些例子:

@namespace foo url(http://www.example.com);
foo|h1 { color: blue }  /* first rule */
foo|* { color: yellow } /* second rule */
|h1 { color: red }      /* ...*/
*|h1 { color: green }
h1 { color: green }

上面代码定义了一个命名空间 foo。

第 1 条 Rule 匹配 HTML 中所有位于命名空间 foo 的 <h1> 标签。

第 2 条 Rule 匹配 HTML 中位于命名空间 foo 的所有标签。

第 3 条 Rule 匹配不属于任何命名空间的 <h1> 标签。

第 4 条 Rule 等价 HTML 中所有 <h1> 标签,这些 <h1> 标签可以位于任意命名空间,也可以不位于任何命名空间。

第 5 条 Rule 等价于第 4 条 Rule,因为没有定义默认命名空间。

属性 Selector

属性 Selector 匹配 HTML 标签中具有相应属性的标签,比如:

[class="example"]

匹配所有具有 class 属性的 HTML 标签,假设一个 <span> 标签具有 class 属性并且属性值是 “example",那么这个 <span> 标签就被匹配到:

<span class="example">Hello, World!</span>

属性 Selector 的语法如下:

// 匹配具有属性名为 attr 的 HTML 标签
[attr]

// 匹配具有属性名为 attr,属性值为 val 的 HTML 标签
[attr=val]

// 匹配具有属性名 attr 的 HTML 标签,并且该属性值是一个由空格分割的 list,
// 如果这个属性值 list 里面有一个值是 val,那么就匹配上。
// 如果 val 本身是一个空字符串或者包含空格的字符串,那么就匹配不上任何 HTML 标签
[attr~=val]

// 匹配具有属性名 attr 的 HTML 标签,并且该属性值为 val 或者以 val- 开头
[attr|=val]

// 匹配具有属性名 attr 的 HTML 标签,并且属性值以 val 开头。
// 如果 val 是空字符串,那么匹配不到任何 HTML 标签
[attr^=val]

// 匹配具有属性名 attr 的 HTML 标签,并且属性值以 val 结尾。
// 如果 val 是空字符串,那么匹配不到任何 HTML 标签
[attr$=val]

// 匹配具有属性名 attr 的 HTML 标签,并且属性值至少包含 val。
// 如果 val 是空字符串,那么匹配不到任何 HTML 标签
[attr*=val]

具体例子如下:

// 匹配具有 title 的 <h1> 标签
h1[title]

// 如果 <span> 标签有一个 class 属性,并且属性值为 example,则匹配
span[class="example"]

// 如果 <a> 标签的 rel 属性值是 "copyright copyleft copyeditor",则匹配
a[rel~="copyright"]

// 如果 <a> 标签的 hreflang 的属性值为 en 或者 en-US,则匹配
a[hreflang|="en"] 

// 如果 <object> 标签的 type 属性以 image/ 开头,则匹配
object[type^="image/"] 

// 如果 <a> 标签的 href 属性以 .html 结尾,则匹配
a[href$=".html"] 

// 如果 <p> 标签的 title 属性值为 "hello, world",则匹配
p[title*="hello"] 

和类型 Selector、通用 Selector 一样,属性 Selector 的属性名 attr 也可以结合命名空间。不同的是,类型 Selector、通用 Selector 可以结合默认命名空间,属性 Selector 不能结合默认命名空间。

相关语法如下:

@namespace foo "http://www.example.com";
[foo|attr=val] { color: blue }
[*|attr] { color: yellow }
[|attr] { color: green }
[attr] { color: green }

上面代码定义了一个命名空间 foo。

第 1 条 Rule 匹配属性名 attr 位于命名空间 foo 的 HTML 标签。

第 2 条 Rule 匹配属性名 attr 位于任意命名空间或者不位于任何命名空间的 HTML 标签。

第 3 条 Rule 和第 4 条 Rule 是等价的,只匹配 attr 不属于任何命名空间的 HTML 标签。

类 Selector

类 Selector 以 '.' 字符开始。

例子如下:

.test {
    font-size: 20px;
}

这个类 Selector 匹配第 1 个 和第 3 个 <h1> 标签,不匹配第 2 个 <h1> 标签,因为第 2 个 <h1> 标签没有对应的 class:

<h1 class="test">标题一</h1>
<h1>标题二</h1>
<h1 class="test">标题三</h1>

ID Selector

ID Selector 以 '#' 字符开始,ID Selector 必须是唯一的,因此只匹配唯一的 HTML 标签:

例子如下:

#test {
    font-size: 20px;
}

这个 ID Selector 匹配第 1个 <h1> 标签:

<h1 id="test">标题一</h1>
<h1>标题二</h1>
<h1>标题三</h1>

伪类 Selector

有一些匹配行为,上述的简单 Selector 无法表达,比如有一个场景是,当用户鼠标停留在 <h1> 标签上,<h1> 标签才变换背景色。为了表达"用户停留"这个行为,就需要使用伪类 :hover,例子如下:

h1:hover {
    background-color: red;
}

伪类 Selector 以 ':' 开头,可以分成普通伪类和函数伪类。

:hover 就是普通伪类,:lang() 就是一个函数伪类。

伪类必须结合类型 Selector 和通用 Selector 才能使用,必须跟在类型 Selector 和通用 Selector 后面。

复合 Selector

复合 Selector 由一个或者多个简单 Selector 组成,如果有多个简单 Selector,简单 Selector 之间不能由 Combinator 连接(Combinator 参看组合 Selector)。

一个复合 Selector 的所有简单 Selector 匹配完成,整个复合 Selector 才算匹配,比如:

div#test {
    background-color: red;
}

只匹配具有 id 属性并且 id 属性等于 test 的 <div> 标签:

<div>Test1</div>
<div id="test">Test2</div>

如果类型 Selector 或者通用 Selector 出现在复合 Selector 中,那么类型 Selector 和通用 Selector 必须是复合 Selector 中的第一个简单 Selector。

同时,如果复合 Selector 中已经有了一个类型 Selector 或者通用 Selector,就不可以有其他的类型 Selector 和通用 Selector。

在复合 Selector 中,类 Selector、ID Selector、属性 Selector、伪类 Selector 都称为 Subclass-Selector。

复合伪元素 Selector (Pseduo-Compound Selector)

和伪类(Pseduo-Class)一样,伪元素(Pseduo-Element)也是用来表达 HTML 标签无法表达的信息。

与伪类不同的是,伪类侧重表达某种状态或者行为,而伪元素侧重表达 HTML 文档的某部分内容,比如 HTML 文档某行的首字母,就可以使用伪元素 ::first-letter 来表示。

复合伪元素 Selector 的组成首先要有一个伪元素,这个伪元素的后面可以跟一个伪类,这个伪元素的前面可以有一个复合 Selector或者另外一个复合伪元素 Selector。因此下面两种复合伪元素 Selector 都是合法的:

.foo::before:hover
.foo::before::marker

复合伪元素 Selector 中不能出现 Combinator。

复合微元素 Selector 是用来匹配伪元素的,要成功匹配伪元素需要满足:

1 被匹配的伪元素需要是复合伪元素 Selector 中的伪元素;

2 匹配复合伪元素 Selector 中的伪类;

3 匹配复合伪元素 Selector 中的复合 Selector,这个被匹配的 HTML 标签被称为 Originating Element。

如果复合伪元素 Selector 没有复合 Selector,那么默认使用通用 Selector,也就是说,下面两种复合伪元素 Selector 是等价的:

::before <====> 等价于 *::before

本质上讲,复合伪元素 Selector 跟复合 Selector 不一样,如果一个地方只能使用复合 Selector,那么就不能使用复合伪元素 Selector。

NOTE: A pseudo-compound selector is not a compound selector, and can’t be used in places that expect a compound selector only. Pseudo-compound selectors act as if they carry a combinator with themselves, expressing their relationship with their originating element, just as the > combinator expresses a relationship with a parent element.

组合 Selector

组合 Selector 是由 Combinator 连接的两个复合 Selector。

Combinator 总共有 4种:

1 Descendant Combinator

2 Child Combinator

3 Next-Sibling Combinator

4 Subsequence-Sibling Combinator

Descendant Combinator

后代 Combinator,由空格表示,匹配一个 HTML 标签的所有后代标签,比如:

h1 em

会匹配 <h1> 标签的所有后代 <em> 标签:

<h1>This <span class="myclass">headline
is <em>very</em> important</span></h1>

Child Combinator

子 Combinator,由 '>' 表示,匹配一个 HTML 标签的所有子标签,比如:

body > p

会匹配 <body> 标签的所有子 <p> 标签:

<body>
    <p>段落1</p>
    <p>段落2</p>
    <p>段落3</p>
</body>

 Next-Sibling Combinator

下一个相邻兄弟节点 Combinator,由 '+' 表示,直接看例子:

div + p

会匹配 <div> 标签后面第一个 <p> 标签,而不会匹配其他 <p> 标签:

<p>上一个兄弟节点</p>
<div>我要匹配</div>
<p>下一个兄弟节点</p>
<p>下下个兄弟节点</p>

Subsequence-Sibling Combinator

后续兄弟 Combinator,由 '~' 表示,直接看例子:

div ~ p

会匹配 <div> 标签后面所有的兄弟 <p> 标签,而不会匹配 <div> 标签上面的兄弟 <p> 标签:

<p>上一个</p>
<div>DIV1</div>
<div>DIV2</div>
<p>下一个</p>
<p>下下一个</p>

Selector List

由逗号 ',' 连接的简单 Selector、复合 Selector、组合 Selector 组成,比如:

div, h1#item, p + span {
    background-color: red;
}

h1, h2, h3 {
    background-color: green;
}

参考连接

Selector Level 4

CSS Syntax Model Level 3

List of CSS descriptors, both proposed and standard