高级CSS选择器指南--第一部分

114 阅读12分钟

无论你是选择完全自己编写CSS,还是使用一个框架,或者被要求在一个设计系统中构建--理解选择器、级联和特异性对于开发CSS和修改现有的样式规则都是至关重要的。

你可能对基于ID、类和元素类型来创建CSS选择器相当熟悉。而且,你可能经常使用简陋的空格字符来选择子代。

在这个由两部分组成的迷你系列中,我们将探讨一些更高级的CSS选择器,以及何时使用它们的例子。

CSS的特殊性和级联

成功设置CSS选择器的一个关键概念是理解所谓的CSS特异性,以及CSS中的 "C",也就是级联。

特异性是应用于某个CSS声明的权重,由匹配选择器中每个选择器类型的数量决定。当多个声明具有相同的特异性时,在CSS中找到的最后一个声明将应用于该元素。特异性只适用于同一元素被多个声明锁定的情况。根据CSS规则,直接针对的元素将总是优先于元素从其祖先继承的规则。-MDN文档

正确使用级联和选择器的特异性意味着你应该能够完全避免在你的样式表中使用!important

增加特异性的结果是覆盖了从级联的继承性。

作为一个小例子--.item 会是什么颜色?

<div id="specific">
  <span class="item">Item</span>
</div>
#specific .item {
  color: red;
}

span.item {
  color: green;
}

.item {
  color: blue;
}

.item 将是红色的,因为在选择器中包括#id 的特异性战胜了级联和元素选择器。

这并不意味着要在你所有的元素和选择器中加入#ids ,而是要意识到它们对特异性的影响。

关键概念:特异性越高,越难覆盖规则。

每个项目在规则的可重用性方面都会有独特的需求。共享低特异性规则的愿望导致了CSS效用驱动框架的兴起,如Tailwind和Bulma。

另一方面,严格控制继承性和特异性的愿望,比如在设计系统内,使得像BEM这样的命名惯例很受欢迎。在这些系统中,一个父选择器与子选择器紧密耦合,以创建可重复使用的组件,创造自己的特定性泡沫。

如果你在想 "我不需要学习这些,因为我使用的是框架/设计系统",那么你在充分使用CSS方面就会受到很大的限制。

这种语言的美妙之处在于构建优雅的选择器,这些选择器足够的事情,并能使小的样式表整洁。

通用选择器

通用选择器(* )之所以被命名,是因为它普遍适用于所有元素。

曾经有人建议不要使用它,特别是作为一个后缀,因为性能方面的考虑,但这不再是一个有效的考虑。事实上,十多年来,这已经不是一个问题了。相反,你应该担心你的JS包的大小,并确保你的图像被优化,而不是为了性能原因而对CSS选择器进行微调😉。

一个更好的理由是,当它本身被使用时,它的特异性为零,这意味着它可以被一个id、class或元素选择器所覆盖。

通用选择器#的实际应用

重置CSS盒状模型

我最常见的用法是作为我的第一个CSS重置规则:

*,
*::before,
*::after {
  box-sizing: border-box;
}

这意味着我们希望所有的元素在计算盒子模型时都包括填充和边框,而不是这些宽度加入到任何定义的尺寸中。例如,在下面的规则中,.box ,宽度将是200px ,而不是200px + 20px ,因为有了填充物。

.box {
  width: 200px;
  padding: 10px;
}

垂直韵律

另一个非常有用的应用是Andy Bell和Heydon Pickering在他们的Every Layout网站和书中推荐的,叫做"The Stack",它最简单的形式看起来像这样。

* + * {
  margin-top: 1.5rem;
}

当与一个将所有元素的边距减少到零的重置或父规则一起使用时,这将对所有跟随另一个元素的元素应用一个顶部的边距。这是一个快速获得垂直节奏的方法。

如果你确实想更多一点--嗯,选择性--那么我喜欢在特定的情况下把它作为一个后缀来使用,比如下面的情况。

article * + h2 {
  margin-top: 4rem;
}

这类似于堆叠的想法,但更多地是针对标题元素,以便在内容部分之间提供更多的喘息空间。

属性选择器

这是一个极其强大的类别,但往往没有充分使用它的潜力。

