伪元素能帮助我们做些什么
伪元素已经不是什么新东东了,平时大家在实际生产中肯定有使用过伪元素。但伪元素能帮助我们做些什么呢?针对该问题,有很多同学能很好的回答,但也有很多场景并不是所有开发人员都完全了解的。今天再次花时间来整理一下伪元素能帮助我们做些什么?我想接下来的内容和实例肯定会有不少同学感兴趣的。如果你是其中的一位,那么请继续往下阅读。
伪元素是什么
元素事实上是HTML中的概念,常常把HTML中的标签称作为元素。那么伪元素是什么呢?从其名称上来说,伪即为假,在实际的DOM中是不存在的,而事实上呢?我们可以借助一些CSS的特性让其模拟成一个元素,对于这样的元素我们称之为伪元素。
在W3C的标准规范中也有独立的规范文档,时到今日,伪元素的最新规范是**CSS Pseudo-Elements Module Level 4**。在该规范中有我们熟悉的伪元素,比如::first-line
、::first-letter
、::selection
、::placholder
、::after
和::before
,也有我们不熟悉的::marker
、::inactive-selection
、::spelling-error
和::grammar-error
。
不过我们今天要聊的仅仅是其中的::before
和::after
两个伪元素。这两个伪元素配合W3C的另外一个规范CSS Generated Content Module Level 3中的content
可以创建出两个伪元素。这样一来,一个HTML元素就具备多个盒模型,即有多个背景和边框等,正如下图所示:
伪元素如何生成内容
刚才提到过,伪元素::before
和::after
需要和CSS的content
结合在一起才能有效的生成内容或盒子。比如:
<!-- HTML -->
<div>我是一个div</div>
// CSS
div::before,
div::after {
content: ''
}
在浏览器中查看元素时,可以看到::before
插入到div
内容前面,::after
插入到div
内容后面,如下图所示:
再次强调,::before
和::after
能生效是因为我们在伪元素中显式的声明了content
,哪怕是个空字符串。虽content
是一个空字符串值,但这个时候其实已经在页面中就生成了一个盒模型,而且也具备了“Computed”的样式,如下图所示:
这个时候适用于元素的CSS属性也就适用于伪元素上了。比如:
div {
display: flex;
align-items: center;
}
div::before,
div::after {
content:"";
display: block;
width: 32px;
height: 32px;
border-radius: 100%;
background-color: #f36;
margin: 0 5px;
}
效果如下:
如果在::before
和::after
中未显式设置content
的话,那么就无法将伪元素插入到DOM结构中,如下所示:
上面我们看到的是给content
一个空字符串,事实上除了空字符串还可以是任意你想要的字符串,比如文本,HTML实体符,Emoji等。比如:
除此之外,还可以配合content
的其他特性来生成内容,比如attr()
函数将HTML标签的属性值当作伪元素的内容,还可以通过url()
函数将图片当作伪元素内容:
<!-- HTML -->
<div data-content="Let's Go!">我是一个div</div>
// CSS
div::before {
content: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/161359/quotes.png)
}
div::after {
content:attr(data-content)
}
更为厉害的是,还可以将attr()
、url()
与字符串内容结合起来使用:
div::before {
content:"dodododod" url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/161359/quotes.png)
}
div::after {
content:attr(data-content) "➜";
}
还可以更复杂一些,将他们都结合在一起:
div::before {
content:"dodododod" url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/161359/quotes.png) attr(data-content)
}
div::after {
content:attr(data-content) "➜" url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/161359/quotes.png);
}
content
除了上面所提到的特性之外,还可以配合counters
、counter-increment
和counter-reset
实现自动计数器和列表编号等,但该效果暂时先不阐述,我们将放到后面来聊这个特性。
有关于
content
更多的特性,可以阅读W3C的CSS Generated Content Module Level 3规范文档。
接下来我们来看看在实际上能帮助我们做些什么?
伪元素的常见用例
我们先来看看伪元素相关的常见用例。
清除浮动
CSS的float
属性虽然其本质不是用来布局的,但很长一段时间中它都被用于Web布局中。熟悉float
的同学都知道,浮动会带来一些其他的麻烦事情,比如说容器高度的坍塌。也正因为这些原因,在使用float
时,最好记得清除浮动。其中清除浮动有一种经典的用法clearfix
,它就是借助伪元素来完成:
.clearfix:before,
.clearfix:after {
content:"";
display:table;
}
.clearfix:after {
clear:both;
overflow:hidden;
}
Icon图标
随着CSS的@font-face
的出,伪元素常被用于一些Icon Font中,用来制作Icon图标。在业内很多Icon Font库都采用这种方式来实现的,比如著名的 Font Awesome。
.fa-flag:before {
content: "\f024";
}
另外在一些纯CSS绘制的Icons上也有伪元素的身影,比如@wentin的**CSS Icons**库中的很多标有借助了伪元素:
<!-- HTML -->
<div class="audio-solid icon"></div>
// CSS
.audio-solid.icon {
color: #000;
position: absolute;
margin-left: 5px;
margin-top: 8px;
width: 9px;
height: 7px;
border-left: solid 1px currentColor;
border-right: solid 1px currentColor;
border-bottom: solid 1px currentColor;
border-radius: 0 0 50% 50%;
}
.audio-solid.icon:before {
content: '';
position: absolute;
left: 1px;
top: -6px;
width: 5px;
height: 10px;
border: solid 1px currentColor;
border-radius: 4px;
background-color: currentColor;
}
.audio-solid.icon:after {
content: '';
position: absolute;
left: 4px;
bottom: -4px;
width: 1px;
height: 4px;
background-color: currentColor;
}
伪元素和Sprites的结合
在Web开发中,时常会用到CSS Sprites或者是SVG Sprites来节约请求。那么在使用Sprites时为了更好的控制图标的容器,常常是通过增加一个额外的标签或者伪元素。比如:
上面的示例每个button
的文本前面有一个Icon图标,示例中的每个图标的尺寸大致是32px x 32px
,使用伪元素::before
来起到一个空标签作用,从而更好的更控制图标容器大小,位置。
制作Ribbon和Bubbles
红丝带(Ribbon)和气泡(Bubbles)时常都是通过纯CSS绘制而成,比如气泡效果。早在2010年@necolas就用纯CSS实现了不同效果的气泡,而这些气泡效果都有使用到伪元素:
和气泡类似,结合伪元素可以实现不同效果的丝带,比如下面这个效果:
如果你足够仔细的话,你会发现,这个和前面绘制图标的案例有点类例。的确是这样的,特别是在一个div
绘制不同图形的案例,更显伪元素的强大:
是不是觉得不可思议,要是你也想尝试着使用一个div
绘制出一个图形(你喜欢的图形),其思路和具体操作步骤可以参阅读@Lynn Fisher和Robert Nyman一起写的一篇教程《Single Div Drawings with CSS》。
而且在Codepen上你搜索“Single Div”,你会发现有很多优秀的案例,足可以让你脑动大开:
告诉你一个更有意思的网站,那就是CSSBattle,这是一个况技网站,用最少的代码实现同一个效果,谁的代码量少谁就获胜。这里面的效果基本上是基于一个
div
完成的。可以说是一个练习CSS绝佳场所。
CSS Divider(分隔线)
在实际生产中会碰到区块之间的分隔线,常常我们把其称为CSS Divider,如下图所示:
面对这样的效果,在内容前后插入伪元素是非常有效的,比如下面这样的一个示例:
div {
display: flex;
justify-content: center;
align-items: center;
&::before,
&::after {
content: '';
display: block;
height: 0.09em;
min-width: 30vw;
}
&::before {
background: linear-gradient(to right, rgba(240,240,240,0), #fff);
margin-right: 4vh;
}
&::after {
background: linear-gradient(to left, rgba(240,240,240,0), #fff);
margin-left: 4vh;
}
}
你看到的效果如下:
上面的效果是最简单的,你可以根据自己所需要的效果,让其更个性化:
@Samia Rai在《25 Creative CSS Divider Examples》一文中收集了共25
种有关于CSS制作的分隔线案例,当然有些是没有使用到伪元素的。如果你感兴趣的话,你可针对同样的效果,做一些改造。
CSS Tooltips
提示框(Tooltips)对于大家而言应该不会感到陌生。在实际制作提示框效果时,使用纯CSS也可以很好的实现。特别是配合HTM标签自定义属性会显得更为有意思。在介绍伪元素如何生成内容的时候,我们提到过content
配合HTML标签自定义属性的时候,可以让自定义属性的值很好的放到伪元素中。
也就是说,基于该特性,我们可以很好的实现提示框的效果。比如:
<!-- HTML -->
<span class="tool" data-tip="By adding this class you can provide almost any element with a tool tip." tabindex="1">tool</span>
// CSS
.tool {
cursor: help;
position: relative;
}
.tool::before,
.tool::after {
left: 50%;
opacity: 0;
position: absolute;
z-index: -100;
}
.tool:hover::before,
.tool:focus::before,
.tool:hover::after,
.tool:focus::after {
opacity: 1;
transform: scale(1) translateY(0);
z-index: 100;
}
.tool::before {
border-style: solid;
border-width: 1em 0.75em 0 0.75em;
border-color: #3E474F transparent transparent transparent;
bottom: 100%;
content: "";
margin-left: -0.5em;
transition: all .65s cubic-bezier(.84,-0.18,.31,1.26), opacity .65s .5s;
transform: scale(.6) translateY(-90%);
}
.tool:hover::before,
.tool:focus::before {
transition: all .65s cubic-bezier(.84,-0.18,.31,1.26) .2s;
}
.tool::after {
background: #3E474F;
border-radius: .25em;
bottom: 178%;
color: #EDEFF0;
content: attr(data-tip);
line-height: 1.2;
margin-left: -8.75em;
padding: 1em;
transition: all .65s cubic-bezier(.84,-0.18,.31,1.26) .2s;
transform: scale(.6) translateY(50%);
width: 17.5em;
}
.tool:hover::after,
.tool:focus::after {
transition: all .65s cubic-bezier(.84,-0.18,.31,1.26);
}
效果如下:
有关于提示框我们还可以组件化,对于提示框组件的实现要比上面这种纯CSS实现要更复杂化,但其中的一些思路是值得我们去思考的。如果你要是对这方面感兴趣的话,可以花点时间阅读前段时间学习React的时整理的一篇教程《提示框组件的实现给我带来的思考和探索》。
自动生成计数器
如果给一个列表添加列表项目,如果是一个默认效果的话,可能会直接考虑ol
这样的有序列表,但很多时候我们是需要一些个性化的列表项目符:
不考虑别的,原生CSS就具备这方面的特性。不知道大家是否还记得,我们在前面给大家预留了一个话题:
伪元素结合
content
中的counters
、counter-increment
和counter-reset
实现自动计数器和列表编号。
其原理很简单:
配合不同的CSS我们可以实现很多个性化的效果:
这里要提一下我的偶像 @Ana Tudor,她给大家提供的案例更是令我们感到CSS强大的魅力。比如她的博文《Restricting a (pseudo) element to its parent’s border-box》中的案例:
在《如何通过CSS自定义属性给CSS属性切换提供开关》一文还有更多有关于这方面的案例,感兴趣的可以点击这里查看:
虽然它们的结合能让我实现很多个性化的列表项的效果,但这里我要告诉大家的是,在未来CSS的::marker
能赋予我们更强的能力,如果对该特性感兴趣的话,可以阅读《聊聊CSS的::marker
》一文。
CSS Loading Animation
CSS的伪元素事实上就是一种免费的DOM元素,DOM元素具备的很多特性它们也同样具备。CSS的样式也是如此。比如我们常常在加载页面时用到的Loading动效,就有很多是借助CSS的伪元素一起完成的,比如@Camden Foucht写的LoadLab就是一个很好的示例:
有关于更多的Loading动画还可以参考下面这些链接中提供的各种案例:
如果你对Web动效方面感兴趣的话,可以点击这里查看有关于这方面的教程。
优化链接
在Web页面中,你的链接会有站内链接也会有站外链接,可以通过伪元素很好的告诉你的用户它们之间的区别。比如下面这个示例:
a[href^="http"]:hover::after{
content:"(" attr(href) "➜)";
padding: 0 5px;
margin-left: 5px;
background: linear-gradient(to right,var(--mainColor) 0%,var(--mainColor) 5px,transparent);
}
效果如下:
类似的思路也可以用于其他的一些链接或导航菜单上(当然也可以用于你自己喜欢的任何地方):
如果你的链接是链接了一些文件(提供给用户下载或在线阅读),那么可以配合CSS属性选择器,根据文件扩展名提供不同的Icon图标向用户示意:
Switch Toggle Button (切换按钮)
自定义的checkbox
和radio
(也常称作Switch Toggle),通过label
标签和其伪元素,可以轻易实现个性化定制效果,比如下面这个示例:
有关于这方面的案例还有很多,感兴趣的话可以查看下面的链接:
上面列举了十种我们使用伪元素常见的场景,当然还有很多场景没有可能被我遗漏了,如果你有这方面的经验和案例,欢迎在下面的评论是与我们一起分享。
伪元素的不常见用例
接下来,我们再来看一些伪元素不见常的用例和使用场景。
盒阴影
在CSS中虽然有box-shadow
和drop-shadow()
可以让我们给一个元素添加阴影效果。但有些场景,他们是心有余而力不足的。比如:
针对于这样的场景我们使用伪元素可以让事情变得简单地多:
上面这个模拟3D按钮的算是简单了。采用同样的技术,也可以很好的实现上图所示的侧边,中间等不位置的阴影效果:
这样做是有一定原因的。将box-shadow
转换为伪元素实现阴影效果,对于性能是较大帮助的。特别是有动效的地方,可以让阴影效果更为平滑:
上面的录屏中的Demo来自于@tobiasahlin在《How to animate box-shadow with silky smooth performance》【译文】一文中提供的Demo。
如果你使用浏览器开发者工具查看的话,两个效果之间的渲染的差异有多大:
当你悬停在左边的卡片(在
box-shadow
上应用动画)与悬浮在右边的卡片(对其伪元素的opacity
应用动画)进行相比时,你会很明显的发现有更多的重绘。刚好,@ChokCoco刚发表的博客也提到了这方面的技术,感兴趣的可以阅读《CSS 阴影动画优化技巧一则》一文。
链接和图片的连动
这个效果的思路很奇特,在伪元素上使用图片,而且伪元素和链接能相互连动。这个效果是来自于@Ahmad Shadeed的《Uncommon Use Cases For Pseudo Elements》一文。先上Demo吧:
代码并不复杂,主要是思路新奇:
.link-1 {
color: #854FBB;
}
@media (min-width: 700px) {
.link-1:after {
content: "";
position: absolute;
right: 0;
top: 20px;
width: 150px;
height: 100px;
background: currentColor;
opacity: 0.85;
transition: 0.3s ease-out;
}
.link-1:hover {
text-decoration: underline;
}
.link-1:hover:after {
transform: scale(1.2);
opacity: 1;
}
}
尝试着在上面的示例中,将鼠标悬浮到链接上或者右侧颜色的区域上,你都可以看到两者有连动效果:
想象一下,如果将示例中的颜色区域换成产品图片,是不是很有创意:
扩展可点击区域
可点击区域是不合理直接影响了用户和你的产品的交互,特别是在移动端。大家可能有碰到过,有些产品在按钮、链接、复选框或单选框等操作上就是失效,要点击很多次才能有效果。造成这种行为就是因为点击区域过小。
在社区中,有关于可点击区域大小给用户带来的体验是否合理,有较多的探讨,比如:
- Finger-Friendly Design: Ideal Mobile Touch Target Sizes
- Optimal Size and Spacing for Mobile Buttons
- Create a semantic “breakout” button to make an entire element clickable
- Enhancing The Clickable Area Size
特别是在一些带可点击操作的图标上,Icon图标的实际尺寸并不适合一些系统的设计规范,在iOS上就提供Icon图标可点击区域应该是48px x 48px
,如果你使用的图标小区该区域的话,我们就应该通过别的方式来进行扩展。那么伪元素是一个较好的方式。比如下面这个示例:
@Ahmad Shadeed 和 @hankchizljaw 有过一个共同的观点。比如在一个卡片上,可以让整个卡片都具有可点击效应(click
事件绑定在button
或一个<a>
)元素之上。如下图所示:
也可以查看下面的Demo源码:
蒙层效果
大家是否还记得在《Clipping和Masking 何时使用》一文中介绍Masking和Clipping技术时,先用常规则的CSS技术实现了缕空的效果:
正如你所看到的,不管是使用box-shadow
、border
还是radial-gradient()
都没有离开伪元素的身影。在某些场景之下,如果要给元素上面添加一层,借用伪元素的确是一个较好的选择。特别是想对图片做一些特殊效果的时候:
除了这种简易效果之外,还可以实现《CSS如何实现交叉布局》一文中提到的交叉布局效果:
以及一些淡入淡出的效果:
注意,该示例用的是CSS的
mask
技术,如果你感兴趣的话,可以思考一下怎么通过伪元素来实现上面的效果。
Slider 和 Output
@Ana Tudor在《Using Conic Gradients and CSS Variables to Create a Doughnut Chart Output for a Range Input》一文介绍了怎么使用input[type="range"]
实现一个圆形进度条。这也是个很有意思的案例,而且涉及到知识点较多,效果中运用到伪元素只是其中小小的一部分,感兴趣的同学可以阅读教程和示例源码:
不一样的计数器
伪元素和content
结合是可以做很多事情。正如前面所示,结合content
中的counters
、counter-increment
和counter-reset
实现自动计数器和列表编号等。除此之外,配合CSS自定义属性,还可以实现另样的效果,比如下面这个递增(递减)的案例:
有关于伪元素不常见的用例就整到这个为止。@Ahmad Shadeed在他的博文《Uncommon Use Cases For Pseudo Elements》还提供了一些其他的案例,感兴趣的可以去看看。如果你有其新奇或有意思的案例,欢迎与我们一起共享。
小结
CSS伪元素并不是什么新知识点或者说新技能,在这篇文章中主要是搜集和整理了自己在以往项目中用到的案例(或将来可用)。说实话并没有太多的隐藏技能,只不过有些案例可以开拓我们的思路,打开我们的眼界。最后希望这篇文章对你有所帮助。