为什么:before和:after伪元素需要一个"content"属性?

29

在下面的场景中,为什么:after选择器需要一个content属性才能起作用?

.test {
    width: 20px;
    height: 20px;
    background: blue;
    position:relative;
}
   
.test:after {
    width: 20px;
    height: 20px;
    background: red;
    display: block;
    position: absolute;
    top: 0px;
    left: 20px;
}
<div class="test"></div>

注意,在指定 content 属性之前,您看不到伪元素:

.test {
    width: 20px;
    height: 20px;
    background: blue;
    position:relative;
}
   
.test:after {
    width: 20px;
    height: 20px;
    background: red;
    display: block;
    position: absolute;
    top: 0px;
    left: 20px;
    content:"hi";
}
<div class="test"></div>

为什么这是预期的功能呢?我们可能认为display:block会强制元素显示出来。但奇怪的是,你实际上可以在Web调试工具中看到样式,但它们不会显示在页面上。

5个回答

28

以下是一些W3C规范和草案的参考:

选择器级别3

:before:after伪元素可用于在元素内容之前或之后插入生成的内容。

:before:after 伪元素

作者使用:before:after伪元素来指定生成内容的样式和位置。正如它们的名称所示,:before:after伪元素分别指定文档树内容之前和之后的内容位置。通过与这些伪元素一起使用,content属性指定要插入的内容。

content属性

初始值:none

此属性与:before:after伪元素一起用于在文档中生成内容。值具有以下含义:

none - 不生成伪元素。


应用于::before::after伪元素的样式影响生成内容的显示。content属性就是这个生成的内容,若不存在此属性,则默认值为content: none,意味着没有内容可应用样式。

如果你不想重复多次使用content:'';,你可以通过在CSS中全局地定义所有::before::after伪元素来覆盖它们(JSFiddle示例):

::before, ::after {
    content:'';
}

谢谢您的回复。这更符合我所寻找的内容。但是,有人能想到使用 content: none 而不是 display: none 的用例吗?这个属性的规范似乎非常奇怪。 - D_C
display:none 可以用于这些伪元素上,以隐藏生成的内容而不覆盖它。例如:计数器,只有在悬停在元素上时才可见,无须每次都重新生成。这是一个更新后的 Fiddle(将鼠标悬停在文本上方):http://jsfiddle.net/VzZUz/1/ - James Donnelly
3
在任何情况下,包括计数器,将"display: none" 应用于 ::before 和 ::after 与将 "content: none" 应用效果完全相同。如果你更喜欢使用 "display" 属性来隐藏伪元素,也可以这样做。如果特异性是一个问题,切换到另一个属性也可能会有用。 - BoltClock
1
我不建议在未经限定的选择器中全局启用所有 ::before/::after 伪元素。如果您的页面非常复杂,这将导致浏览器可能会三倍于渲染框的数量,而生成的大多数内容框永远不会使用。content 的初始值为 none 而不是空字符串,理由就在这里。 - BoltClock
1
这可能是Dave一直在寻找的答案。请参见SLaks答案下的评论。您想要补充您的答案还是我发布一个单独详细的答案(请注意,我说“有一个原因”而没有详细说明)? - BoltClock
显示剩余4条评论

25
每个::before和/或::after伪元素都需要一个content: ''声明的原因是,content的初始值为normal,这会计算为在::before::after伪元素上的none。请参见规范content的初始值不是空字符串而是计算为none的值,在::before::after伪元素上的原因有两个:
  1. 在每个元素的开头和结尾具有空内联内容非常愚蠢。请记住,::before::after伪元素的最初目的是在源元素的主要内容之前和之后插入生成的内容。当没有内容可插入时,创建额外的框以插入空内容是毫无意义的。因此,none值告诉浏览器不要费力创建额外的框。

    使用空的::before::after伪元素来仅为布局美观性而创建额外框的做法相对较新,甚至有些纯粹主义者可能会因此将其称为hack。

  2. 在每个元素的开头和结尾添加空内联内容意味着每个(非替换)元素,包括htmlbody,默认情况下会生成不止一个框,而是最多产生三个框(对于已经生成多个框的元素,如具有列表样式的元素,可能会产生更多)。您实际上会使用多少个元素的这两个额外框?那就可能将布局成本增加到三倍,但获得的利益很少。

实际上,即使在本十年内,页面上不到10%的元素需要使用 ::before::after 伪元素来进行布局。

因此,这些伪元素是可选的 - 因为将它们设为默认选项不仅浪费系统资源,而且在原始目的方面也毫无逻辑。性能问题也是我不建议使用 ::before, ::after 为每个元素生成伪元素的原因。

