Skip to content
This page has been auto-translated and may contain errors.View in English

CSS 选择器

docs.scrimba.com

您有一个充满元素的页面和一组想要应用的样式,但在您能够说明哪些元素应用它们之前,颜色或字体是无用的。选择器是您指向正确元素的方式:这个段落、每个按钮、仅导航内的链接。熟悉选择器之后,CSS 的其余部分就变成了选择要改变什么的问题,因为您已经知道如何到达它。

类型、类和 ID 选择器

CSS 规则有两部分:选择要设置样式的元素的**选择器**,以及说明如何设置的声明块。最简单的选择器是元素的名称,称为类型选择器。写 p,页面上的每个段落都会被设置样式。

这通常过于宽泛。您很少希望所有段落看起来都一样。因此,您使用 HTML class 属性给元素一个标签,称为类,并从 CSS 中用点号定位该标签。将类想象成一个名牌:您在想要视为一组的每个元素上贴上相同的标签,然后一次性设置该标签的样式。

有三种基本方式来定位元素。**类型选择器**通过标签名称匹配(p 匹配每个段落)。类选择器通过 class 属性匹配,并以前导点编写(.button)。ID 选择器通过 id 属性匹配,并以前导哈希编写(#header)。

将类作为默认样式钩子。类可以应用于任意多个元素,一个元素可以携带多个类,因此类描述可重用的组:所有具有 .card 的内容共享一个外观。类型选择器对于广泛的基础样式(例如设置正文字体)很有用,但它们很粗糙,因为它们会影响该类型的每个元素。ID 应该在页面上是唯一的,因此通过它们设置样式会将规则锁定到单个元素,正如您将在优先级下看到的,它们承载额外的权重,使得它们很难被覆盖。

三种选择器类型涵盖基础知识,其中的区别既涉及匹配,也涉及权重。类型选择器(也称为标签或元素选择器)通过元素名称匹配:psectioninput。类选择器,写作 .card,匹配任何 class 属性包含该标记的元素,属性是空格分隔的,所以一个元素可以拥有多个类。ID 选择器,写作 #header,匹配唯一的 id 属性等于该值的元素;ID 在文档中必须是唯一的。

实践规则是几乎完全通过类设置样式。类是这三个中唯一既可重用又具有低优先级的(浏览器用来解决冲突的优先级分数,本章末尾介绍),这样可以保持您的样式平坦和可覆盖。类型选择器属于一小组基础规则:重置、body 上的默认字体、ainput 上的合理默认值。ID 作为 JavaScript 钩子和片段链接目标(URL 中的 #pricing)是合法的,但作为样式钩子,它们是一个陷阱:它们是唯一的,所以它们无法描述一个组,它们的高优先级意味着稍后的类规则无法覆盖它们。用类设置样式,为锚点和脚本保留 ID。

html
<button class="btn-primary">注册</button>
<button class="btn-primary">立即购买</button>
css
/* 类型选择器:页面上的每个按钮 */
button {
  font-family: inherit;
}

/* 类选择器:仅标记为 btn-primary 的元素 */
.btn-primary {
  background: rebeccapurple;
  color: white;
}

两个按钮都从一个规则中获取 .btn-primary 样式,因为它们共享该类。那种重用就是您使用类的全部原因。这直接基于 CSS 如何工作,其中每个规则都将选择器与一组声明配对。

Juno类型、类和 ID 选择器p 这样的类型选择器会影响所有该类型的元素,这通常超过您想要的。类是一个名牌,您将其贴在关心的元素上,并用点号设置样式,如 .btn-primary。从类开始处理几乎所有内容,您不会出错。
Juno类型、类和 ID 选择器 类是您的默认钩子:可重用,一个元素可以拥有多个。为广泛的基础样式(如正文字体)保留类型选择器,并将 ID 视为对于样式来说过于具体和单一用途的。为自己节省未来的麻烦,首先选择类。
Juno类型、类和 ID 选择器 类是唯一既可重用又具有低优先级的基本选择器,这正是您通过它们设置样式并保持规则平坦的原因。类型选择器在小型基础层中获得位置,ID 属于脚本和链接锚点,而不是样式。当 ID 规则拒绝被稍后的类覆盖时,您会为保持它们分开而感到庆幸。

组合器:按关系选择

有时您希望仅在元素位于另一个元素内时设置其样式。导航中的链接应该看起来与段落中的链接不同,即使两者都是 <a> 元素。您使用**组合器**来描述该关系:将两个选择器并排放置,空格分隔,CSS 将其解读为"第二个,但仅在第一个内部"。

所以 .nav a 意思是"任何位于具有 nav 类的元素内部的链接"。空格在这里发挥真实作用。它不是装饰,它是向内看的指令。

**组合器**连接两个选择器并基于元素在 HTML 中的关系进行匹配。最常见的是后代组合器,写作空格:.nav a 匹配位于 .nav 内任何位置的每个 <a>,无论嵌套有多深。

当您只想要直接子元素而不是更深的后代时,使用子组合器 >.menu > li 匹配作为 .menu 直接子元素的列表项,跳过任何嵌套列表。还有两个兄弟组合器用于共享父元素的元素:相邻兄弟 + 匹配另一个元素之后的元素(h2 + p 设置紧跟标题之后的段落样式),通用兄弟 ~ 匹配任何后续兄弟。后代和子组合器涵盖大多数实际工作;兄弟组合器对于间距和表单模式很方便。

**组合器**是两个选择器之间的字符,定义了它们必须满足的结构关系。后代组合器(空格)在左侧嵌套在任何深度的位置匹配右侧选择器。子组合器(>)将其限制为仅直接子元素:.menu > li 忽略埋藏在嵌套 .submenu 中的列表项。相邻兄弟组合器(+)匹配与另一个元素具有相同父元素并紧接着它的元素,通用兄弟组合器(~)匹配任何具有相同父元素的后续兄弟。

在生产中值得坚持两件事。首先,复合选择器在浏览器的匹配器中从右到左读取:对于 .nav a,引擎首先找到 <a> 元素,然后向上走以检查每个都有 .nav 祖先。这就是为什么很长的后代链成本更高,以及为什么紧密的 .nav > a 可能比松散的 .nav a 更便宜。其次,后代选择器将您的 CSS 与您的标记结构耦合:.card .title 在有人将标题包装在另一个元素中的那一刻就破了。倾向于在元素上直接使用类(.card-title)保持样式对标记更改的弹性,当关系是您实际想要表达的内容时,您才降到组合器。

css
/* 后代:导航内任何链接 */
.nav a {
  text-decoration: none;
}

/* 子元素:仅作为 .menu 直接子元素的列表项 */
.menu > li {
  display: inline-block;
}

第一个规则到达导航内嵌套的每个链接;第二个仅到达菜单的顶级项,并单独保留任何嵌套列表。在它们之间选择就是选择您想要规则向下传播多远。

Juno组合器 两个选择器之间的空格表示"内部":.nav a 设置位于导航内的链接样式。该空格是指令,不是外观间距,所以不要不小心删除它。这个模式涵盖很多日常样式。
Juno组合器 空格是后代组合器,在任何深度匹配;> 将其缩小为仅直接子元素。兄弟 +~ 在您需要基于它之前的内容设置元素样式时存在。后代和子组合器将处理您日常写的大部分内容。
Juno组合器 组合器很有用,它们也会将您的 CSS 与您的 HTML 结构相关联,因此像 .card .title 这样的长后代链在一次重构时可能破裂。更倾向于在元素上直接使用类,当关系是您实际想要表达的内容时才降到组合器。匹配也从右到左运行,所以紧密的 > 通常击败松散的后代链。

伪类:按状态设置样式

元素可以处于不同的状态。链接可以静止或鼠标悬停在其上。**伪类**让您根据其状态设置元素样式,您在选择器后用一个冒号编写它。

您将首先使用的是 :hover。写 .btn-primary:hover,这些样式仅在指针悬停在按钮上时应用,这就是按钮和链接在您指向它们时给出小视觉反应的方式。

**伪类**以特定状态或位置定位元素,用单个冒号编写::hover 当指针悬停在其上时,:focus 当它被选中用于键盘输入时,:first-child 当它是其父元素内的第一个元素时。

:focus 值得注意,因为它是可访问性问题,不是便利。使用键盘导航的人按 Tab 从一个控件转到下一个,:focus 样式是唯一告诉他们所在位置的信号。永远不要删除焦点轮廓,除非用可见的替代方案替换它。还有像 :nth-child() 这样的结构伪类,它在括号中接受一个模式::nth-child(odd) 匹配每隔一个元素,对于给表行条纹很有用。:first-child:last-child 无需额外类即可抓住一个组的末端。

伪类(单冒号后的关键字)匹配处于给定状态或结构位置的元素,无需您添加任何标记。状态伪类包括 :hover:active:focus;结构伪类包括 :first-child:last-child 和参数化的 :nth-child(),其参数是 an+b 模式(:nth-child(2n) 是每隔一个元素,:nth-child(3n+1) 是每三个元素中的第一个)。

两个生产注意事项。:focus 对键盘可访问性是不可协商的:键盘用户依赖焦点环来知道他们的位置,所以在没有可见替代方案的情况下移除轮廓会使接口对他们来说无法使用。更倾向于 :focus-visible,它为键盘交互显示环但在鼠标点击时抑制它,所以您既获得干净的指针体验又获得可访问的键盘体验。在结构方面,请记住 :nth-child(n) 计数所有兄弟,无论类型,而 :nth-of-type(n) 仅计数同一元素的兄弟,这是规则工作与一个在稍微误数当一个流浪元素坐在列表中时。

css
.btn-primary:hover {
  background: purple;
}

/* 键盘用户需要可见的焦点指示符 */
.btn-primary:focus-visible {
  outline: 2px solid rebeccapurple;
  outline-offset: 2px;
}

第一个规则在指针悬停在按钮上时触发;第二个在按钮被键盘选中时绘制清晰的环,这是保持页面无需鼠标即可使用的原因。颜色和轮廓选择连接到 CSS 如何工作以及您在任何属性上设置的值。

Juno伪类 伪类设置元素在某个状态下的样式,用一个冒号编写,如 :hover 当指针悬停在其上时。那是按钮悬停效果的来源。您将其直接附加到选择器上,中间没有空格。
Juno伪类 伪类匹配状态和位置::hover:focus:nth-child():first-child。将 :focus 视为可访问性要求,因为键盘用户需要看到他们所在的位置,所以永远不要在没有可见替代方案的情况下移除轮廓。那个习惯会为所有人保持您的页面可用。
Juno伪类 达到 :focus-visible 而不是仅仅 :focus,这样环为键盘用户显示但不在鼠标点击时显示,永远不要发送完全移除焦点指示符的接口。注意 :nth-child:nth-of-type 陷阱:一个计数每个兄弟,另一个仅计数同一类型的兄弟,错误的选择在流浪元素落入列表的那一刻误数。这是那种看起来没问题的 bug,直到它发生。

伪元素:设置元素部分的样式

伪类设置整个元素在某个状态下的样式。**伪元素**是不同的:它让您设置元素的一部分的样式,或添加根本不在您的 HTML 中的小内容。

您将首先遇到的两个是 ::before::after。它们在元素的开始或结束处插入内容,您使用 content 属性告诉它们要插入什么。注意两个冒号:那个双冒号是 CSS 将伪元素与伪类分开的方式。

**伪元素**设置元素的特定部分样式而不是整个,它使用双冒号(::)来区别于单冒号伪类。最常用的是 ::before::after,它们在元素的最开始或最后创建生成的子元素。它们需要 content 属性来渲染,即使它是空的(content: ""),该生成的内容是装饰性的:屏幕阅读器可能会跳过它,所以永远不要在那里放置含义。

另一个常见的是 ::placeholder,它设置空输入内的淡色提示文本。因为伪元素针对元素的一个切片,它们让您建立小视觉片段(标签前的图标、标题后的装饰线)而无需向标记添加额外标签。

**伪元素**公开了没有自己标签的元素的一部分,通过双冒号关键字设置样式。::before::after 生成内联框作为元素的第一个或最后一个子元素;它们仅在设置 content 时渲染(使用 content: "" 作为纯装饰框),该生成的内容位于可访问性树外,所以它必须保持装饰性,永不承载用户需要的含义。::placeholder 针对输入的提示文本,::selection 针对突出显示的文本,::first-line::first-letter 针对排版切片。

单冒号与双冒号的故事值得了解。现代伪元素使用 ::(两个冒号)来区别于伪类,这个区别在 CSS3 中引入。对于四个原始伪元素(:before:after:first-line:first-letter),单冒号形式仍出于遗留原因解析,但写双冒号形式;单冒号是兼容性遗留物,不是您应该携带到新代码中的样式。在生产中,::before::after 做大量安静的工作:工具提示、自定义列表标记、较旧布局中的 clearfix 以及装饰性修饰,否则会使 HTML 混乱。

css
/* 在每个导航链接后添加装饰箭头 */
.nav a::after {
  content: " →";
  color: rebeccapurple;
}

/* 设置搜索框内提示文本的样式 */
.search-input::placeholder {
  color: grey;
  font-style: italic;
}

::after 规则插入一个仅存在于样式中而不是在 HTML 中的箭头,而 ::placeholder 到达输入内部以设置浏览器为您绘制的文本样式。两者都设置您用简单选择器无法以其他方式到达的东西。如果您设置提示文本和标签样式,排版章节在文本方面更深入。

Juno伪元素 伪元素设置元素的部分样式或添加小内容,它使用两个冒号,如 ::before::after。这两个需要 content 属性来显示。双冒号是区别伪元素与伪类的快速方法。
Juno伪元素 伪元素使用双冒号并设置元素的切片样式:::before::after::placeholder。前两个需要 content 值,甚至是空 "",该内容是装饰性的,所以保持真实含义在您的 HTML 中。它们为您节省了仅为了悬挂装饰而添加标签。
Juno伪元素 用两个冒号编写伪元素;单冒号形式仅作为四个原始元素的遗留怪癖生存。来自 ::before::after 的内容位于可访问性树外,所以它保持装饰性,永不承载用户需要阅读的任何东西。它们安静地处理工具提示、自定义标记和装饰,否则会膨胀您的标记。

优先级:哪个规则赢

迟早两个规则会尝试以不同的方式设置同一元素的样式。一个说文本是蓝色,另一个说是红色。浏览器必须选择一个,决定的方式称为**优先级**:更具体的选择器赢。

简短的版本是强度顺序。ID 选择器击败类选择器,类选择器击败类型选择器。所以如果 #header.title 都在同一元素上设置颜色,ID 赢。当两个规则具有相同的优先级时,在文件中写得更靠后的赢。

当多个规则针对同一元素并设置相同属性时,浏览器用**优先级**解决冲突,一个基于涉及选择器类型的分数。从最强到最弱的顺序是:内联样式(元素上的 style 属性)、然后 ID、然后类(和伪类和属性选择器)、然后类型选择器(和伪元素)。更具体的选择器赢,无论源顺序如何;仅当优先级相平时,稍后的规则赢。

这是倾向于类和避免为样式使用 ID 的实践原因。如果您用 ID 设置样式,稍后的类规则无法覆盖它们,所以您最终升级。而 !important,您可以追加到声明来强制它赢过一切,应该被视为最后手段:它打破了正常级联,唯一击败一个 !important 的是另一个 !important,这是您不想开始的螺旋。

**优先级**是浏览器用来在冲突声明之间选择的算法。它计算为三部分计数,通常写作 (a, b, c)a 是 ID 选择器的数量,b 是类、属性和伪类选择器的数量,c 是类型和伪元素选择器的数量。部分从左到右比较,所以任何 ID 击败任何数量的类,任何类击败任何数量的类型选择器。#header .title a 分数 (1, 1, 1).nav .menu .item a 分数 (0, 3, 1);第一个仅在 ID 上赢。内联 style 属性位于所有选择器优先级之上,!important(您追加到值来强制它的标志)甚至覆盖那个;通用选择器 * 和组合器对分数没有贡献。

两个现代工具改变了您管理这个的方式。:is() 接受一个选择器列表并在任何都匹配时匹配,但它接受其最具体参数的优先级,所以 :is(#header, .title) 继承 ID 的权重。:where() 做相同的匹配但优先级总是零,这使它成为编写低权重基础样式的工具,任何东西都可以覆盖。:has(),关系伪类,匹配包含某物的元素("有图像的 .card",.card:has(img))并采用其参数的优先级。所有这一切下的可维护性实践是保持优先级低而平坦:更倾向于单类选择器,避免 ID 和 !important,使用 :where() 当您想要默认值屈服时把权重归零。平坦优先级意味着稍后的规则总是可以通过更晚而赢,这是级联为您工作而不是反对您。

css
/* 类型选择器,优先级 (0, 0, 1) */
p {
  color: grey;
}

/* 类选择器,优先级 (0, 1, 0):这个赢 */
.intro {
  color: black;
}
html
<p class="intro">这个段落是黑色的,因为类规则高于类型规则。</p>

两个规则都针对段落,两个都设置 color,但类选择器比类型选择器更具体,所以段落是黑色。将一个选择器向上或向下推动优先级梯子往往是不诉诸更重工具解决冲突的全部。盒模型章节在间距规则冲突时遇到相同的级联。

Juno优先级 当两个规则争夺同一元素时,更具体的赢:ID 击败类,类击败类型选择器。如果它们具有相同的优先级,在文件中最后出现的赢。这解释了大多数冲突。
Juno优先级 强度运行内联,然后 ID,然后类,然后类型,更具体的规则赢,无论它在文件中何处。这就是为什么通过类设置样式和避免 ID 保持您的 CSS 可覆盖。保持 !important 作为最后手段,因为唯一击败一个的是另一个。
Juno优先级 优先级计数为 (a, b, c) 用于 ID、类和类型,从左到右比较,所以一个 ID 超越任何类的堆。保持它低而平坦:单个类,没有 ID 用于样式,没有 !important,和 :where() 当您想要默认值屈服时把权重归零。平坦优先级意味着稍后的规则总是可以通过更晚而赢,这正是级联为您工作。