前言
面试前端候选人的时候,我经常会问这样一个有关 CSS 的问题:
你知道伪类与伪元素么,它们的分别是什么?这时,能回答上来的很少。换一种问法,你知道 :hover, :active, :focus, :visited 么?这时,基本都能回答上来,这不就是 a 标签的四种状态么。
嗯,ok。然后继续问,那么 ::before 和 ::after,听说过么?这时,能听到的回答是,嗯,我看到过,偶尔会用。
伪类与伪元素,都有一个“伪”字,那它们有什么区别么?这时,回应我的,是一片沉默。。。
从回答上来分析,虽然伪类和伪元素平时都有接触,但在概念上,都比较模糊。今天,我们就来说说伪类与伪元素的区别,以及使用场景。伪类,不是只有 a 标签的四种状态。伪元素,也不是只有 ::before 与 ::after。更多的伪类与伪元素,详见文末附录。
概念上的区别
从概念上来区分,大致有以下几点:
伪类,更多的定义的是状态。常见的伪类有 :hover,:active,:focus,:visited,:link,:not,:first-child,:last-child 等等。
伪元素,不存在于 DOM 树中的虚拟元素,它们可以像正常的 html 元素一样定义 css,但无法使用 JavaScript 获取。常见伪元素有 ::before,::after,::first-letter,::first-line 等等。
CSS3 明确规定了,伪类用一个冒号 (:) 来表示,而伪元素则用两个冒号 (::) 来表示。但目前因为兼容性的问题,它们的写法可以是一致的,都用一个冒号 (:) 就可以了,所以非常容易混淆。
实战场景——伪类
表单校验
表单的校验中,常会用到 :required、:valid 和 :invalid 这三个伪类。先来看看它们所代表的含义。
:required,指定具有 required 属性 的表单元素
:valid,指定一个 匹配指定要求 的表单元素
:invalid,指定一个 不匹配指定要求 的表单元素
看下面这个例子:
<p>input 中类型为 email 的校验 </p>
<p> 符合 email 校验规则 </p>
<input type=”email” required placeholder=” 请输入 ” value=”24238477@qq.com” />
<br><br>
<p> 不符合 email 校验规则 </p>
<input type=”email” required placeholder=” 请输入 ” value=”lalala” />
<br><br>
<p> 有 required 标识,但未填写 </p>
<input type=”email” required placeholder=” 请输入 ” value=”” />
input {
&:valid {
border-color: green;
box-shadow: inset 5px 0 0 green;
}
&:invalid {
border-color: red;
box-shadow: inset 5px 0 0 red;
}
&:required {
border-color: red;
box-shadow: inset 5px 0 0 red;
}
}
效果如下:
折叠面板
过去,要实现折叠面板的显示或隐藏,只能用 JavaScript 来搞定。但是现在,可以用伪类 :target 来实现。:target 是文档的内部链接,即 URL 后面跟有锚名称 #,指向文档内某个具体的元素。
看下面这个例子:
<div class=”t-collapse”>
<!– 在 url 最后添加 #modal1,使得 target 生效 —>
<a class=”collapse-target” href=”#modal1″>target 1</a>
<div class=”collapse-body” id=”modal1″>
<!– 将 url 的 #modal1 变为 #,使得 target 失效 —>
<a class=”collapse-close” href=”#”>target 1</a>
<p>…</p>
</div>
</div>
.t-collapse {
>.collapse-body {
display: none;
&:target {
display: block;
}
}
}
元素的 index
当我们要指定一系列标签中的某个元素时,并不需要用 JavaScript 获取。可以用 :nth-child(n) 与 :nth-of-type(n) 来找到,并指定样式。但它们有一些小区别,需要注意。
首先,它们的 n 可以是大于零的数字,或者类似 2n+ 1 的表达式,再或者是 even / odd。
另外,还有 2 个区别:
:nth-of-type(n) 除了关注 n 之外,还需要关注最前面的类型,也就是标签。
:nth-child(n) 它关注的是:其父元素下的第 n 个孩子,与类型无关。
看下面这个例子,注意两者的差异:
<h1> 这是标题 </h1>
<p> 第一个段落。</p>
<p> 第二个段落。</p>
<p> 第三个段落。</p>
<p> 第四个段落。</p>
<p> 第五个段落。</p>
实战场景——伪元素
antd 的彩蛋事件
还记得 2018 年圣诞节的“彩蛋事件”,在整个前端圈,轰动一时。因为按钮上的一朵云,导致不少前端 er 提前回家过年了。当时,彩蛋事件出现的第一时间,就吓得我赶快打开工程看了一眼,果然也中招了。为了保住饭碗,得赶紧把云朵去掉。
查看了生成的 html,发现原来是 button 下藏了一个 ::before。所以,赶紧把样式覆盖掉,兼容代码如下:
.ant-btn {
&::before {
display: none !important;
}
}
美化选中的文本
在网页中,默认的划词效果是,原字色保持不变,划过时的背景变为蓝底色。其实,这是可以用 ::selection 来进行美化的。看下面这个例子:
<p>Custom text selection color</p>
::selection {
color: red;
background-color: yellow;
}
效果如下:
划过的部分美化为:红色的字体,并且底色变为了黄色。
总结
CSS 也可以实现动态的交互,并非只有 JavaScript 才能实现。
书写的时候,要尊重规范。写伪类的时候用 :,而写伪元素的时候用 ::。
兼容性的问题,交给 postcss 去做。本文并未涉及兼容性的写法,包括前缀问题,可以交给 autoprefixer 去做。
附录
CSS3 中的伪类
:root 选择文档的根元素,等同于 html 元素:empty 选择没有子元素的元素:target 选取当前活动的目标元素:not(selector) 选择除 selector 元素意外的元素:enabled 选择可用的表单元素:disabled 选择禁用的表单元素:checked 选择被选中的表单元素:nth-child(n) 匹配父元素下指定子元素,在所有子元素中排序第 n:nth-last-child(n) 匹配父元素下指定子元素,在所有子元素中排序第 n,从后向前数:nth-child(odd)、:nth-child(even)、:nth-child(3n+1):first-child、:last-child、:only-child:nth-of-type(n) 匹配父元素下指定子元素,在同类子元素中排序第 n:nth-last-of-type(n) 匹配父元素下指定子元素,在同类子元素中排序第 n,从后向前数:nth-of-type(odd)、:nth-of-type(even)、:nth-of-type(3n+1):first-of-type、:last-of-type、:only-of-type
CSS3 中的伪元素
::after 已选中元素的最后一个子元素::before 已选中元素的第一个子元素::first-letter 选中某个款级元素的第一行的第一个字母::first-line 匹配某个块级元素的第一行::selection 匹配用户划词时的高亮部分
PS:欢迎关注我的公众号“超哥前端小栈”,交流更多的想法与技术。