你知道你可以通过利用CSS属性选择器实现类似于regex的匹配结果吗?

这对于修改BEM风格的系统或其他使用相关类名但也许不是单一的通用类名的框架来说是特别有用的。

让我们看一个例子:

[class*="component_"]

这个选择器将选择所有有一个包含"component_"字符串的类的元素,这意味着它将匹配 "component_title "和 "component_content"。

而且你可以通过在关闭属性选择器之前加入i ,确保匹配不区分大小写:

[class*="component_" i]

但你不必指定一个属性值,你可以简单地检查它是否存在,例如:

a[class]

这将选择所有具有任何类值的a (链接元素)。

属性选择器的实际应用#

协助进行可访问性检查

这些选择器可以被用来执行一些基本的可访问性检查,例如以下内容:

img:not([alt]) {
  outline: 2px solid red;
}

这将为所有不包括alt 属性的图片添加一个轮廓。

附加到Aria上以强制执行可访问性

如果属性选择器被用作唯一的选择器,也可以帮助强制执行可访问性,使属性的缺失阻止相关的样式设计。做到这一点的一个方法是附加到所需的aria 属性。

一个例子是在实现一个手风琴交互时,你需要包括以下按钮,是否通过JavaScript切换aria布尔值。

<button aria-expanded="false">Toggle</button>

然后,相关的CSS可以使用aria-expanded 作为属性选择器,与相邻的兄弟姐妹组合器一起,对相关内容进行打开或关闭的样式。

button[aria-expanded="false"] + .content {
  /* hidden styles */
}
button[aria-expanded="true"] + .content {
  /* visible styles */
}

为非按钮式导航链接设置样式

在处理导航时,你可能会有一些默认的链接和样式为 "按钮 "的链接。在这种情况下,使用下面的方法来选择非 "按钮 "链接会非常有用。

nav a:not([class])

移除默认列表样式

我开始从Andy Bell和他的现代CSS重置中吸收的另一个提示是,根据role 属性的存在来移除列表样式。

/* Remove list styles on ul, ol elements with a list role, 
which suggests default styling will be removed */
ul[role="list"],
ol[role="list"] {
  list-style: none;
}

子项组合器

儿童组合器选择器--> --在为元素的子代应用样式时,非常有效地增加了一点特殊性以减少范围。它是唯一一个处理元素层次的选择器,可以复合选择嵌套元素。

子代组合器将子代样式从任何与子代选择器相匹配的子代扩大到只有直接子代

换句话说,虽然article p 选择了article 中的所有 p ,但article > p 只选择了直接在文章中的段落,而不是嵌套在其他元素中。

✅选择的是article > p

<article>
  <p>Hello world</p>
</article>

🚫 未用选择article > p

<article>
  <blockquote>
    <p>Hello world</p>
  </blockquote>
</article>

子母组合器的实际应用# 嵌套导航列表链接

嵌套的导航列表链接

考虑一个侧边栏的导航列表,比如一个文档网站,其中有嵌套层次的链接。ul 从语义上讲,这意味着一个外层的ul ,以及嵌套在li

对于视觉层次来说,你可能希望以不同的方式设计顶级链接和嵌套链接。为了只针对顶层链接,你可以使用以下方法。

nav > ul > li > a {
  font-weight: bold;
}

这里有一个CodePen,你可以试验一下,如果你删除该选择器中的任何一个子组合器会发生什么

元素选择器的范围

我喜欢在我的页面布局中使用元素选择器,例如headerfooter 。但你可能会遇到麻烦,因为这些元素是某些其他元素的有效子元素,例如footerblockquotearticle 中。

在这种情况下,你可能想从footer 调整到body > footer

嵌入/第三方内容的样式

有时你确实无法控制类名、ID,甚至是标记。例如,对于广告或其他JavaScript驱动的(非框架)内容。

在这种情况下,你可能会面临大量的div或spans,在这种情况下,child combinator对于将样式附加到不同层次的内容上非常有用。

请注意,所讨论的许多其他选择器在这种情况下也能有所帮助,但只有子组合器能处理层次,并能影响嵌套元素。

一般兄弟姐妹组合器

一般兄弟姐妹组合器--~ --选择位于前一个(先前定义的)元素之后的某个地方的定义的元素,而且是在同一个父类中

