1.模式匹配
在css中,通过模式匹配规则来决定给文档树中的元素应用什么样的样式。这些模式规则就被称为选择器。一个选择器可能只是一个单一的元素名称,也可能是包含复杂上下文的模式规则集合。如果一个元素满足一个选择器中的所有条件,我们就称这个选择器选中了该元素。
下面的这张表格概要的展示了CSS2.1的选择器语法:
2.选择器语法
一个选择器既可以是类型选择器,又可以是全局选择器,也可以是前面二者与零个或多个属性选择器、ID选择器或伪类组合形成的选择器集合。如果一个元素满足一个选择器中的所有组成条件(单个简单选择器),那么这个元素满足该选择器。
一个选择器由以连接符分隔的众多单个简单选择器组成。连接符包含: 空白字符、">"和"+"。空白字符也可以出现在连接符与单个选择器之间。
文档树中满足一个选择器的元素被称作这个选择器的主体。一个包含单个简单选择器的选择器匹配所有满足条件的任何元素。当在一个选择器前面增加一个单个简单选择器时,实际上是在原来的基础上增加了额外的约束条件。所以,一个选择器的主体总是该选择器中最后一个单个简单选择器的主体的子集。
例如:
p em {color: red}
上面的选择器的主体就是它的单个简单选择器em的主体的子集。
2.1 分组
当多个选择器拥有相同的约束条件时,可以用逗号对他们进行分组。
在下面的例子中,我们将拥有完全相同的约束条件的三个选择器压缩成一个。因此,
h1 {font-family: sans-serif}
h2 {font-family: sans-serif}
h3 {font-family: sans-serif}
与
h1, h2, h3 {font-family: sans-serif}
相同。
3.全局选择器
全局选择器,也就是,匹配任何类型的元素。它匹配文档树中的任何单一元素。
如果全局选择器与其他选择器一起使用,那么将被省略。比如:
- *[lang=fr] 与 [lang=fr] 相同
- *.warning 与 .warning 相同
- *#myid 与 #myid 相同
4.类型选择器
一个类型选择器匹配一种元素类型。类型选择器匹配文档树中该类型的任何实例。
下面的选择器匹配文档树中所有的H1元素:
h1 {font-family: sans-serif}
5.后代选择器
很多时候,我们希望选择器可以匹配文档树中的某个元素,而这个元素又是另一个元素的后代元素(比如,匹配那些H1元素中的EM元素)。后代选择器就表达了这样的关系。一个后代选择器由两个或多个由空白字符分隔的选择器组成。形如"A B"的后代选择器匹配B元素,且B元素是A元素的任意一个后代。
举个例子,考虑下面的两条规则:
h1 {color: red}
em {color: red}
尽管上面两条规则的本意是通过改变文本的颜色来展现强调效果,但是在如下的例子中,强调的效果就不存在了:
<H1>This headline is<EM>very</EM> important</H1>
我们再补充一条规则来使得H1中的任何EM的文本颜色都是蓝色的:
h1 {color: red}
em {color: red}
h1 em {color: blue}
第三条规则在下面的代码片段中将会匹配EM元素:
<H1>This <SPAN class="myclass">headline
is <EM>very</EM> important</SPAN></H1>
下面的这个选择器:
div * p
将会匹配P元素,这个P元素至少是DIV的孙子元素(*匹配任何元素)。
下面这个选择器,包含了后代选择器和属性选择器,将匹配任何(1)设置了href属性并且(2)在P元素中同时P元素在一个DIV元素中的元素。
div p *[href]
6.子选择器
子选择器匹配某个元素当且仅当这个元素是另一个元素的子元素的时候。一个子选择器由两个或多个由">"分隔的选择器组成。
如下选择器将会设置所有是BODY的子元素的P元素的样式:
body > p {line-height: 1.3px}
下面这个选择器包含了后代选择器和子选择器:
div ol > li p
该选择器匹配这样的P元素:这个P元素是LI的后代,而LI是OL的子元素,同时OL是DIV的后代。
7.相邻兄弟选择器
相邻兄弟选择器的语法形式为: E1 + E2,当中E2是该选择器的主体。选择器匹配这样的元素: E1和E2拥有共同的父元素,并且在文档树中,E1为在E2之前紧挨着E2的元素(忽略非元素节点,如文本节点和注释节点)。
因此,如下选择器表示当P元素为MATH元素的相邻兄弟元素时,P不缩进:
math + p {text-indent: 0}
下面的这个例子减小了H1和紧随H1的H2的垂直距离:
h1 + h2 {margin-top: -5mm}
下面的这个选择器在上面的例子基础上增加了一个类选择器,因此当H1有opener类时才会被这选中:
h1.opener + h2 {margin-top: -5mm}
8.属性选择器
CSS2.1 允许文档的作者给源文档中定义了某种属性的元素应用规则。
8.1
属性选择器共有四种:
- [att]
匹配设置了att属性的元素,不管属性的值是什么 - [att=val]
匹配att属性值等于"val"的元素 - [att~=val]
匹配这样的元素,该元素的att属性的值是一个由空白字符分隔的多个值组成的列表,这些值中有一个为"val"。如果"val"包含空白字符 ,则该条规则不生效(因为属性值列表就是有空白字符分隔的)。如果"val"是一个空字符串,该条规则也不生效。 - [att|=val]
匹配这样的元素,该元素的att属性要么就等于"val",要么以"val"开头并且紧跟一个"-"(U+002D)。该规则主要用于匹配语言区域码。
属性值必须是标识符或者字符串。选择器中的属性名是否大小写敏感由文档语言决定。
例如,下面的选择器匹配设置了title属性的所有H1元素,不管title的值是什么:
h1[title] {color: blue}
如下的选择器将会匹配所有的class属性值为"example"的SPAN元素:
span[class=example] {color: bule}
多个属性选择器可以用于同一个元素的多个属性,甚至可以多次应用于同一个元素的同一个属性。
如下选择器匹配所有的hello属性值为"Cleveland",并且goodbye属性值为"Columbus"的SPAN元素:
span[hello="Cleveland"][goodbye="Columbus"] {color: blue}
下面的选择器展示了"="与"~="的不同。第一个选择器将会匹配rel属性的值为"copyright copyleft copyeditor"的A元素,而第二个选择器将仅会匹配href属性值为"http://www.w3.org/" 的A元素。
a[rel~="copyright"]
a[href="http://www.w3.org/"]
如下选择器将会隐藏所有lang属性为"fr"(法语)的元素:
*[lang=fr] {display: none}
下面这个选择器将会匹配所有的lang属性以"en"开头(包括"en", "en-US", "en-cockney")的元素,
*[lang|="en"] {color: red}
8.2 DTDs中的默认属性值
属性选择器匹配文档树中元素的属性值。默认的属性值一般会保存在一份DTD中或者其他的什么地方,但是不一定总是可以被选择器匹配到。我们的样式表需要在默认值没有被包含在文档树中的时候也能正常工作。
更准确的说,UA(User Agent用户客户端)可能但不被强制去读DTD的“外部子集”,但被要求在文档的“内部子集”中查找默认属性值。取决于UA,定义在DTD的外部子集的默认属性值不一定会出现在文档树中。
例如,EXAMPLE元素的notation属性有个默认值为"decimal"。DTD片段可能为:
<!ATTLIST EXAMPLE notation (decimal, octal) "decimal">
如果样式表中包含如下选择器:
EXAMPLE[notation=decimal] {/*default property settings*/}
EXAMPLE[notation=octal] {/*other settings*/}
第一个选择器可能并不会匹配到notation属性被默认设置的元素,也就是说没有显示设置notation属性的元素。为了覆盖所有场景,默认属性值的选择器必须删除:
EXAMPLE {/*default property settings*/}
EXAMPLE[notation=octal] {/*other settings*/}
这里,对于notation属性值为"octal"的元素,因为EXAMPLE[notation=octal]选择器比类型原则器更加特别,第二个选择器中申明的样式将会覆盖第一个选择器中的样式。有一点需要铭记,所有仅仅应用于默认场景的样式都将会被非默认场景的样式覆盖。
8.3 类选择器
在html中,我们使用.号替代~=来表示class属性。
例如,我们给所有的class属性包含pastoral的元素使用如下样式:
*.pastoral {color: green} /*all element with class~=pastoral*/
或者直接写为
.pastral {color: green}
如下选择器只给class属性中包含pastoral的H1元素使用样式:
H1.pastoral {color: green} /*H1 element with class~=pastoral*/
也就是说如下代码中,只有第二个H1元素的文本将会是绿色的。
<H1>Not green</H1>
<H1 class="pastoral">Very green</H1>
类选择器也可以用来匹配class属性值的子集,如下:
p.marine.pastoral {color: green}
如下将会选中class属性值中包含有marine和pastoral的P元素。也就是说上面这条规则将会匹配到class="pastoral blue aqua marine"的P元素,但是不会匹配到class="pastoral blue"的P元素。
css让元素的class属性变得非常强大,开发者可以通过给元素的class属性指定样式属性来定义自己的"文档语言",例如可以使用SPAN表示DIV。我们应该避免这样的做法,因为html中结构化的元素有着特定的含义,而开发者自定的样式类却没有。
9. ID选择器
在html中,所有的元素都可以通过ID来唯一表示。ID选择器由"#"加元素的ID属性值组成。
如下的ID选择器选中ID属性值为"chapter1"的H1元素:
h1#chapter1 {text-align: center}
下面的这段代码中,选择器将会匹配ID属性值为"z98y"的元素:
<HEAD>
<TITLE>Match P</TITLE>
<STYLE type="text/css">
*#z98y {letter-spacing: 0.3em}
</STYLE>
</HEAD>
<BODY>
<P id="z98y">Wide text</p>
</BODY>
而在在下面的代码中,选择器将只会匹配到ID属性值为"z98y"的H1元素,而不会匹配到P元素:
<HEAD>
<TITLE>Match H1 only</TITLE>
<STYLE type="text/css">
H1#z98y { letter-spacing: 0.5em }
</STYLE>
</HEAD>
<BODY>
<P id=z98y>Wide text</P>
</BODY>
在html中,ID选择器的优先级比属性选择器的优先级高,例如选择器#p123将比选择器[id=p123]的优先级高。
10. 伪元素和伪类
在CSS2.1中,样式通常应用于文档树中的某个元素。这种简单的模型可以满足大多数场景的需要,但是在某些特殊场景中,这样的模型就不能满足需要了。例如,在HTML4中,没有元素可以表示段落的第一行,因此没有选择器可以选中段落的第一行。
CSS引入伪类和伪元素的概念来给文档树以外的信息添加样式。
- 伪元素提供了对文档树除了文档语言定义的内容以外的抽象。举个例子,文档语言并没有提供可以访问元素内容第一个字母或者第一行的机制。CSS伪元素给样式开发者提供了这样的方式。伪元素甚至可以让样式开发者给文档树中不存在的内容添加样式(:before和:after伪元素可以用来创建内容)。
- 伪类按照元素的特征来将元素分类,而不是按照元素的名称、属性或者内容。大体上来说,这些特征不能从文档树中推导出来。比如,有些伪类是动态的,也就是说一个元素可能根据用户与文档的互动而获得或者失去一个伪类。然而,也有例外的情况,"first-child"和":lang()"均可以从文档树中推导出。
伪类和伪元素既不出现在文档源代码中,也不出现在文档树中。
伪类可以出现在任何选择器中,而伪元素只能追加在选择器的最后一个单个简单选择器后面。
伪类和伪元素的名字大小写敏感。
11. 伪类
11.1 :first-child伪类
:first-child伪类匹配那些是其父元素的第一个子元素的元素。
如下的例子中,选择器匹配任何一个是DIV元素的第一个子元素的P元素。
div > p:first-child {text-index: 0}
在下面的代码片段中,上述选择器会选中DIV中的P元素:
<P> The last P before the note.
<DIV class="note">
<P> The first P inside the note.
</DIV>
但是不会匹配如下代码片段中的P元素:
<P> The last P before the note.
<DIV class="note">
<H2>Note</H2>
<P> The first P inside the note.
</DIV>
下面的选择器将设置P元素的后代EM元素的字体为粗体,前提是这个P元素是其父元素的第一个子元素。
p:first-child em {font-weight: bold}
注意: 因为匿名盒子并不是文档树的一部分,因此当计算第一个子元素的时候,并不会计算匿名盒子。如下面的:
<P>abc<EM>default</EM></P>
EM元素是P的第一个子元素。
下面的这两个选择器是等价的:
* > a:first-child /*A is first child of any element*/
a:first-child /*Same*/
11.2链接伪类: :link 和 :visited
UA通常要区别显示未访问的链接和已访问的链接。CSS提供":link"和":visited"来区别这两种状态:
- :link 应用于没有被访问的链接
- :visited 应用于已经别访问的链接
这两种状态互斥。
文档语言决定了什么元素是超链接。比如,在html4中,链接的伪类应用于有"href"属性的A元素。因此,如下CSS2.1的申明有相同的效果:
a:link {color: red}
:link {color: red}
如果下面的链接:
<A class="external" href="http://out.side/">external link</A>
被访问过,那么,
a.external:visited { color: blue }
将会将它变成蓝色。
11.3 动态伪类: :hover,:active和:focus
可交互的UA有时候会根据用户的动作来改变显示状态。CSS提供三个伪类:
- :hover 应用于用户(通过某些指针设备)指定某个元素但没有激活该元素时。例如,用户将光标(鼠标指针)悬停在元素的盒子上时。
- :active 应用于被用户激活了的元素。如用户点击鼠标之后,放开鼠标之前。
- :focus 应用于当元素获得焦点时。
一个元素可以同时被多个伪类选中。
CSS2.1未定义正在:active或者:hover状态的父元素,其子元素是否也在那个状态。
UA并不被强制要求根据伪类的变化来刷新当前显示的文档流。比如,样式表中指定:active的链接的字号要比未被激活的链接的字号大,但是这样可能导致文字改变位置,因此UA可能会忽视这样的规则。
a:link { color: red } /* unvisited links */
a:visited { color: blue } /* visited links */
a:hover { color: yellow } /* user hovers */
a:active { color: lime } /* active links */
注意,A:hover必须要放在A:link和A:visited之后,否则样式层叠规则将会隐藏A:hover的'color'属性。同样的,A:active在A:hover之后,当用户激活并悬停在A元素上时,A的颜色将会是lime。
混合使用动态伪类:
a:focus { background: yellow }
a:focus:hover { background: white }
最后一个选择器将会选中在:focus和:hover状态的A元素。
11.4语言伪类: :lang
如果文档语言指定了如何检测元素使用的人类语言,那么就可以通过css选择器来选中这个元素。例如,在html中,元素的语言通过lang属性、META元素或者来自协议中的信息(如HTTP的头)来确定。
":lang(C)"伪类匹配语言为C的元素。匹配模式与"|="相同,即元素的lang属性值与C相同,或者是以C加"-"开头的字符串。在进行语言匹配的时候,字符大小写敏感且只能在ASCII范围内。C不强制要求是一个有效的语言名,但不能为空。
下面的选择器当语言为加拿大法语或德语是设置HTML文档中的引用标记:
html:lang(fr-ca) { quotes: '« ' ' »' }
html:lang(de) { quotes: '»' '«' '\2039' '\203A' }
:lang(fr) > Q { quotes: '« ' ' »' }
:lang(de) > Q { quotes: '»' '«' '\2039' '\203A' }
注意选择器[lang|=xx]和:lang(xx)的不同。如下HTML片段中,只有BODY会被[lang|=fr]选中(因为它有lang属性),但是BODY和P都将被:lang(fr)选中(因为他们都是法语)。
<body lang=fr> <p>Je suis Français.</p> </body>
12.伪元素
伪元素与真实的元素在CSS中表现几乎一样,但是也有一些不同。
12.1 :first-line
:first-line伪元素给段落内容的第一行添加指定的样式。如:
p:first-line { text-transform: uppercase }
表示将所有段落的第一行全部变成大写。但是,选择器"P:first-line"不会匹配任何真实的HTML元素,它将匹配一个伪元素,这个伪元素由UA在每个段落的开头插入。
注意一点,第一行的长度由很多因素决定,包括页面的宽度,字号等。因此,像下面的这一段HTML段落:
<P>This is a somewhat long HTML
paragraph that will be broken into several
lines. The first line will be identified
by a fictional tag sequence. The other lines
will be treated as ordinary lines in the
paragraph.</P>
有可能被像下面这样分行:
THIS IS A SOMEWHAT LONG HTML PARAGRAPH THAT
will be broken into several lines. The first
line will be identified by a fictional tag
sequence. The other lines will be treated as
ordinary lines in the paragraph.
之所以如此显示,是因为UA为:first-line添加了虚拟标签序列(fictional tag sequence)。
<P><P:first-line> This is a somewhat long HTML
paragraph that </P:first-line> will be broken into several
lines. The first line will be identified
by a fictional tag sequence. The other lines
will be treated as ordinary lines in the
paragraph.</P>
如果伪元素截断了真实的元素,那么期望的效果是虚拟标签序列关闭这个元素然后重新开启它。例如,上面的代码中我们增加一个SPAN元素:
<P><SPAN class="test"> This is a somewhat long HTML
paragraph that will be broken into several
lines.</SPAN> The first line will be identified
by a fictional tag sequence. The other lines
will be treated as ordinary lines in the
paragraph.</P>
UA在给:first-line插入虚拟标签序列的时候就可以给SPAN模拟一个开始和结束标签。
<P><P:first-line><SPAN class="test"> This is a
somewhat long HTML
paragraph that will </SPAN></P:first-line><SPAN class="test"> be
broken into several
lines.</SPAN> The first line will be identified
by a fictional tag sequence. The other lines
will be treated as ordinary lines in the
paragraph.</P>
:first-line伪元素只能应用于块容器元素。
元素的"第一个格式化行"可能是他在同一个流中的块级后代的"第一个格式化行",什么是同一个流中的块级后代,也就是,元素的没有浮动也没有设置定位的块级后代。例如:
<DIV><P>This line...</P></DIV>
中DIV的第一行就是P的第一行(假定P和DIV都是块级元素)
table-cell或者是inline-block的第一行不能是他们祖先元素的第一个格式化行。因此,在
<DIV><P STYLE="display: inline-block">Hello<BR>Goodbye</P> etcetera</DIV>
中,DIV第一个格式化行不是"Hello"。
在如下代码片段中
<p><br>First...
P的第一行不包含任何字符(使用HTML4中定义的BR默认样式)。"First"不是第一个格式化行。
:first-line伪元素与行内元素(inline)很像,但是也有一些限制。CSS2.1中规定,这些css属性可以应用于:first-line伪类: 字体属性(font properties)、颜色属性(color property)、背景属性(background properties)、"word-spacing"、"letter-spacing"、"text-decoration"、"text-transform"和"line-height"。UA也可以对其应用其他css样式属性。
12.2 :first-letter 伪元素
如果一个块级元素的第一行没有被其他内容(如行内表格或者图片)占据的话看,:first-letter伪元素必须选中第一行的第一个字母。:first-letter伪元素通常被用于"首字母大写"和"首字母下沉"。
这些属性可以被应用于:first-letter伪元素:字体属性(font properties)、"text-decoration"、"text-transform"、"letter-spacing"、"word-spacing"(在合适的时候)、"line-height"、"float"、"vertical-align"(仅当float为none时)、外边距属性(margin properties)、内边距属性(padding properties)、边框属性(border properties)、颜色属性(color property)和背景属性(background properties)。UA也可以应用其他的样式属性。
下面的这个例子展示了首字母大写的一种可能的实现效果。注意,first-letter伪元素继承的"line-height"是1.1,但是UA给首字母计算了一个不同的值来去除前两行间不必要的间距。同时也注意,第一个字母的虚拟开始标签在SPAN中,因此首字母的font weight是normal,不会SPAN的bold。
p { line-height: 1.1 }
p:first-letter { font-size: 3em; font-weight: normal }
span { font-weight: bold }
...
<p><span>Het hemelsche</span> gerecht heeft zich ten lange lesten<br>
Erbarremt over my en mijn benaeuwde vesten<br>
En arme burgery, en op mijn volcx gebed<br>
En dagelix geschrey de bange stad ontzet.
下面这段代码设置了首字母大写和跨越了两行的首字母下沉。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<HTML>
<HEAD>
<TITLE>Drop cap initial letter</TITLE>
<STYLE type="text/css">
P { font-size: 12pt; line-height: 1.2 }
P:first-letter { font-size: 200%; font-style: italic;
font-weight: bold; float: left }
SPAN { text-transform: uppercase }
</STYLE>
</HEAD>
<BODY>
<P><SPAN>The first</SPAN> few words of an article
in The Economist.</P>
</BODY>
</HTML>
UA添加的虚拟标签序列如下:
<P>
<SPAN>
<P:first-letter>
T
</P:first-letter>he first
</SPAN>
few words of an article in the Economist.
</P>
注意,:first-letter伪元素是紧随元素的内容的,而:first-line伪元素的开始标签是被插入到块级元素的开始标签之后的。
:first-letter伪元素也应用于纯数字,如"67 million dollars is a lot of money"中的"6"。
:first-letter伪元素仅用于块容器元素。
:first-letter伪元素可被用于所有包含文本或者在相同流中的后代包含文本的元素中。UA应该把:first-letter伪元素的虚拟开始标签放在元素的第一个文本之前,即使这个第一个文本在元素的后代中。
如下代码片段的虚拟标签序列:
<div>
<p>The first text.
是
<div>
<p><div:first-letter><p:first-letter>T</...></...>he first text.
table-cell或行内元素的首字母不能是其祖先元素的首字母。因此,在
<DIV><P STYLE="display: inline-block">Hello<BR>Goodbye</P> etcetera</DIV>
中DIV的首字母不是"H"。实际上,DIV没有首字母。
首字母必须在第一个格式化行中。例如: 在片段
<p><br>First...
中,第一行不包含任何字母,因此":first-letter"不匹配任何东西(使用BR在HTML4中的默认样式)。实际上,":first-letter"不匹配"First"中的"F"。
下面的例子展示了重叠的伪元素如何互相影响。每个P元素的首字母将是绿色的并且字号为24pt。第一行剩余的内容将会使蓝色的,而段落剩余的内容则为红色。
p { color: red; font-size: 12pt }
p:first-letter { color: green; font-size: 200% }
p:first-line { color: blue }
<P>Some text that ends up on two lines</P>
假设在"end"之前换行,那么代码段的虚拟标签列表为:
<P>
<P:first-line>
<P:first-letter>
S
</P:first-letter>ome text that
</P:first-line>
ends up on two lines
</P>
注意,:first-letter元素在:first-line元素内,:first-line的css属性会被:first-letter继承,但是会被:first-letter中的同名属性覆盖。
12.3 :before 和 :after伪元素
:before 和 :after伪元素可以被用来在元素内容的前后插入一些生成内容,通常与content属性一同使用。
h1:before {content: counter(chapno, upper-roman) ". "}
例如,下面的选择器会在每个class属性值包含note的P元素的内容之前插入一个字符串"Note: "。
p.note:before { content: "Note: " }
元素盒子包含生成的内容,因此修改一下上面的选择器,
p.note:before { content: "Note: " }
p.note { border: solid green }
将会导致每个段落都包裹在绿色实线边框之中,包括插入的生成字符串。
:before和:after伪类将会继承所有可以被继承的元素的属性。
举个例子,下面这个选择器将会给每个Q元素内容之前插入一个开引号。引号的颜色是红色的,但是字体将会与Q元素的字体保持一致。
q:before {
content: open-quote;
color: red
}
在:before或者:after的声明中,没有显示声明也没有继承的属性将使用初始值。
因此,在上面的例子中,因为display属性的初始值为inline,所以引号将作为一个行内盒子(也就是说跟Q元素的内容在同一行)被插入到Q元素内容之前。 当我们设置了display属性为block之后,被插入的内容就变成了块级元素,例如:
body:after {
content: "The End";
display: block;
margin-top: 2em;
text-align: center;
}
:before和:after伪元素在与其他元素盒子相互作用的时候,表现得就像他们就是真正的元素一样,只不过被放进了绑定他们的那个元素盒子里面。
举个例子,如下的html代码片段和样式表,
<p> Text </p> p:before { display: block; content: 'Some'; }
将与下面的html片段展现的一模一样。
<p><span>Some</span> Text </p> span { display: block }
所以,当:first-letter和:first-line伪元素被应用于包含由:before和:after生成的内容的元素时,他们应用于包括生成内容在内的元素内容的第一个字母或第一行。
p.special:before {content: "Special! "}
p.special:first-letter {color: #ffd800}
上面将"Special"的"S"显示成金色的。