纯css行号需用counter-reset/counter-increment配合display:block和::before伪元素实现,但易受white-space、br、tab等影响;prism.js通过js解析换行符并注入dom节点更可靠,支持动态内容与懒加载。

用 pre + code 配合 CSS 实现最简行号
纯 HTML/CSS 能做行号,但不是靠标签本身——<pre class="brush:php;toolbar:false;"></pre> 和 <code> 只负责保留格式和语义,行号得靠 CSS 计数器(counter-reset/counter-increment)模拟。浏览器不原生支持“给每行自动加编号”,所以必须把换行当作独立元素来处理,而 pre 里的文本默认是连续的,得用 white-space: pre 保持换行,再让每一行被 ::before 伪元素注入序号。
常见错误是直接对 pre 设置 counter-reset,却不把每行变成可计数的块级上下文——结果只显示一个“1”。正确做法是让 code 内的换行符生效,并用 line-height 和 display: block 配合伪元素逐行生成编号。
- 必须给
code设置display: block,否则::before无法按行触发 - 用
counter-increment: line放在code::before里无效,要放在code span或换行包裹的div上;更稳妥的是把每行手动写成<span>...</span> - 如果源码来自后端或用户输入,别依赖 JS 插入
span,优先用 Prism.js 或 highlight.js 这类已处理好行号的库
Prism.js 的 line-numbers 插件为什么比手写 CSS 更可靠
Prism.js 的 line-numbers 插件不是靠 CSS 计数器,而是解析 DOM 后,在 <code> 外层插入一个绝对定位的 <div class="line-numbers-rows">,里面塞满 <code><span></span>,数量与代码行数一致。它能准确响应动态内容、懒加载、甚至 v-for 渲染后的代码块。
手写 CSS 行号在以下场景会失效:white-space: pre-wrap 导致折行误计数、含 <br> 的内联 HTML、使用 tab 缩进但未统一转空格。Prism 则先 normalize 换行符(统一为 \n),再 split 计算行数,最后用 JS 注入对应高度的 span,保证左侧 gutter 高度始终匹配代码区。
- 启用方式:引入
prism-line-numbers.css和prism-line-numbers.js,再给<code>加class="js line-numbers" - 注意:不要同时用
prism-okaidia.css和prism-line-numbers.css,后者已含基础样式,重复引入可能覆盖行号颜色 - 若代码块是异步加载(如 Markdown 渲染后),需手动调用
Prism.highlightAll(),否则行号不出现
highlight.js 默认不带行号,得用 plugin-line-numbers
highlight.js 从 v11 开始移除了内置行号功能,必须额外加载官方插件 @highlightjs/vue-plugin-line-numbers(Vue)或 highlightjs-line-numbers.js(原生)。它不像 Prism 那样注入 DOM 节点,而是通过 after:highlightElement 钩子,在高亮完成后往 el.parentNode 插入一个 <div> 作为行号容器,再用 JS 计算每行 <code>offsetTop 和 offsetHeight 来对齐。
这种方案对字体、行高、padding 更敏感。如果代码块用了非标准 line-height: 1.4 或 font-size: 13px,行号位置容易偏移。解决方法是在插件初始化时传入 singleLine: false 和 fullWidth: true,并确保父容器 position: relative。
- CDN 引入顺序不能错:
highlight.min.js→highlightjs-line-numbers.min.js→init() - 调用
hljs.lineNumbersBlock(document.querySelector('code'))时,目标code必须已渲染且可见,否则getBoundingClientRect()返回 0 - 不支持
async渲染的代码块,比如用innerHTML动态插入后立刻调用,得加setTimeout(..., 0)等 DOM 更新完成
VS Code 风格编辑器行号(gutter)没法用纯 CSS 模拟
真正编辑器的行号 gutter(比如 VS Code 左侧数字列)是独立滚动区域,代码区横向滚动时行号不动,还能点击跳转、高亮当前行、显示断点图标。这需要两个同步滚动的容器:gutter(固定宽度、position: sticky)和 code-area(overflow-x: auto),再用 JS 监听 scrollLeft 做联动。
很多前端组件(如 react-simple-code-editor、monaco-editor)都用这个模式。如果你只是想在文档里展示带行号的代码片段,用 Prism 就够了;但要做可编辑、可交互、支持折叠/断点的代码区,就必须上真实编辑器,而不是在 pre 上堆 CSS。
最容易被忽略的一点:行号的“可访问性”。屏幕阅读器不会读取 CSS 伪元素生成的数字,也不会理解 aria-hidden="true" 的行号列。如果项目有无障碍要求,得给行号容器加 role="region" 和 aria-label="Line numbers",并确保键盘焦点能进入代码区而非卡在 gutter 上。











