Article

深入了解::before和::after伪元素实用技巧

伪元素::before::after是令人难以置信的通用的CSS工具。了解它们可以帮助您编写实用的 CSS 来解决各种情况。或使用它们来创造令人印象深刻的 CSS 效果。

本文从底层开始,介绍了这些伪元素以及如何使用它们。随着本文的进行,我们将深入探讨更有趣的主题。我们还将大量讨论content的 CSS 属性,因为它至关重要。

什么是伪元素?

在 CSS 中,您可以将关键字::before::after关键字添加到选择器中以创建伪元素。伪元素被插入到与选择器匹配的元素中,该元素位于其中的任何内容之前或之后。

举个例子可能最简单:

<p class="my-paragraph">
  I'm a paragraph!
</p>
.my-paragraph {
  padding: 20px;
  border: 4px dashed lightgray;
}

.my-paragraph::before {
  content: 'This is before!';
  color: hotpink;
}

.my-paragraph::after {
  content: 'This is after!';
  color: steelblue;
}

使用以上 HTML 和 CSS 后,产生以下效果。

bookcc.top

如果您最擅长使用开发工具,那么您会发现<p>内部的::before::after元素。您可以单击它们以查看或编辑其样式,就像其他任何元素一样。

bookcc.top

我们只需要使用 CSS 来创建 HTML 中不存在的元素和内容,使其样式无限可能!

装饰线标题

::before::after非常适合添加装饰,下面是带有装饰线条的标题。

bookcc.top

HTML只有一个<h1>标签。

<h1>Elegant Heading</h1>

两侧的双线都完全在 CSS 中处理。

h1 {
  display: grid;
  grid-template-columns: minmax(50px, 1fr) auto minmax(50px, 1fr);
  align-items: center;
  text-align: center;
  gap: 40px;
}

h1::before, h1::after {
  content: '';
  border-top: 6px double;
}

这将在标题文本之前和之后插入伪元素。它们的content设置为'',因为我们实际上不需要任何内容​​,我们只希望在那里存在元素,因此我们可以对它们应用双边框来创建线条。

现在创建伪元素所需的content属性,但是正如您在此处所看到的,可以将其设置为''(空字符串)。

通过将<h1>元素转换为 3 列来实现布局。左列和右列是双线,宽度均为minmax(50px, 1fr),表示它们的匹配宽度始终不小于 50px。标题文本整齐地居中。

装饰丝带标题

现在,让我们进一步介绍功能区标题。

bookcc.top

这次,我们仍然只是需要 HTML 中的<h1>单个标签。

<h1>Ribbon Heading</h1>

从下面获取 CSS 效果

h1 {
  position: relative;
  margin: 0 auto 20px;
  padding: 10px 40px;
  text-align: center;
  background-color: #875e46;
}

h1::before, h1::after {
  content: '';
  width: 80px;
  height: 100%;
  background-color: #724b34;

  /* position ribbon ends behind and slightly lower */
  position: absolute;
  z-index: -1;
  top: 20px;
  
  /* clip ribbon end shape */
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 25% 50%);

  /* draw and position the folded ribbon bit */
  background-image: linear-gradient(45deg, transparent 50%, #5d3922 50%);
  background-size: 20px 20px;
  background-repeat: no-repeat;
  background-position: bottom right;
}

h1::before {
  left: -60px;
}

h1::after {
  right: -60px;
  transform: scaleX(-1); /* flip horizontally */
}

这次,我们使用绝对定位来放置伪元素。左侧的碳带末端用::before创建,右侧的碳带用::after创建。CSS 共享样式一起绘制两者,面向相同的方向,然后翻转h1::aftertransform: scaleX(-1)

碳带末端的形状通过clip-path来实现。折叠的带状钻头是在底部内角上background-image绘制linear-gradient()并小心放置的小三角形。

引用块装饰

让我们回到设置content上,::before::after不是使用空字符串,因为您可以在其中做一些有趣的事情。

content可以设置为特殊值open-quoteclose-quote。这样做会插入适合当前语言的开/闭引号。样式<blockquote>是一个很好的用例。

