仅使用CSS在pre标签中创建行号

78

我试图使用CSS样式来为每行前面带有行号的代码pre框添加样式。这是我的做法:

pre {
  background: #303030;
  color: #f1f1f1;
  padding: 10px 16px;
  border-radius: 2px;
  border-top: 4px solid #00aeef;
  -moz-box-shadow: inset 0 0 10px #000;
  box-shadow: inset 0 0 10px #000;
}
pre span {
  display: block;
  line-height: 1.5rem;
}
pre span:before {
  counter-increment: line;
  content: counter(line);
  display: inline-block;
  border-right: 1px solid #ddd;
  padding: 0 .5em;
  margin-right: .5em;
  color: #888
}
<pre>
  <span>lorem ipsum</span>
  <span>&gt;&gt; lorem ipsum</span>
  <span>lorem ipsum,\ </span>
  <span>lorem ipsum.</span>
  <span>&gt;&gt; lorem ipsum</span>
  <span>lorem ipsum</span>
  <span>lorem ipsum</span>
  <span>lorem ipsum</span>
</pre>

然而,所有的行都有数字1。递增功能不起作用。这里有一个jsfiddle

  1. 我做错了什么?
  2. 这种只使用CSS的解决方案与浏览器兼容性如何?

6
为什么不使用 <OL> 标签? - Graham
1
@Graham 如果我告诉你我完全忘记了,你会相信我吗?:P :P - Tasos
4个回答

96

计数器为什么没有增加?

您在父级标签层面没有重置或创建计数器。如果您将以下行添加到 pre 选择器中,代码将正常工作。当您不在父级层面创建计数器(使用 counter-reset)时,每个元素都会创建自己的计数器,然后递增它。

counter-reset: line;

计数器是什么时候创建的?

来自 W3C 规范

counter-reset 属性在一个元素上创建新的计数器。

counter-set 和 counter-increment 属性可以操作现有计数器的值。只有当元素上不存在给定名称的计数器时,它们才会创建新的计数器。

在本例中,由于我们没有使用 counter-reset 属性来创建计数器,所以在 span:before 伪元素选择器中使用 counter-increment 属性将会创建给定名称的计数器并对其进行增量递增。


计数器如何获取当前值?

再次来自W3C 规范

如果一个元素具有前一个同级元素,则必须继承该同级元素的所有计数器。否则,如果该元素具有父元素,则必须继承该父元素的所有计数器。否则,该元素必须具有一组空计数器。

然后,该元素从文档顺序中的紧随其后的元素继承计数器值。

在本例中,由于计数器仅在伪元素中创建,其父级(即 span)不知道其创建情况,因此该 span 的同级元素不会继承该计数器。由于它甚至没有继承任何计数器,因此也不会从前面的元素中获取当前值。


为什么在父级上创建计数器可以工作?

当在 pre 标签级别创建计数器时,该计数器将传递给其每个子元素(即每个 span,依次每个 span:before 将知道或继承此计数器),现在 span:before 中的增量语句只会增加从父级继承的计数器的值。

现在,由于每个 span 继承其前一个同级元素的 line 计数器,因此它们还将从文档顺序中的前一个元素中继承当前值,从而从 1 到 2、3 等继续上升。


为什么在 span 或者 pre 上使用 counter-increment 可以工作?

正如你所猜测的,counter-increment属性在不存在计数器时会创建一个新的计数器,因此将counter-increment: line添加到span上将在遇到第一个时创建计数器。现在,由于每个兄弟元素都继承了该计数器,它不会每次创建一个新的计数器,而是从前面的元素继承值。因此这种方法可以工作,但最好还是使用counter-reset语句显式地创建计数器。


浏览器支持情况如何?

CSS计数器的浏览器支持非常好。这不是CSS中的新功能,即使在IE8中也支持它


演示:

pre {
  background: #303030;
  color: #f1f1f1;
  padding: 10px 16px;
  border-radius: 2px;
  border-top: 4px solid #00aeef;
  -moz-box-shadow: inset 0 0 10px #000;
  box-shadow: inset 0 0 10px #000;
  counter-reset: line;
}
pre span {
  display: block;
  line-height: 1.5rem;
}
pre span:before {
  counter-increment: line;
  content: counter(line);
  display: inline-block;
  border-right: 1px solid #ddd;
  padding: 0 .5em;
  margin-right: .5em;
  color: #888
}
<pre><span>lorem ipsum</span>
<span>&gt;&gt; lorem ipsum</span>
<span>lorem ipsum,\ </span>
<span>lorem ipsum.</span>
<span>&gt;&gt; lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
<span>lorem ipsum</span>
</pre>


2
很好;我总是错误地认为我们必须在非伪元素上定义counter-increment;我完全没有意识到在父元素(包裹“计数元素”的元素)上使用counter-reset也可以起作用。 CSS的不断奥秘! :) - David Thomas
1
@DavidThomas:我终于完全填充好了。提醒一下你,如果你有兴趣的话 :) - Harry
1
如何进行优化,使得当行号宽度不同时,所有的条形图垂直对齐更好? - Max Peng
@MaxPeng:给伪元素一个足够宽的width以容纳所有可能的编号值,这样它们就可以更好地对齐(就像这里一样)。但问题是你必须根据预期的最大值分配一个任意的宽度。 - Harry

4
你需要在你的中增加line
pre span {
    display: block;
    line-height: 1.5rem;
    counter-increment: line;
}

看一下这个更新的jsfiddle

它能够工作,但是当我的<span>计数超过10,000时,需要几十秒的时间。 - vikyd

2

2

你可以考虑使用有序列表<ol>代替<pre>,它已经带有右对齐的编号(当数字位数跳跃时更好看,例如9 -> 10)。 <li>也不需要闭合标签</li>https://html.spec.whatwg.org/multipage/syntax.html#optional-tags),这样可以节省打字。

ol {
  font-family: monospace;
  white-space: pre; 
}

li::marker {
  font-size: 10px;
  color: grey;
}
<ol><li>lorem ipsum      
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum,\ 
<li>lorem ipsum.
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum
<li>lorem ipsum
<li>lorem      
<li>ipsum
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum
</ol> 

然而,::marker 样式似乎受限(list-style-type)。例如,去掉句号或使用 vertical-align: super 似乎需要其他解决方法(回到 li:beforecounter)。


1
谢谢 - 这也解决了一个问题,即我希望我的用户能够选择文本行而不选择数字。 - drchuck
这是一个不完整的解决方案。将代码标记为有序列表是不可访问的,因为它将被屏幕阅读器呈现为一个带有项目编号的列表,而不是一个带有行号的代码块。 - undefined

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