伪元素能帮助我们做些什么

发布于 大漠

伪元素已经不是什么新东东了,平时大家在实际生产中肯定有使用过伪元素。但伪元素能帮助我们做些什么呢?针对该问题,有很多同学能很好的回答,但也有很多场景并不是所有开发人员都完全了解的。今天再次花时间来整理一下伪元素能帮助我们做些什么?我想接下来的内容和实例肯定会有不少同学感兴趣的。如果你是其中的一位,那么请继续往下阅读。

伪元素是什么

元素事实上是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除了上面所提到的特性之外,还可以配合counterscounter-incrementcounter-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中的counterscounter-incrementcounter-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 (切换按钮)

自定义的checkboxradio(也常称作Switch Toggle),通过label标签和其伪元素,可以轻易实现个性化定制效果,比如下面这个示例:

有关于这方面的案例还有很多,感兴趣的话可以查看下面的链接:

上面列举了十种我们使用伪元素常见的场景,当然还有很多场景没有可能被我遗漏了,如果你有这方面的经验和案例,欢迎在下面的评论是与我们一起分享。

伪元素的不常见用例

接下来,我们再来看一些伪元素不见常的用例和使用场景。

盒阴影

在CSS中虽然有box-shadowdrop-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;
    }
}

尝试着在上面的示例中,将鼠标悬浮到链接上或者右侧颜色的区域上,你都可以看到两者有连动效果:

想象一下,如果将示例中的颜色区域换成产品图片,是不是很有创意:

扩展可点击区域

可点击区域是不合理直接影响了用户和你的产品的交互,特别是在移动端。大家可能有碰到过,有些产品在按钮、链接、复选框或单选框等操作上就是失效,要点击很多次才能有效果。造成这种行为就是因为点击区域过小。

在社区中,有关于可点击区域大小给用户带来的体验是否合理,有较多的探讨,比如:

特别是在一些带可点击操作的图标上,Icon图标的实际尺寸并不适合一些系统的设计规范,在iOS上就提供Icon图标可点击区域应该是48px x 48px,如果你使用的图标小区该区域的话,我们就应该通过别的方式来进行扩展。那么伪元素是一个较好的方式。比如下面这个示例:

@Ahmad Shadeed@hankchizljaw 有过一个共同的观点。比如在一个卡片上,可以让整个卡片都具有可点击效应(click事件绑定在button或一个<a>)元素之上。如下图所示:

也可以查看下面的Demo源码:

蒙层效果

大家是否还记得在《Clipping和Masking 何时使用》一文中介绍MaskingClipping技术时,先用常规则的CSS技术实现了缕空的效果:

正如你所看到的,不管是使用box-shadowborder还是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中的counterscounter-incrementcounter-reset实现自动计数器和列表编号等。除此之外,配合CSS自定义属性,还可以实现另样的效果,比如下面这个递增(递减)的案例:

有关于伪元素不常见的用例就整到这个为止。@Ahmad Shadeed在他的博文《Uncommon Use Cases For Pseudo Elements》还提供了一些其他的案例,感兴趣的可以去看看。如果你有其新奇或有意思的案例,欢迎与我们一起共享。

小结

CSS伪元素并不是什么新知识点或者说新技能,在这篇文章中主要是搜集和整理了自己在以往项目中用到的案例(或将来可用)。说实话并没有太多的隐藏技能,只不过有些案例可以开拓我们的思路,打开我们的眼界。最后希望这篇文章对你有所帮助。