例如,p ~ img 将为所有位于段落之后的图像设置样式,只要它们共享同一个父元素。

这意味着所有下面的图片都会被选中:

<article>
  <p>Paragraph</p>
  <h2>Headline 2</h2>
  <img src="img.png" alt="Image" />
  <h3>Headline 3</h3>
  <img src="img.png" alt="Image" />
</article>

但在这种情况下,图像不会被选中:

<article>
  <img src="img.png" alt="Image" />
  <p>Paragraph</p>
</article>

你可能希望更具体一点(参见:相邻的兄弟姐妹组合器),这个选择器往往在创造性的编码练习中使用最多,比如我的CommitSweeper纯CSS游戏

一般兄弟姐妹组合#的实际应用

状态变化的视觉指示

将一般兄弟姐妹组合器与有状态的伪类选择器相结合,如:checked ,可以产生有趣的结果。

以下是一个复选框的HTML:

<input id="terms" type="checkbox" />
<label for="terms">I accept the terms</label>
<!-- series of <p> with the terms content -->

我们可以使用一般的同级组合器来改变条款的样式,只有当复选框被选中时,才会出现:

#terms:checked ~ p {
  font-style: italic;
  color: #797979;
}

低特异性的变化

如果我们也使用通用选择器,我们可以快速生成轻微的变化,如简单的卡片布局。

与其用类来移动内容,进出嵌套的div来改变标题和段落的排列,我们可以用通用同级组合器来产生以下变化。

Three card layouts including a headline, two paragraphs, and an image. Any content that follows an image gains the style described below.

该规则为任何跟在图片后面的元素增加了一些边距,减小了字体大小,并淡化了文本颜色。

img ~ * {
  font-size: 0.9rem;
  color: #797979;
  margin: 1rem 1rem 0;
}

你可以在这个CodePen中试验这些一般同级组合器的结果

这个规则的特异性极低,所以你可以通过添加一个更有针对性的规则轻松地覆盖它。

此外,由于它只适用于当元素是共享的图像的父元素的直接后代--在这种情况下是li ,一旦你把内容包裹在另一个元素中,该规则将只适用到继承被子元素使用。为了更好地理解这一点,试着将最后一个卡片项目的内容包裹在一个div中。颜色和边距将在div 和 type 元素上被继承,但h3 上的本地浏览器样式阻止了一般同级组合器中的font-size 被继承,因为本地浏览器规则的特异性高于通用选择器,而通用选择器在技术上是针对div 的。

相邻兄弟姐妹组合器

相邻同胞组合器--+ --选择直接跟在前一个(先前定义的)元素后面的元素。

我们已经在通用选择器的例子中使用了这个--* + * --只对紧随另一个元素的元素应用顶部空白--所以让我们直接进入更多的例子吧

相邻兄弟姐妹组合器的实际应用#

针对导航中缺乏Flexbox间隙支持的Polyfill

Flexbox间隙支持即将推出,但在撰写本报告时,Safari在其技术预览之外显然还不能使用。

因此,在像网站导航这样的灵活布局非常有用的情况下,我们可以使用相邻的兄弟姐妹组合器来协助增加边距,作为gap 的聚填。

nav ul li + li {
  margin-left: 2rem;
}

这使得列表项之间有了间隙效果,而不需要在第一个项目上清除额外的空白。

Result of the previously described rule where the flex inlined link items have space between them provided by margin-left

在表单标签和输入之间应用间距

我们为通用选择器探索的 "堆栈 "中应用的理论是,只在一个方向上应用边距。

我们可以把这个想法用于表单字段对象,以提供足够的间距来保持字段类型之间的视觉层次。在这个例子中,我们在标签和它的输入之间添加一个更小的边距,在输入和标签之间添加一个更大的边距。

label + input {
  margin-top: 0.25rem;
}

input + label {
  margin-top: 2rem;
}

注意:这个例子在有限的范围内有效。你可能想用一个分组元素来包围字段类型,以确保字段类型之间的一致性,而不是列出所有字段类型,除了input ,如selecttextarea 。 如果你对表单设计感兴趣,请查看ModernCSS的迷你系列,并继续关注我即将推出的关于跨浏览器表单字段造型的蛋头课程