但是您可能会问:为什么不能将display 属性默认设置为 none::before, ::after 上?简单来说:因为 display 的初始值不是 none,而是 inline。在 ::before, ::after 上使 inline 计算为 none 不是一个选项,因为那样你永远无法在行内显示它们。display 的初始值在 ::before, ::after 上设为 none 也不是一个选择,因为属性只能有一个初始值。(这就是为什么 content 的初始值总是 normal 并被定义为在 ::before, ::after 上计算为 none 的原因。)


其他答案都不错,但这个应该是被采纳的答案。它完整地回答了问题。 - Michael Benjamin
下一个问题是:既然content属性仅适用于::before::after伪元素,那么为什么它的初始值是normal,而不是直接将其计算为none?为什么不省略额外的步骤,直接将初始值设为none - Michael Benjamin
2
@Michael_B:我猜他们最初是计划让内容与实际元素一起工作,最终将其推迟到CSS3(propdef说“在元素上,始终计算为'normal'”,即使该属性一开始就不适用于元素)。将初始值设置为none意味着整个页面都会消失......直到您添加html,body,body * {content:normal}。因此,为了允许CSS3的定义,初始值为normal更有意义,然后对于::before,::after来说,它会计算为none,因为通常情况下根本不需要伪元素。 - BoltClock
我把这个改成了被接受的答案。感谢多年后的跟进。 - D_C

9
根据您对其他答案的评论,我认为您的问题实际上是:
为什么伪类的内容属性必须在CSS中设置,而非伪类之外的内容可以在HTML或CSS中设置?
原因是:
- 根据定义,伪类是根据页面的HTML标记动态创建的每个元素。 - 所有页面元素(包括伪类)都必须具有内容属性才能显示。 - 像

这样的HTML元素也是如此,但您可以使用标记(或CSS声明)快速设置其内容属性。 - 但是,与非伪类元素不同,伪类本身不能在标记中赋值。
∴ 因此,所有伪类都是不可见的(它们的“内容”属性没有值),除非您告诉它们不要是这样(通过使用CSS声明给它们赋值)。

以这个简单的页面为例:
<body>
<p> </p>
</body>

我们知道这个页面显示不了任何内容,因为

元素没有文本。更准确的重新表述是,

元素的内容属性没有值

我们可以通过在HTML标记中设置

元素的内容属性来轻松改变这个情况。
<body>
<p>This sentence is the content of the p element.</p>
</body>

这将在加载时显示,因为`

`元素的内容属性具有值;该值为字符串。

"This sentence is the content of the p element."

或者,我们可以通过在CSS中设置<p>元素的内容属性来显示<p>元素:

p { content: "This sentence is the content of the p element set in the CSS."; }

这两种将字符串注入到 <p> 元素中的方法完全相同。

现在,考虑使用伪类完成同样的事情:

HTML:
  <body>
      <p class="text-placeholder">P</p>
  </body>

CSS:
  p:before { content: "BEFORE... " ; }
  p:after { content: " ...and AFTER"; }

结果:
BEFORE...  P ...and AFTER

最后,想象一下如果不使用CSS如何完成这个示例。这是不可能的,因为没有办法在HTML标记中设置伪类的内容。

你可能很有创意,想象一些东西可以像这样工作:

<p:before>BEFORE... </p>
<p> P </p>
<p:after> ...and AFTER</p>

但实际上,<p:before><p:after>并不是HTML元素,所以它们无法显示。

总而言之:

  • 伪类适用于所有标记元素。
  • 它们默认情况下是不可见的,因为它们没有初始化内容属性。
  • 不能使用HTML标记设置伪类的内容属性。
    因此,必须通过CSS声明来声明伪元素的内容属性,以便其显示出来。


1
第一次阅读时,这个答案非常令人困惑,我不得不进行第二次阅读,并将所有“伪类”替换为“伪元素”(并保留所有现有的“伪元素”用法)。请注意,这两个术语不能互换。 - BoltClock

6
直到您添加content: ...,伪元素才真正存在。
设置其他样式属性并不足以迫使浏览器创建该元素。

1
但是为什么需要这个要求呢?难道不是 :after 选择器的存在表明了元素的必要性吗? - D_C
@DaveC content属性的值是您输入要添加为生成元素的内容的位置。如果没有内容,则无需生成元素。 - Marc Audet
我强烈不同意。按照这种逻辑,添加内容:“”也会导致:after标签消失...实际上,这使其显示出来。 - D_C
@DaveC 抱歉,我说错了。如果没有内容属性,则无需生成伪元素。正如您所指出的,伪元素可以具有空字符串作为值。 - Marc Audet
我完全理解它是以那种方式实现的...也许我应该换个说法来提问...为什么规范不假设定义伪元素时它有content: ""呢?假设一个缺失的元素是否有利可图?特别是当任何没有内容的伪元素都是完全无效的样式。我想你可以将其隐藏,并在以后使用不同的样式添加内容: ""; 但是,这种行为可以通过display: none实现。 - D_C

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接