CSS选择器
CSS 有多种选择器,可以帮助我们针对不同的 HTML 标签设置样式。
选择器
通配符选择器
通配符选择器(*)可以选择任何元素,通常用于设置默认样式。
* {
margin: 0;
padding: 0;
}
类型选择器
类型选择器(Type Selectors)直接使用 HTML 标签来选择选择元素:
p { color: blue; }
上面的代码将所有的 <p>
标签文本渲染为蓝色。
class 选择器
个人理解 class 选择器代表着 “一类有着相同样式的标签”,所以该选择器可以用于多个 HTML 标签。
类选择器使用 .
开头,后接类名:
<style>
.first {
font-weight: bold;
text-decoration: line-through;
}
</style>
<p class="first">超越技术</p>
<div>哈哈.</div>
<p>一起学前端!</p>
一个标签也可以包含多个 class,多个 class 之间使用空格分隔:
<!-- 包含两个 class:info 和 highlight -->
<p class="info highlight">...</p>
此外,当你在不同的 HTML 标签中使用了相同的 class,然后还想选择某个具体的标签时,你可以像下面这样组合使用两个选择器:
<style>
p.first { color: red; }
</style>
<p class="first">该颜色会是红色</p>
<span class="first">该颜色不会是红色</span>
id 选择器
个人理解这里的 id 在使用场景上就和数据库中的主键一样,可以用来定位某个唯一的 HTML 标签,所以该选择器或者说 id 属性的值,在 HTML 文档中需要是唯一的,不能出现在多个元素上。
id 选择器使用 #
号来定义:
<style>
#my-id {
background-color: yellow;
}
</style>
<p id="my-id">超越技术</p>
<div>哈哈.</div>
<p>一起学前端!</p>
因为 id 选择器的唯一性,所以就没必要像上面提到的 class 选择器一样,通过标签再进一步缩小选择范围了。
属性选择器
我们都知道可以在 html 标签上自定义任意的属性,比如下面的例子:
<!--
`data-rakuyo` 不是很好,因为它不明所以;
但是它又很好,可以一眼让你看出它是一个自定义属性。
-->
<div data-rakuyo>...</div>
属性选择器使用 []
中括号包裹属性名称。针对上面的代码,我们可以通过属性选择器来选择该 div
标签:
/* 选择所有带有 `data-rakuyo` 属性的元素,不论其所属哪个 html 标签 */
[data-rakuyo] { font-weight: bold; }
再进一步缩小范围,属性选择器可以通过指定标签来筛选 “具有某些属性的标签”:
/* 仅限带有 `data-style` 属性的 <button> 标签 */
button[data-style] { font-weight: bold; }
可以通过具体的属性值来做条件筛选:
<style>
[data-style="cancel"] { color: gray; }
[data-style="done"] { color: black; }
</style>
<button data-style="cancel">取消</button>
<button data-style="done">下单</button>
属性选择器还可以借助一些匹配操作符,比如 $=
和 *=
来实现特定的功能:
/* 选择 `src` 属性以 `https` 开头的所有图片 */
img[src^="https"] { color: red; }
/* 选择 `src` 属性以 `.jpg` 结尾的所有图片 */
img[src$=".jpg"] { border: 3px solid black; }
/* 选择 `href` 属性包含 `external` *字符串* 的所有链接 */
a[href*="external"] { color: green; }
/* 选择 `href` 属性包含 `external` *单词* 的所有链接 */
a[href~="external"] { color: green; }
注意
*=
和~=
的区别。前者匹配字符串,而后者匹配的是一个完整的单词。
伪选择器
伪类选择器
伪类选择器用于定义元素在特定状态下的样式,比如鼠标悬停时的样式。或者用于选择第n个子元素。
对于特定状态,有以下几种:
名称 | 说明 | 示例 |
---|---|---|
:hover |
用于在用户将鼠标悬停在元素上时应用样式 |
|
:active |
用于选取被用户激活(例如鼠标点击)的元素 |
|
:visited |
用于选取用户已访问过的链接 |
|
:focus |
用于选取当前获取了焦点的元素通常在用户通过键盘或者鼠标进行交互时出现 |
|
对于选择子元素,有以下几种用法:
名称 | 说明 | 示例 |
---|---|---|
:nth-of-type(n) |
该选择器允许你选择相同类型的且特定位置的子元素请注意,n 从1开始,且不能是负数 |
|
:nth-child(n) |
该选择器允许你选择特定位置的子元素 |
|
:nth-last-child(n) |
该选择器与:nth-child(n) 类似,但是它从元素的末尾开始计数。 |
|
:first-child “nth-child(1)” |
该选择器选取某个元素的第一个子元素 |
|
:last-child “nth-last-child(1)” |
该选择器选取某个元素的最后一个子元素 |
|
我觉得 :nth-of-type
和 :nth-child
的概念比较接近,同时容易混淆,所以在这里特意将他们两个单独拿出来,用一个例子来看看这两个选择器之间的区别。
<div class="container">
<p>Paragraph 1</p>
<div>Div 1</div>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
<div>Div 2</div>
</div>
针对上面这段 html,两个选择器的结果为:
选择器 | 结果 | 说明 |
---|---|---|
.container p:nth-of-type(2) |
选择到 <p>Paragraph 2</p> |
这个选择器将选择容器 .container 内的第二个 <p> 元素。在给定的 HTML 结构中,第二个 <p> 元素是 "Paragraph 2",因此该选择器会影响到这个段落元素。 |
.container p:nth-child(2) |
不会选择任何元素 | 这个选择器将选择容器 .container 内所有的 <p> 元素中的第二个子元素。在给定的 HTML 结构中,第二个子元素是 "Div 1",而不是 <p> 元素。因此,这个选择器不会选择任何元素,因为在该结构中,第二个子元素不是 <p> 元素。 |
简而言之,nth-child(n)
会先按照顺序选择父视图的子元素,再看该子元素是否符合限制条件,如果符合则样式生效,否则不会生效;而 nth-of-type(n)
则相反,会先选择符合要求的元素,再按照顺序进行选择。
同理可得,因为
:first-child
、:last-child
和:nth-child(1)
、:nth-last-child(1)
相同,所以假如使用p:fist-child
,但是第一个标签不是<p>
,那么该样式也不会生效。
伪元素选择器
伪元素选择器用于选择元素的特定部分而不是元素本身。伪元素选择器以双冒号 ::
开头。
下面是一些常见的伪元素选择器:
/* 选择元素的第一行文本 */
p::first-line {
font-weight: bold;
}
/* 选择元素的第一个字母 */
p::first-letter {
font-size: 150%;
}
/* 在元素内容之前插入内容 */
p::before {
content: "前置内容 ";
}
/* 在元素内容之后插入内容 */
p::after {
content: " 后置内容";
}
/* 选择用户选中的文本部分 */
::selection {
background-color: yellow;
color: black;
}
/* 选择输入框的占位符文本 */
input::placeholder {
color: gray;
}
除此之外还有一些不太常见的伪元素选择器:
/* 选择列表项的标记部分(通常是列表项前面的符号,如圆点或数字) */
li::marker {
color: red;
}
/* 选择元素的背景层,用于处理全屏元素的背景样式 */
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
/* 选择拼写错误的文本部分 */
::spelling-error {
text-decoration: underline wavy red;
}
/* 选择语法错误的文本部分 */
::grammar-error {
text-decoration: underline dashed blue;
}
组合选择器
组合选择器是 CSS 中的一种选择器,允许你针对同时满足多个条件的元素应用样式。
后代选择器
后代选择器(Descendant Selector)允许你选择某个元素内部的所有后代元素。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Descendant Selector Example</title>
<style>
/* 选择 .container 内部的所有 p 元素 */
.container p {
color: blue;
}
</style>
</head>
<body>
<div class="container">
<p>This is a paragraph.</p>
<div>
<p>This is another paragraph.</p>
</div>
</div>
</body>
</html>
在这个例子中,.container p
选择器会选择所有嵌套在 .container
内部的 <p>
元素,并将它们的颜色设为蓝色。即两个 <p>
标签都会变成蓝色。
子选择器
子选择器(Child Selector)用于选择某个元素的直接子元素。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Child Selector Example</title>
<style>
/* 选择 .container 下的直接子元素 p */
.container > p {
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<p>This is a paragraph.</p>
<div>
<p>This is another paragraph.</p>
</div>
</div>
</body>
</html>
这个例子中,.container > p
选择器只会选择 .container
直接子元素中的 <p>
元素,并将它们的字体加粗。而里层的 This is another paragraph.
并不会被加粗。
对比 后代选择器 来看,后代选择器会一直查询到该叶子节点的最末端,渲染所有满足条件的标签,不管中间是否还有其他标签;而 子选择器 只会渲染下一个层级(直接)的子节点,并不会进一步深入。
相邻兄弟选择器
相邻兄弟选择器(Adjacent Sibling Selector)选择紧接在指定元素后的兄弟元素。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Adjacent Sibling Selector Example</title>
<style>
/* 选择 .container 后面紧邻的 p 兄弟元素 */
.container + p {
color: green;
}
</style>
</head>
<body>
<div class="container">
<p>This is a paragraph.</p>
</div>
<p>This is another paragraph.</p>
</body>
</html>
在这个例子中,.container + p
选择器会选择紧接在 .container
元素后面的 <p>
元素,并将它们的颜色设为绿色。即 This is another paragraph.
会被渲染为绿色。
有以下几点需要注意:
- “相邻兄弟选择器” 强调 相邻。如果我们将代码进行修改:
<div class="container">
<p>This is a paragraph.</p>
</div>
<span>123</span>
<p>This is another paragraph.</p>
此时 .container
不再有任何相邻的 <p>
节点,所以 .container + p
不会选择任何的标签进行渲染。
- “相邻兄弟选择器” 只会渲染相邻的 一个 节点。如果我们将代码进行修改:
<div class="container">
<p>This is a paragraph.</p>
</div>
<p>This is another paragraph. 1</p>
<p>This is another paragraph. 2</p>
那么只有相邻的第一个 <p>
标签,即 This is another paragraph. 1
会被渲染为绿色,另外一个 <p>
标签仍为黑色。
通用兄弟选择器
通用兄弟选择器(General Sibling Selector)选择与指定元素相邻的所有兄弟元素。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>General Sibling Selector Example</title>
<style>
/* 选择 .container 后的所有 p 兄弟元素 */
.container ~ p {
font-style: italic;
}
</style>
</head>
<body>
<div class="container">
<p>This is a paragraph.</p>
</div>
<p>This is another paragraph.</p>
<p>This is yet another paragraph.</p>
</body>
</html>
在这个例子中,.container ~ p
选择器会选择紧跟在 .container
元素后的所有 <p>
元素,并将它们的字体样式设为斜体。很容易看出来,该选择器和 相邻兄弟选择器 互为一对。
组合选择器总结
后代选择器(
) 和 子选择器(>
) 互为一对:同样是选择子元素,前者会深入到层级的最末端,而后者只会选择下一个层级,点到为止。
相邻兄弟选择器(+
) 和 通用兄弟选择器(~
) 互为一对:同样是选择相邻元素,前者只选择同层级内相邻的节点,而后者会选择同层级内所有符合条件的节点。
课后思考
现在我们提出来这么一个需求:从接口处获取商品的价格(忽略接口请求步骤),得到一个价格字符串,比如 "¥35.66"
。针对该字符串进行渲染,要求如下:
- 文字颜色均为红色。
¥
和.66
部分样式一致:15号字。但是需要注意¥
符号并不一定存在。35
部分加粗,18号字。
使用 js+html+css,留几秒钟思考给出答案。
... ... ... ... ...参考答案如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
/* 样式1:¥符号 15 号字,红色 */
.currency-sign,
.after-dot {
font-size: 15px;
}
/* 样式2:¥ 和 . 之间的内容,18号字,加粗,红色 */
.between {
font-size: 18px;
font-weight: bold;
}
.currency-sign,
.after-dot,
.between {
color: red;
}
</style>
</head>
<body>
<span class="currency-value"></span>
<!-- 使用 JavaScript 定义变量 -->
<script>
// 定义变量,代替接口请求的步骤
const currencyValue = "¥35.66";
// 获取 span 标签
const spanElement = document.querySelector('.currency-value');
// 检查 span 标签是否存在
if (spanElement) {
// 检查 currencyValue 是否包含 "¥" 符号
if (currencyValue.includes('¥')) {
// 渲染 ¥ 符号
const currencySignSpan = document.createElement('span');
currencySignSpan.innerText = '¥';
currencySignSpan.classList.add('currency-sign');
spanElement.appendChild(currencySignSpan);
}
// 删去 "¥" 符号后的值
let valueWithoutSign = currencyValue.replace('¥', '');
// 按 "." 分隔值
const [beforeDot, afterDot] = valueWithoutSign.split('.');
// 渲染 "." 之前的内容
const beforeDotSpan = document.createElement('span');
beforeDotSpan.innerText = beforeDot;
beforeDotSpan.classList.add('between');
spanElement.appendChild(beforeDotSpan);
// 渲染 "." 符号 和 "." 之后的内容
const afterDotSpan = document.createElement('span');
afterDotSpan.innerText = '.' + afterDot;
afterDotSpan.classList.add('after-dot');
spanElement.appendChild(afterDotSpan);
}
</script>
</body>
</html>
然后我们可以再进一步,如果改成 vue3 + TypeScript 呢?
<template>
<span class="currency-value">
<span v-if="hasCurrencySign" class="currency-sign">¥</span>
<span class="between">{{ beforeDot }}</span>
<span class="after-dot">.{{ finalAfterDot }}</span>
</span>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const currencyValue = ref<string>("¥35.66");
const hasCurrencySign = currencyValue.value.includes('¥');
const [beforeDot, afterDot] = currencyValue.value.replace('¥', '').split('.');
const finalAfterDot = afterDot || '00';
</script>
<style scoped>
/* 样式1:¥符号 15 号字,红色 */
.currency-sign,
.after-dot {
font-size: 15px;
}
/* 样式2:¥ 和 . 之间的内容,18号字,加粗,红色 */
.between {
font-size: 18px;
font-weight: bold;
}
.currency-sign,
.after-dot,
.between {
color: red;
}
</style>
可见因为 vue 中的模版可以直接引用变量,为我们减少了大量的 DOM 操作,代码量得以减少了不少。