优先级与选择器
CSS 是有局部作用域的(ShadowDOM)。
CSS 选择符有五种:
- 后代关系(空格);
- 父子关系(>);
- 相邻兄弟关系(+);
- 随后兄弟关系(~);
- 列关系(||)。
CSS 优先级有六种:
- 0 级:通配选择器(*,不包括伪元素)、选择符(空格、>、+、~、||)和逻辑组合伪类;
- 1 级:标签选择器;
- 2 级:类选择器、属性选择器和伪类;
- 3 级:ID 选择器;
- 4 级:内联样式;
- 5 级:!important(可以使 JS 无效)。
CSS 选择器优先级与 DOM 位置无关。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<style>
/* 选择器的优先级并不考虑 DOM 的位置,所以背景是绿色 */
div p {
background: red;
}
html p {
background: green;
}
/* 提升选择器优先级的方法:重复选择器自身 */
.paragraph.paragraph {
font-size: 24px;
}
/* 提升选择器优先级的方法:使用属性选择器 */
.paragraph[id] {
color: red;
}
.paragraph {
color: black;
font-size: 12px;
}
</style>
<div>
<p class="paragraph" id="paragraph">
背景是 green,字体颜色是 red,字体大小是 24px。
</p>
</div>
|
在 HTML 中,标签和属性都是不区分大小写的,而属性值是区分大小写的。于是,在 CSS 中,标签选择器不区分大小写,属性选择器中的属性也不区分大小写,而类选择器和 ID 选择器本质上是属性值,因此要区分大小写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<style>
P {
font-size: 18px;
}
[ID] {
background: brown;
}
.PARAGRAPH {
/* 没生效 */
color: aqua;
}
[data-my='papa'] {
/* 没生效,因为属性值大小写敏感 */
text-align: right;
}
[data-my='papa' i] {
/* 生效了,i 会让浏览器忽略属性值大小写 */
text-decoration: line-through;
}
</style>
<div>
<p class="paragraph" id="paragraph" data-my="PaPa">
背景是 brown,字体颜色是 black,字体大小是 18px,文字左对齐,有删除线。
</p>
</div>
|
CSS 选择器最佳实践:
- 不要使用 ID 选择器,优先级太高,使得样式覆盖变得困难;
- 样式重置可以使用标签选择器或属性选择器;
- 不要嵌套选择器:
- 渲染性能差,CSS 是从右向左匹配渲染的(.box > div 是先匹配所有的 div 再匹配 box 类名的);
- 选择器性能排序:ID 选择器>类选择器>标签选择器>通配选择器>属性选择器>伪类选择器;
- 每多嵌套一层选择器就会多一层匹配和计算;
- 优先级混乱,嵌套越多,优先级计算越复杂;
- 样式布局脆弱,过多的层级会和 HTML 层级结构进行绑定,不好维护。
- 命名建议使用小写,使用英文或缩写,专有名词可以使用拼音;
- 命名设置统一前缀,强化品牌的同时避免样式冲突;
- 对于组合命名,可以使用短横线或下划线。
对 JS 中的后代选择器可能存在的错误认识:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<div id="divEle">
<div class="loney">单身如我</div>
<div class="outer">
<div class="inner">内外开花</div>
</div>
</div>
<script>
const divsOne = document.querySelectorAll('#divEle div div');
// 查找 ID 为 divEle 的元素的子元素,选择同时满足 div div 选择器的 DOM 元素
const divsTwo = document.querySelector('#divEle').querySelectorAll('div div'); // querySelectorAll 是全局属性的
const divsThree = document.querySelectorAll('div div');
const divsFour = document
.querySelector('#divEle')
.querySelectorAll(':scope div div'); // 使用 :scope 伪类查找
console.log(divsOne); // NodeList [div.inner]
console.log(divsTwo); // NodeList(3) [div.loney, div.outer, div.inner]
console.log(divsThree); // NodeList(3) [div.loney, div.outer, div.inner]
console.log(divsFour); // NodeList [div.inner]
</script>
|
相邻/随后兄弟选择器会忽略文本节点和注释节点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<style>
div {
background: red;
}
p {
background: green;
}
div + p {
text-decoration: line-through;
}
</style>
<div>Hamburger brisket strip steak pastrami bresaola tenderloin.</div>
<!-- Bacon kevin pork loin jerky fatback strip steak. -->
Pork bacon chicken shankle, pastrami sirloin chislic.
<p>Pastrami beef ribs shank pork belly sirloin boudin.</p>
|
为什么没有前面兄弟选择器?
受制于 DOM 渲染规则;如果 CSS 支持了前面兄弟选择器或者父元素选择器,那就必须要等页面所有子元素加载完毕才能渲染 HTML 文档。网页呈现速度必然会大大减慢,浏览器会出现长时间的白板,这会造成不好的体验。
属性选择器
八种属性选择器:
- [attr],只要包含指定的属性就匹配;
- [attr=“val”],属性值完全匹配选择器;
- [attr~=“val”],属性值单词完全匹配选择器;
- [attr|=“val”],属性值起始片段完全匹配选择器(val、val-*);
- [attr^=“val”],属性值以字符 val 开头的元素;
- [attr$=“val”],属性值以字符 val 结尾的元素;
- [attr*=“val”],属性值包含字符 val 的元素;
- [attr=“val” i],以上几种均适用,匹配时忽略属性值大小写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<p data-id class="a">Bacon kevin pork loin jerky fatback strip steak.</p>
<p data-id="one" class="b">Bacon kevin pork loin jerky fatback strip steak.</p>
<p data-id="one two" class="c">
Bacon kevin pork loin jerky fatback strip steak.
</p>
<p data-id="one-two three" class="d">
Bacon kevin pork loin jerky fatback strip steak.
</p>
<script>
console.log(document.querySelectorAll('[data-id]')); // NodeList(4) [p.a, p.b, p.c, p.d]
console.log(document.querySelectorAll("[data-id='one']")); // NodeList [p.b]
console.log(document.querySelectorAll("[data-id~='one']")); // NodeList(2) [p.b, p.c]
console.log(document.querySelectorAll("[data-id|='one']")); // NodeList(2) [p.b, p.d]
</script>
|
基于属性选择器的过滤技术:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<div>
<input type="text" placeholder="输入城市名称" id="inputEle" />
<ul>
<li data-search="重庆市chongqing">重庆市</li>
<li data-search="哈尔滨市haerbing">哈尔滨市</li>
<li data-search="长春市changchun">长春市</li>
<!-- ... -->
</ul>
</div>
<script>
const inputEle = document.getElementById('inputEle');
const styleEle = document.createElement('style');
document.head.appendChild(styleEle);
inputEle.addEventListener('input', e => {
const value = e.target.value;
styleEle.innerHTML = value
? `[data-search]:not([data-search*=${value}]){display:none;}`
: '';
});
</script>
|
DEMO 在
这里
。
伪类
:focus 默认只能匹配特定的元素:
- 设置了 contenteditable 属性的普通元素;
- 设置了 tabindex 属性的普通元素
- 非 disabled 状态的表单元素;
- 包含 href 属性的 a 元素;
- area、summary 元素。
:focus 只有在当前元素处于聚焦状态的时候才匹配,而 :focus-within 在当前元素或者是当前元素的任意子元素处于聚焦状态的时候都会匹配。换句话说,子元素聚焦,可以让父级元素的样式发生变化。:focus-within 的行为本质上是一种“父选择器”行为,子元素的状态会影响父元素的样式。
:focus-visible 可以让我们知道元素的聚焦行为到底是鼠标触发还是键盘触发;如果希望去除鼠标点击时候的 outline,而保留键盘访问时候的 outline,只要一条 CSS 规则就可以了:
1
2
3
|
:focus:not(:focus-visible) {
outline: 0;
}
|
a 标签的伪类要遵守『爱恨原则』,定义的顺序必须是 :link→:visited→:hover→:active:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<style>
/* 与 a{} 的区别是:a:link 只会匹配具有 href 属性的 a 元素 */
a:link {
background-color: aqua;
}
/* :visited 仅支持很少的 CSS 属性(大多和颜色相关且不支持透明色) */
a:visited {
background-color: blueviolet;
}
a:hover {
background-color: chartreuse;
}
a:active {
background-color: darkcyan;
}
</style>
<a href="https://baidu.com/" target="_blank">百度</a>
|
:placeholder-shown 表示当输入框的 placeholder 内容显示的时候,匹配该输入框;通过此伪类可以实现比较有意思的
交互
。
:checked 与 [checked] 的区别:
- :checked 只能匹配标准表单元素,不能匹配普通元素;
- [checked] 可以与设置了 checked 属性的任意元素匹配;
- [checked] 不会响应 JS 对元素属性的修改;
- :checked 可以匹配从祖先元素那里继承过来的状态。
可以对输入值进行验证的伪类:
- :valid 与 :invalid;
- :in-range 与 :out-of-range;
- :required 与 :optional;
- :user-invalid;
- :blank。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<style>
/* 只要表单元素没有设置 required,都会匹配此伪类 */
:optional {
width: 300px;
}
/* 页面加载就会生效,即使用户没进行任何操作,所以不太友好 */
.input-valid:valid,
.input-range:in-range {
border: 2px solid green;
}
.input-valid:invalid,
.input-range:out-of-range {
border: 2px solid red;
}
</style>
<input
class="input-valid"
type="text"
pattern="\w{4,6}"
placeholder="请输入验证码"
/>
<input
class="input-range"
type="number"
min="10"
max="100"
placeholder="请输入数字"
/>
<input class="input-range" type="range" min="10" max="100" />
|
:root 表示的就是 html 元素,只是 :root 优先级更高。对应的,在 ShadowDOM 中使用 :host 伪类。
:empty 的使用:
- 不会匹配含有注释/空格的空元素(毕竟注释和空格都算是节点);
- 不会匹配没有闭合的闭合元素;
- ::before/::after 不会影响匹配;
- 可以非常方便的隐藏空节点;
- 结合伪元素,可以在空元素内做一些用户提示。
否定伪类 :not():
- 本身的优先级为 0,最终选择器优先级是由括号里的表达式决定的,:not(p) 的优先级等同于标签选择器的优先级;
- :not() 可以级联,例如,input:not(:disabled):not(:read-only);
- 不支持多个表达式,也不支持选择符,例如,:not(li, dd) 是无效的。
:fullscreen 伪类用来匹配全屏元素;通过调用 element.requestFullScreen 可以让元素全屏显示,通过调用 document.cancelFullScreen 可以取消全屏。
:lang() 用来匹配指定语言环境下的元素。