<blockquote lang="en">
  Hello! I am a block quote in English. Check out how the quotation marks are automatically added around me!
</blockquote>

<blockquote lang="fr">
  Salut ! Je suis une citation en français. Je ne connais pas le français, alors j’ai demandé à quelqu’un sur Twitter de le traduire !
</blockquote>
blockquote::before {
  content: open-quote;
}

blockquote::after {
  content: close-quote;
}

blockquote::before, blockquote::after {
  opacity: 0.25;
  padding: 0 10px;
  font-size: 3em;
}

blockquote {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 20px;
  padding: 20px 10px;
  border-radius: 10px;
  background-color: #e5ddcb;
}

现在,我们有了自动时尚的<blockquote>口号。

bookcc.top

不同于<blockquote><q>标记(内联引号)默认情况下显示引号。这不是魔术,它是浏览器的默认用户代理样式表,它与我们刚才展示的一样!

q::before {
  content: open-quote;
}

q::after {
  content: close-quote;
}

关于另一个很酷的事情open-quoteclose-quote,自带筑巢感知。例如,对于英语,如果您有嵌套<q>标签,则外部标签将使用“ ”,而内部标签将使用‘和’

CSS quotes属性使您可以直接控制open-quoteclose-quote的用途。

q {
  quotes: '<' '>'; /* wrap all quotes with < and > */
}

q {
  quotes:
    '<' '>'      /* wrap outermost quotes with < and > */
    '<<' '>>'    /* then quotes nested one level deep with << and >> */
    '<<<' '>>>'; /* then quotes nested deeper with <<< and >>> */
}

让我们用实际演示来结束本节quotes

bookcc.top

每个引用口号都有相同的标记。

<q>If you're fine with <q>unexplained phenomena</q> then go ahead.</q>

但是具有不同的quotes值。

.customized {
  quotes: '→' '←';
}

.customized-nested {
  quotes: '⟪' '⟫' '⟨' '⟩';
}

.emoji {
  quotes: '😀💬"' '"';
}

q::before, q::after {
  color: #ff003c;
}

显示元素属性

使用::before::after可以在伪元素中显示元素属性attr()。例如,href<a>下面元素的属性。

<a href="https://tacobell.com">Taco Bell</a>

我们可以href以此显示链接中的值。

a::after {
  content: ' → ' attr(href); /* show an arrow before the href */
}

下面是它的样子。

bookcc.top

我在上面的 CSS 中添加了一些额外的内容。您可以将多个事物放在一起content(通常用空格隔开)以将它们串联起来。在这种情况下,' → '可与串联attr(href)以实现您在演示中看到的内容。

显示计数

CSS 计数器使您可以对事物进行计数。可以使用::before::after显示计数值。该演示显示选中了多少个复选框,没有涉及JavaScript

bookcc.top

这是相关的 CSS。

input:checked {
  counter-increment: total;
}

output::before {
  content: counter(total);
}

每个选中的复选框都会增加total计数器的数量,然后将其显示在<output>元素的::before伪元素中。

如果您想了解有关 CSS 计数器的更多信息,请参阅本文

影像嵌入

您可以将图像放入::before::after设置contenturl()

div::before {
  content: url(image.png);
}

还记得content以前的串联吗?它也适用于图像!这是连接图像,字符串和属性的示例。

<div data-lives="3"></div>
div::before {
  content: url(mario.gif) ' × ' attr(data-lives);
}

bookcc.top

并不是必须使用content显示图像。另一种选择是background-image与适当大小的伪元素一起使用。

div::before {
  content: '';
  background-image: url(image.png);
  display: block;
  width: 100px;
  height: 100px;
}

值得一提的是,::before::after默认display: inline。如要设置width/ height,则需要更改它,如上面的CSS所示。

内容替换

正如您现在所注意到的,::before::after依赖content。除非content设置,否则它们甚至不会渲染。但是,依赖关系不是对等的,在某些情况下,content可以不使用::before::after

如果content仅将其设置为单个图像,则可以直接在元素上使用它来替换该元素的 HTML 内容。

.replace {
  content: url(replace.png);
}

让我们在以下 HTML 上尝试一下。

