在前一章中,您看到了 CSS 中最常见的选择器类型的简要回顾。这三种类型被称为类选择器、标识选择器和后代选择器,在 CSS 版本 3 之前都是可用的。现在 CSS3 已经出现,它带来了许多新的选择器作为新功能的一部分。
在这一章中,我们不会介绍每一个新的选择器,但是您将很快看到现在可以以您可能从未想象过的方式瞄准您的 HTML 元素的绝对灵活性。
这些新的选择器中有许多是以伪选择器的形式实现的(选择器是另一个选择器的扩展,比如 hover),你可以很容易地写一本关于选择器的书。
在本章中,我们将只关注最重要的可用的新选择器,以及它们的使用的基本演示。至于其他,我强烈建议你阅读互联网上的许多优秀资源。除了我已经提到的其他链接之外,纯粹从 CSS 主题开始的一个很好的地方是 Mozilla 开发者网络上的 CSS 页面。关于 MDN 的便利之处在于兼容性指南,它会告诉你哪些选择器与哪些浏览器一起工作,哪些会给你带来问题。
目标所有选择器完全按照它所说的去做:它在它运行的空间中尽可能地瞄准一切。
这意味着,如果你把它放在一个基于标识的选择器中,那么在该标识选择器控制下的所有东西都会受到影响。
使用*符号可以直接告诉你,它的行为就像一个通配符,尽可能贪婪,尽可能多的选择。起初,这似乎是一件很棒的事情,直到你突然意识到在移动设备上,或者在有数百万嵌套元素的页面上,它会让你的页面变慢多少。
通用选择器主要设计用于作为初始设置的一部分进行全面的更改,例如将整个页面的 CSS 模型设置为一组已知的默认值(如 0 像素边距和无边框)。
通常在 CSS 重置中使用,以在已知状态下开始。许多著名的前端用户界面框架广泛使用它,在应用它们可能提供的任何自定义样式之前,将您的页面置于已知状态。
通用选择器应该谨慎使用,其影响范围应该尽可能小,以免造成任何严重的性能瓶颈。
如果您想确保文档中每个元素的边距和填充都设置为0
,那么您可以应用这个选择器,如代码清单 13 所示。
*
{
margin: 0;
padding: 0;
}
代码清单 13:使用通用选择器
将它包含在样式文件顶部附近的某个位置后,您可以确保在它之后,没有任何规则会应用任何边距或填充。
理解起来比较简单的选择器之一是,相邻的选择器选择紧挨着的元素,并且每次只选择一次。
假设您有以下 HTML 标记:
<div>
<h1>Heading</h1>
<p>Text line 1</p>
<p>Text line 2</p>
<h1>Heading</h1>
<p>Text line 1</p>
<p>Text line 2</p>
</div>
代码清单 14a:演示相邻选择器的 HTML 标记
如果您随后对此标记应用了以下 CSS 规则:
h1 + p
{
color: red;
}
代码清单 14b:演示相邻选择器的 CSS 规则
只有紧跟在h1
元素之后的p
元素会变成红色。文本的第二行(以及随后的任何其他行)将保持不变,就像h1
元素本身一样,如图 6 所示:
图 6:代码清单 14 的预期结果
相邻的同级选择器最有用的地方之一是创建前导或开头段落。考虑下面的例子。
<!doctype html>
<html>
<head>
<title>Basic HTML 5 document</title>
<link href="styles.css" rel="Stylesheet" type="text/css" />
</head>
<body>
<section>
<article>
<h1>Monstrous Inbox</h1>
<p>Today in local news, a monstrous email inbox was discovered belonging to Mr Jones of durham.</p>
<p>The email inbox was ruomoured to have 11ty billion unread emails in it, but scientists say that calculating the actual amount is going to be almost impossible.</p>
<p>Mr. Jones had no comment, but he did let slip that he had no idea how his inbox grew so large and so fast.</p>
</article>
<article>
<h1>Jelly Table Legs</h1>
<p>Furniture Watchdogs are today warning all people to be on the lookout for tables with Jelly legs.</p>
<p>In recent months there have been a spate of fake tables released into the global furniture market, these fake tables are exceptionally dangerous and could wobble over at any moment.</p>
<p>People should exercise extreme caution when buying new tables, if you’re unsure call a professional table tester to ensure you’re getting a genuine product.</p>
</article>
</section>
</body>
</html>
代码清单 15:假新闻清单
清单 15 中的代码表示一组新闻文章。我已经标记了代码,以便它使用 HTML 5 中的新语义标签来帮助定义文档结构和布局。请注意,您不需要键入在<p>
元素中找到的所有文本;我已经包括了大量的只是为了演示。
如果您将输出渲染为没有任何样式,您将得到类似如下的结果:
图 7:代码清单 15 生成的无样式新闻文章示例
在相邻兄弟选择器的帮助下,以及后代选择器的一些帮助下,我们可以使用一组非常少的样式规则轻松地为这个输出设置样式。
下面的 CSS 代码展示了我们如何使用相邻的兄弟选择器来确保我们的第一段在每篇文章的其余部分中脱颖而出。
section
{
font-family: sans-serif;
font-size: 12pt;
}
section h1
{
color: green;
border-bottom: 1px solid green;
font-size: 15pt;
}
section p
{
color: grey;
}
section h1 + p
{
color: black;
font-size: 14pt;
font-style: italic;
}
代码清单 16:为代码清单 15 中的新闻文章设置样式的 CSS 代码
如代码清单 16 所示,使用的 CSS 非常简单,更重要的是,它是由section
标签驱动的,这意味着任何不在包含部分标签内的标题或段落组合仍然可以安全地使用,并且不会被更改以匹配清单 16 中的样式。
如果我们用新的样式刷新 HTML 输出,并做一些文本编辑,我们现在应该会看到如下内容:
图 8:应用了代码清单 16 中 CSS 规则的代码清单 15
正如您已经看到的,后代选择器允许您在标记的元素链中定位子元素。
例如,如果你想以类类型为news
的div
中的段落标签为目标,那么div.news p
的选择器将完成这项工作。然而,有一个缺点:后代选择器是贪婪的。
当我们说一个选择器是贪婪的,我们的意思是它会尽可能多地尝试匹配(任何有正则表达式经验的人都会知道我在说什么)。
这在实践中意味着,不仅第一级选择的元素会被选中,而且任何低于第一级选择的元素也会被选中。
直接子选择器解决了这个问题,因为它不那么贪婪,并且不超过根选择器下面的第一级。
请考虑以下 HTML 标记:
<div class="test">
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<div>
<p>Paragraph 3</p>
</div>
<p>Paragraph 4</p>
</div>
代码清单 17:演示直接子选择器的 HTML 标记
如果我们在浏览器中呈现代码清单 17,我们将得到四个简单的段落元素。
图 9:使用代码清单 17 呈现的 HTML
您将从标记中注意到,第 3 段位于嵌套的div
元素中;如果我们使用标准的后代选择器来设计这个块的样式,我们将得到以下输出:
div.test p
{
color: red;
}
代码清单 18:使用后代选择器为清单 17 中的代码设置样式的 CSS 规则
图 10:将代码清单 17 中的 HTML 与代码清单 18 中的规则相结合时产生的输出
乍一看,这个输出可能看起来还行,一般来说,实际上可能是您想要实现的。然而,考虑一下这个问题:为什么第 3 段被嵌入到另一个div
元素中?
它可能是以这种方式创建的,目的是使它的样式不同于块中的其他段落元素。
让我们更改我们的规则,因此它使用直接子选择器,如下所示:
div.test > p
{
color: red;
}
代码清单 19: CSS 规则使用直接子选择器为代码清单 17 中的代码设置样式
现在,您应该看到它跳过了嵌套元素中的段落,但继续按照预期设置其他段落的样式。
图 11:将清单 17 中的 HTML 与清单 19 中的规则相结合时产生的输出
这在创建弹出菜单之类的东西时非常有用,在弹出菜单中,您可能希望所有顶级元素呈现未选择的外观,但是当父元素被选择时,形成子菜单的嵌套项需要单独保留。
通用同级选择器,像它的近亲相邻同级选择器一样,用于选择跟随给定类型的父元素的元素。但是,与它的同类不同,它试图在标记层次结构中选择多个匹配项。
相邻的同级选择器最多只能选择给定匹配的第一个匹配;但是,只要层次结构继续匹配,通用同级选择器就会尝试选择匹配项。
如果我们构建两个无序列表,如下所示:
<p>List 1</p>
<ul>
<li><a href="#">Link 1a</a></li>
<li><a href="#">Link 2a</a></li>
<li><a href="#">Link 3a</a></li>
<li><a href="#">Link 4a</a></li>
</ul>
<ul>
<li><a href="#">Link 1b</a></li>
<li><a href="#">Link 2b</a></li>
<li><a href="#">Link 3b</a></li>
<li><a href="#">Link 4b</a></li>
</ul>
<p>List 2</p>
<ul>
<li><a href="#">Link 1a</a></li>
<li><a href="#">Link 2a</a></li>
<li><a href="#">Link 3a</a></li>
<li><a href="#">Link 4a</a></li>
</ul>
<ul>
<li><a href="#">Link 1b</a></li>
<li><a href="#">Link 2b</a></li>
<li><a href="#">Link 3b</a></li>
<li><a href="#">Link 4b</a></li>
</ul>
代码清单 20:演示同级选择器的 HTML 标记
与其余示例一样,如果我们首先不使用样式渲染,我们将获得默认的标准渲染:
图 12:代码清单 20 中的 HTML 生成的输出,没有应用样式规则
为了说明不同之处,我们首先要使用前面看到的相邻的同级选择器,使用以下规则来设置列表的样式。
p + ul a
{
color: red;
}
代码清单 21:代码清单 20 的邻接选择器
当我们使用代码清单 21 中的规则呈现代码清单 20 时,我们得到了以下输出:
图 13:应用了邻接选择器的代码清单 20 产生的输出
如果你一直遵循代码,这不应该是一个惊喜。第一组是风格化的,第二组不是。
但是,如果我们现在将代码切换为使用通用同级选择器:
p ~ ul a
{
color: red;
}
代码清单 22:代码清单 20 的兄弟选择器
我们应该看到选择的不同:
图 14:应用了兄弟选择器的代码清单 20 产生的输出
当相邻的同级选择器在第一个匹配处停止时,一般同级选择器不停止;通用同级选择器将继续向下,匹配规则中的任何内容,只要它之前的元素匹配。
在这一点上,你可能想知道为什么我们有两种方法来实现明显相同的事情。重要的是要记住,虽然我们在本章中已经设计了类似的场景,但是我们是以完全不同的方式完成的。
当我们谈论彼此相邻的元素时,我们的意思是这些元素相当像房子街道上的邻居。如果你沿着每栋房子的路线画一条线,你会发现所有的东西都在同一水平线上。在下面的代码中,每个具有house
属性的div
元素彼此相邻:
<div class="street">
<div class="house"></div>
<div class="house"></div>
<div class="house"></div>
<div class="house"></div>
<div class="house"></div>
</div>
代码清单 23:相邻元素
当我们谈论元素是彼此的后代时,我们谈论的是类似父母对孩子的关系。如果我们将此应用于代码清单 23 中的街道示例,我们可能会有“租户”占据每栋房子。
<div class="street">
<div class="house"><p class="tenant"></p></div>
<div class="house"><p class="tenant"></p></div>
<div class="house"><p class="tenant"></p></div>
<div class="house"><p class="tenant"></p></div>
<div class="house"><p class="tenant"></p></div>
</div>
代码清单 24:后代元素
正如您在代码清单 24 中看到的,我们现在在每个房子里都有一个租户。房客彼此不相邻,因为你必须离开房子,向下移动,然后进入下一个房子才能到达下一个房客。
然而,既然你可以从一所房子里找到房客,那么房客就成了房子的后代。
子代和孙代术语被定义为后代的特殊情况。就像在家谱中一样,我们例子中的租户可能有孩子,这些孩子可能已经长大并有了自己的孩子。
孩子的孩子仍然是房客的后代,但不是房客的孩子;这将是一个孙子,类似于以下内容:
<div class="street">
<div class="house">
<div class="tenant">
<div class="tenantsChild">
<div class="childsChildButTenantsGrandChild"></div>
</div>
</div>
</div>
</div>
代码清单 25:显示子代与依赖关系的代码
如果你要创造更多的div
元素,在租户的范围内有一类tenantsChild
相邻,那些tenantsChild
元素,就像房子一样,会彼此相邻。
这需要一点点练习,直到你养成这样思考的习惯,但是如果你把 HTML 的原生形式看作是一种专业的、基于 XML 的语言,那么你会发现嵌套开始有意义了。一旦嵌套有了意义,选择器的流程和布局也开始就位。
如果你很难马上理解它,不要担心。我也花了很长时间来解开这一切,即使是现在,有时我仍然会弄错。
到目前为止,我们已经介绍了主要的基本选择器。如果你想阅读其余的内容,你可以在 W3C 网站上找到官方文档。
不过,我确实认为应该发出警告。虽然 W3C 官方文档是所有 HTML 的官方来源,但它也是一个很难阅读的网站。
还有更容易的信息来源,比如MozillaHTML Dog。我强烈建议您先消化这些内容,一旦您对该主题有了更好的理解,请访问 W3C 网站。
为了结束这一章,我们将看看在 CSS3 中被大量扩展的东西:属性选择器。
在以前的版本中,引入这种类型选择器的一个非常小的子集的努力很少,但是它在很大程度上没有被注意到,所以大多数开发人员从未停下来看看它能提供什么。
然而在 CSS3 中,属性选择器是全能的,并且允许你做一些相当聪明的元素样式。
首先,我们需要定义什么是属性选择器。
与您目前看到的选择器不同,属性选择器不是位于两个目标之间的单个字符结构;它是父规则的多字符附件,包含自己的内部处理规则。
简而言之,属性选择器有点像拥有一组定制的搜索表达式,特别是用于根据附加到元素上的属性值(或部分属性值)来搜索元素。
看看下面的 HTML 标记。
<ul>
<li><a href="#" rel="external">External Link 1</a></li>
<li><a href="#" rel="internal">Internal Link 1</a></li>
<li><a href="#" rel="internal">Internal Link 2</a></li>
<li><a href="#" rel="external">External Link 2</a></li>
<li><a href="#" rel="external">External Link 3</a></li>
</ul>
代码清单 26:带有自定义属性的 HTML 标记
在代码清单 26 中,您可以看到我已经创建了一个相当标准的无序列表,其中包含带有锚元素的项目。这没什么不寻常的,也是你经常会在 HTML 文档的导航链接中看到的。
然而,我所做的是使用rel
属性来标记链接的关系,指定它是外部链接还是内部链接。
如果我们想用不同的方式设计这两种不同的类型,理论上我们可以添加一个类,比如.internal
或者.external
。但是,有时 HTML 标记是在您无法控制的进程中生成的,因此您可能无法以这种方式向输出中添加类。
这就是属性选择器的作用。您可以使用它来定位元素中的特定命名属性,正如您稍后将看到的,您不仅可以使用它来定位整个字符串,还可以定位字符串的一部分。
首先,让我们看看如何将它应用到前面的链接示例中。将以下样式规则添加到样式表中(假设您已经将前面的 HTML 添加到文档中):
a[rel="external"]
{
color: red;
}
a[rel="internal"]
{
color: green;
}
代码清单 27:代码清单 26 的属性选择器规则
如果在浏览器中渲染,您应该会看到如下内容:
图 15:代码清单 26,其中应用了代码清单 27 中的 CSS 规则
仅仅通过使用两种不同的规则,我们就很容易地锁定了两种不同类型的链接,仅仅是因为rel
属性的不同。
一个真实的例子可能看起来像下面这样;将清单 26 中的代码修改如下:
<ul>
<li><a href="#">Link 1</a></li>
<li><a>Link 2</a></li>
<li><a>Link 3</a></li>
<li><a href="#">Link 4</a></li>
<li><a href="#">Link 5</a></li>
</ul>
代码清单 28:从代码清单 26 修改而来的 HTML 标记
默认情况下,大多数浏览器不会使没有href
属性的链接成为可点击的,至少对于默认样式,它们不会使用通常的蓝色下划线。
图 16:没有样式的代码清单 28 的输出。
然而,在许多情况下(尤其是如果您使用更大的框架),您可能会发现锚标签通常被赋予了统一的样式,以保持一致的外观和感觉。
例如:
a
{
text-decoration: none;
color: green;
}
代码清单 29:给主播一个统一的外观和感觉的可能规则
这段代码可能用于应用统一的样式,并使我们的列表如下所示:
图 17:应用于锚元素的统一样式的代码清单 28
您可以立即看到,您丢失了任何可能提示您链接没有目的地的上下文信息,作为站点用户,您不知道哪些链接是好的,哪些是坏的。
你可以遍历你的代码,给每一个缺少href
的链接添加一个class="nodestination"
,但是如果有人给数据库添加了链接,并且这个链接突然有了一个有效的目的地呢?在这种情况下,你面临的任务是找到所有没有href
的链接,现在找到了,所以你可以删除类名并恢复正常。
解决这个问题的另一种方法是只使用属性选择器,本身没有额外的内容,如下所示:
a[href]
{
text-decoration: none;
color: green;
}
代码清单 30:仅针对具有 href 属性的锚的属性规则
当我们现在刷新页面时,我们应该看到我们现在得到了与以前不同的样式:
图 18:统一样式的代码清单 28,仅适用于具有有效目的地的锚点
现在,为了让链接样式正确,您所要做的就是让您的 web 应用程序平台生成具有href
属性的链接,并将href
属性从任何还没有链接的东西中去掉。
然而,还有一个问题。
如果我们也想对没有href
属性的剩余元素进行样式化,例如,使它们的颜色更浅,这样它们看起来可能会被禁用,该怎么办?
CSS3 也用伪选择器覆盖了这一点。我们将在下一章中了解更多关于新的伪选择器的信息;目前,我们可以通过使用以下属性/伪样式规则来非常简单地解决缺少href
属性的锚点标签的样式问题:
a:not([href])
{
text-decoration: none;
color: silver;
}
代码清单 31:属性规则只针对没有 href 属性的锚
如果我们在更改样式规则以匹配代码清单 31 之后重新呈现文档,我们应该会得到以下结果:
图 19:带有反向属性样式的代码清单 28
以下规则利用了 CSS 的级联特性,并在逻辑上结合了一些东西:
a
{
text-decoration: none;
}
a[href]
{
color: green;
}
a:not([href])
{
color: silver;
}
代码清单 32:自动为缺少 href 属性的链接设置样式的组合规则
理论上,我们实际上可以进一步减少,只需将绿色添加到基本锚点规则中。这将应用绿色作为默认值,但如果不存在href
,则将其更改为银色。这里的想法只是用最简单的语言来展示正在发生的事情,将事情展开通常会使概念更加清晰易懂。
如果我们现在渲染它,我们应该会看到如下内容:
图 20:应用了组合样式的代码清单 28
从这个例子中得到的主要好处是,您现在不再需要担心确保特定的类被应用于给定的链接,这使得文档的维护变得更加容易并且不容易出错。如果你运行的是一个内容管理系统,比如翁布里克或 WordPress,那么你所要关心的就是链接是否有有效的目的地。
| | 注意:我以前在普通段落文本中以这种方式使用过属性选择器,一旦链接变为活动状态,它会自动突出显示与普通文本一致的链接,并使它们在不活动时看起来与段落文本完全一样。 |
我之前提到属性选择器也可以匹配给定属性的部分内容。主选择器增加了一些功能,允许您检查属性值是以给定的子字符串开头、结尾还是包含给定的子字符串。
这在外部和内部链接的情况下非常有用。
回想一下我们在代码清单 26 中看到的代码;我们使用rel
属性来定义锚元素上的内部或外部关系。
就 W3C 规范而言,这可能不是最好的方法,如果使用 HTML 5 验证工具进行测试,HTML 很可能无法通过验证。
然而,这并不意味着我们必须回到使用类属性。
想象一下,你所有的外部链接都有一个完全合格的网址,而你所有的内部链接只有一个相对的页面。在这种情况下,代码清单 26 现在可以重写如下:
<ul>
<li><a href="http://external.com/link1">External Link 1</a></li>
<li><a href="internal/link1">Internal Link 1</a></li>
<li><a href="internal/link2">Internal Link 2</a></li>
<li><a href="http://external.com/link2">External Link 2</a></li>
<li><a href="http://external.com/link3">External Link 3</a></li>
</ul>
代码清单 33:代码清单 26 被重写为不使用 rel 属性
如果你检查href
属性的不同,你会发现所有的外部链接都以“http://”开头,而内部链接没有。
如果我们使用“属性值从”选择器,我们现在可以很容易地用不同的颜色来设计外部的样式。
一般来说,我们可以为锚点标签设置一个统一的样式,默认情况下,它会反映内部链接的样式,如果反映了外部链接,则可以对其进行修改。
要使用“以属性开头”选择器,我们在属性名称和值之间指定一个^=
,如下规则所示:
a[href^="http:"]
{
color: red;
}
代码清单 34:改变以“http”开头的属性颜色的 CSS 样式规则
如果我们将它应用于代码清单 33 中的 HTML 标记,然后在浏览器中呈现它,我们应该会得到以下结果:
图 21:代码清单 33 中的 HTML,应用了代码清单 34 中的规则
现在,如果我们像以前一样添加一个统一的规则,这样默认情况下链接是绿色的,我们最终得到的输出如下所示:
图 22:最终样式就绪后的 HTML 代码清单 33
我相信你可以很快意识到这可以很容易地与规则相结合来检测属性的存在,并在没有href
属性的链接上做出进一步的选择。
属性选择器的其余部分是“以结尾”和“包含”选择器。
如果您想根据文件类型设置链接的样式,“以”结尾会很方便。例如,您可以执行以下操作:
<ul>
<li><a href="photo1.png">Photo 1</a></li>
<li><a href="photo2.png">Photo 2</a></li>
<li><a href="sound1.mp3">Sound 1</a></li>
<li><a href="sound2.mp3">Sound 2</a></li>
<li><a href="document.pdf">Document</a></li>
</ul>
代码清单 35:代码清单 33 被重写以列出文件链接
然后,使用$=
属性选择器创建以下规则,如下所示:
a[href$=".png"]
{
color: red;
}
a[href$=".mp3"]
{
color: green;
}
a[href$=".pdf"]
{
color: blue;
}
代码清单 36:以基于文件类型的属性规则结束
呈现时,它应该给出以下内容:
图 23:代码清单 35 显示了“以”属性选择器的输出
最后一个基于属性的选择器是 contains 选择器,它完全按照 tin 上说的做。
如果一个属性值包含给定的子串,那么 *= 将匹配它。
例如,看看下面的一组锚点标签:
<ul>
<li><a href="/admin/users/list">List Users</a></li>
<li><a href="/admin/personal/settings">Your Account Settings</a></li>
<li><a href="/admin/users/edit">Edit Users</a></li>
<li><a href="/admin/users/report">Users Report</a></li>
<li><a href="/admin/personal/profile">Your Profile</a></li>
</ul>
代码清单 37:代码清单 35 被重写以演示“包含”属性选择器
遵循以下规则:
a[href*="users"]
{
color: red;
}
a[href*="personal"]
{
color: green;
}
代码清单 38:包含基于子字符串的字符串属性规则
这将产生以下输出:
图 24:代码清单 37 产生的输出,应用了代码清单 38 中的规则
我们在这里讨论的内容甚至还没有触及潜在可用的表面,尤其是在属性选择器方面。
在 HTML 5 下,有一种新的属性类型叫做数据属性。这个新属性采用以下形式:
data-
后跟自定义名称,例如:
data-username
data-recordindex
您可以在任何元素上使用这些数据属性,用于任何目的,当与正确的选择器结合使用时,它们允许您使用 HTML 5 轻松地描述它自己的意图和风格。
在这一章中,我们对 CSS3 中添加的主要新选择器进行了相当深入的研究。我们已经看到,我们如何不再局限于如何将我们的元素定位于风格,我们如何不再有过去的限制,允许我们的规则几乎无限的表达。
在下一章中,我们将更仔细地研究新的伪选择器,当结合我们到目前为止所学的内容时,它将把我们拥有的功能提升到新的高度。