CSS计数器是为<pre><code>添加行号的可靠方式,需三步:重置counter-reset、用::before伪元素生成行号、保持white-space: pre;须避免HTML标签干扰、配合prism-line-numbers插件,并用user-select: none兼顾可复制性与美观。

行号需要靠CSS计数器实现,不是HTML原生支持
HTML本身没有<ol>以外的行号机制,<pre><code>结构默认不带行号。强行用<ol>包裹每行会破坏语法高亮兼容性(比如Prism、Highlight.js依赖纯文本节点),也难适配缩进和换行。真正可靠的方式是用CSS ::before伪元素 + counter()生成行号。
给<pre><code>添加行号的最小可行代码
核心是三步:重置计数器、为每行设置伪元素、用white-space: pre保持换行。注意<code>内不能有HTML标签(否则line-height计算错),所有换行必须是真实\n。
<style>
.line-numbers {
counter-reset: line;
}
.line-numbers .line::before {
counter-increment: line;
content: counter(line);
display: inline-block;
width: 3em;
text-align: right;
color: #999;
margin-right: 1em;
}
</style>
<pre class="line-numbers"><code class="line">console.log('hello');</code><code class="line">const x = 42;</code></pre>
关键点:
-
counter-reset必须设在<pre>上,否则多代码块会共用计数器 - 每行用单独
<code class="line">,不能用<br>或\n模拟换行——CSS计数器对\n不触发新行 -
width: 3em要够显示最大行号(比如999行需至少4em)
配合Syntax Highlighter(如Prism.js)时的坑
Prism默认把代码转成嵌套<span>,破坏.line结构。必须改用prism-line-numbers插件,且初始化顺序不能错:
立即学习“前端免费学习笔记(深入)”;
- 先加载
prism.js和prism-line-numbers.js - 再加
class="line-numbers"到<pre>(不是<code>) - 确保
<code>里是纯文本,无多余空格或换行符——Prism会自动按\n切行 - 如果行号错位,大概率是
line-height没继承到伪元素,加line-height: inherit到.line::before
行号对齐和可复制性的取舍
行号本质是装饰性内容,不能被选中或复制——这是故意设计。如果用户需要复制带行号的代码(比如教学场景),就得用JavaScript动态插入<span>,但会增大DOM体积且影响性能:
const codeEl = document.querySelector('code');
const lines = codeEl.textContent.split('\n');
codeEl.innerHTML = lines.map((line, i) =>
`<span class="line-number">${i+1}</span>${line}`
).join('\n');
这种方案的问题:
- 破坏原始
textContent,后续语法高亮可能失效 - 行号变成真实DOM节点,复制时会混入数字
- 响应式缩放时,
span宽度难控制,容易折行
真正要兼顾可复制和美观,得用user-select: none锁住行号区域,只让代码区可选——这才是平衡点。



