<span class="replace">This is text!</span>
<span class="replace"><img src="chicken-nugget.png" alt="chicken nugget"></span>
<img class="replace" src="chicken-nugget.png" alt="chicken nugget">

HTML 正在测试 3 种情况:

  1. 具有简单文本的元素,它将被替换。
  2. <img>内部有元素,它也将被替换。
  3. <img>包含元素。Firefox 不会替代它,但是其他浏览器会替代它。

你自己看!替换了元素内的所有内容。最初默认鸡块(chicken-nugget.png)。

标记内容

::marker是可以使用的另一个伪元素content,类似::before::after。但它专用设置列表项标记的样式。

<ul>
  <li>Cupcakes are essentially cakes, but cuppier.</li>
  <li>The first cupcake was invented by Harley McCupcakken in 1796.</li>
  <li>Alright fine, I don't actually know anything about cupcakes.</li>
</ul>
li::marker {
  content: '🧁';
}

bookcc.top

不幸的是,Safari 不会让你设置content::marker,并且浏览器有其他限制。

另一个选择是完全隐藏标记list-style-type: none,然后使用::before来做自己的事情。

设计图形

有时人们只想通过 CSS 发挥创造力。很有趣!一个特别受欢迎的游戏是查看单个元素可以做多少事情。::before::after经常在这里发挥作用,因为在您的一次性设备上拥有这些额外的伪元素会带来很多可能性。

Lynn Fisher 的 A Single Div 画廊提供了一些令人印象深刻的例子。检查 CSS,您将看到很多::before::after伪元素。

Dhanish Gajjar 的 Pure CSS Flags,Single Divs 是 113 ​​个标志的集合,其中许多标志是用::before::after伪元素绘制的。

然后是 Astrit Malsija 的 700 多个 CSS 图标。仔细研究一下图标,您就会明白。

这些事情并不总是可行的,但并不是毫无意义。通常可以获得一些创意乐趣以及在此过程中学习一些新技巧。

控制布局

回到实际问题,::after可以与实用程序类一起使用以控制布局。一个著名的例子是“clearfix”,它确保元素环绕其中的所有浮动元素。这是一个示例实现,尽管有很多变体。

.clearfix::after {
  content: '';
  display: block;
  clear: both;
}

多年来,CSS 缺一不可,但是现在有了一个更现代的解决方案。

.clearfix {
  display: flow-root;
}

这是一个演示该问题和修复方法的演示。请注意,第一个鸡块延伸到其父元素之外,而第二个和第三个则没有。

bookcc.top

::after伪元素还可以通过布局插入换行符协助。

.inline-element::after {
  content: '\A';
  white-space: pre;
}
<form>
  <label for="email">Email address</label>
  <input autocapitalize="none" type="email" id="email" placeholder="like test@test.com" />
</form>

bookcc.top

有时您想在插入元素之后创建换行符,同时保留该插入元素的插入状态。幸运的是,它所需要的只是一点伪元素魔术。

会动画吗?

可以将 CSS 动画应用于::before::after吗?是的!它们的工作就像处理普通元素一样容易。

话虽如此,为content属性设置动画更加有趣。在 Safari 以外的浏览器中,它可以是独立动画的,这意味着content可以在值之间切换而无需任何逐渐过渡。

div::before {
  content: '';
  animation: flip 6s linear infinite;
}

@keyframes flip {
  from { content: 'Hello!'; }
  to { content: 'Goodbye!'; }
}

bookcc.top

如果您想为中的数字设置动画content,可以执行一个技巧,但是它仅适用于 Chrome,Edge 和 Opera(这些浏览器当前支持 Properties and Values API,而该技巧依赖于该浏览器)。

这是它的工作方式:

  1. 声明一个为整数的自定义属性。
  2. 设置一个计数器,以通过获取该整数的值counter-reset
  3. content伪元素的中显示该计数器。
  4. 对整数值进行动画处理。
@property --num {
  syntax: '<integer>';
  inherits: true;
  initial-value: 0;
}

div::before {
  counter-reset: my-counter var(--num);
  content: counter(my-counter);
  animation: count 10s ease-in-out infinite alternate;
}

@keyframes count {
  to { --num: 100; }
}

bookcc.top

如果您想更深入地研究该技术,请参阅本文,这将使您走得更远。

伪元素与伪类

除了::before::after,还有其他伪元素用于对现有元素的各个部分进行样式设置。我们之前讨论了::marker样式列表项标记。另一个示例是::selection,它用于为选定的文本设置样式。

还有伪类,它们与伪元素不同。伪类基于状态应用样式。例如,我们:checked在较早的演示中使用了仅在选中复选框时才应用样式。

伪元素以双冒号(::)开头,而伪类以单冒号(:)开头。W3C 规范的旧版本使用单冒号的伪元素,所以你可能偶尔会看到:before:after。它们仍然可以工作,但是建议改用::

辅助功能

content是屏幕阅读器如何传达信息的重要标记。根据 W3C 规范,content是语音倾述。

“content”属性适用于语音,生成的内容必须呈现为语音输出。

好吧,听起来不错。但是,让我们仔细看看在各种情况下会发生什么。

如果您使用::before::after出于装饰目的,并且将其content设置为''(空字符串),那么屏幕阅读器将不会宣布它们。

div::after {
  /* screen reader says nothing */
  content: '';
}

屏幕阅读器将阅读放入的文本content。并非总是如此,但是现在……除非您仍在支持 IE11(对不起)。

已经有人分享了他们测试各种浏览器 + 屏幕阅读器组合的结果。比较老旧的浏览器版本看起来很糟糕,直到您意识到所有故障都来自 IE11 和 Firefox 29(2014年发布)。

div::after {
  /* screen reader says "hello" */
  content: 'hello';
}

请记住,表情符号和符号算作文本。屏幕阅读器会宣布它们,这可能很好,但是如果您将它们用作装饰,也可能会很尴尬。

div::after {
  /* screen reader says "yellow five pointed star" */
  content: '⭐';
}

div::after {
  /* screen reader says "clockwise open circle arrow" */
  content: '↻';
}

屏幕阅读器可以识别中的图像content。在我的测试中,VoiceOver 将在 Firefox 和 Safari 中宣布“图像”,并在 Chrome 中宣布“未标记的图像”。

div::after {
  /* screen reader says something like "image" or "unlabelled image" */
  content: url(image.png);
}

如果图像不是装饰性的,则说明我们处境不好。我们有一个有意义的图像,仅作为“图像”传达。

如果图像是装饰性的,则使用屏幕阅读器的人可能不会丢失任何重要信息,但他们不知道!他们会听到“图像”,并想知道错过了什么。

好消息是,您可以为指定替代文本content。这提供了一种方法来告知屏幕阅读器要宣布的内容,类似于将alt属性设置为<img>

div::after {
  /* screen reader says "chicken nugget" */
  content: url(chicken-nugget.png) / 'chicken nugget';
}

div::after {
  /* screen reader says nothing */
  content: url(decorative-image.png) / '';
}

它也可以与文本一起使用,因此我们可以告诉屏幕阅读器如何声明装饰性表情符号和符号。

div::after {
  /* screen reader says nothing */
  content: '⭐' / '';
}

div::after {
  /* screen reader says "refresh" */
  content: '↻' / 'refresh';
}

坏消息是 Firefox 和 Safari 不支持的替代文本content,实际上content完全在这些浏览器中失效。您可以通过content两次声明来解决此问题。

div::after {
  content: '⭐';      /* Firefox and Safari will use this */
  content: '⭐' / ''; /* other browsers will use this */
}

您还应该知道,输入中的文本content是不可选择且不可搜索的,并且content无法右键单击其中的图像以获取图像上下文菜单。对于琐碎或装饰性的东西来说没什么大不了的,但是在放入冗长的文本或重要图像之前,我会重新考虑content

结束语!

::before::after伪元素和content属性可以做很多事情。我认为您可以拥有最少的标记(通常只是一个HTML标记),然后让 CSS 随它一起运行并完成所有这些令人惊奇的事情,真是太好了。

希望我们在本文中介绍了足够的基础知识,使您对各种可能性(和局限性!)有所了解,并激发了一些创造性的兴趣。谢谢阅读!