本文介绍一种无需 javascript 动态计算偏移量、仅用 css grid 即可让行号与 textarea 内容完美对齐并同步滚动的专业方案,解决传统 flex + 定位方式中行号错位、滚动不同步等核心问题。
本文介绍一种无需 javascript 动态计算偏移量、仅用 css grid 即可让行号与 textarea 内容完美对齐并同步滚动的专业方案,解决传统 flex + 定位方式中行号错位、滚动不同步等核心问题。
在构建类代码编辑器的 UI 时,一个常见但易被低估的挑战是:如何让左侧行号(line numbers)始终与 textarea 中的每一行文本严格垂直对齐,且在滚动、增删行、自动换行等操作下保持同步? 原始方案试图通过 top 偏移和手动计算行高来“模拟”对齐,但因 textarea 的滚动容器行为与绝对定位元素的脱离,导致行号反向漂移——这本质上是布局模型误用所致。
✅ 正确解法:使用 display: grid 替代 inline-flex,借助网格天然的行列对齐能力,让 .numbers 和 <textarea> 成为同一网格中的并列轨道(grid-template-columns: 3em auto),二者共享相同的滚动容器与行高基准。
✅ 核心优势
- 零 JS 对齐逻辑:不再需要 numbers.style.top = offset + "px" 等脆弱计算;
- 原生滚动同步:.editor 作为统一滚动容器,.numbers 与 textarea 共享 scroll offset,行号自动跟随内容滚动;
- 响应式健壮:行高变化、字体缩放、动态内容更新均不影响对齐;
- 语义清晰:Grid 明确表达了“行号列 + 编辑区列”的二维结构关系。
✅ 实现要点
- HTML 结构保持简洁:<div class="editor"> 包裹两个直接子元素(.numbers 和 <textarea>);
-
CSS 关键声明:
.editor { display: inline-grid; grid-template-columns: 3em auto; /* 固定行号列宽,剩余给编辑区 */ gap: 10px; overflow-y: auto; /* 滚动由父容器统一控制 */ max-height: 250px; } .numbers, textarea { line-height: 21px; /* 必须一致!这是对齐的物理基础 */ padding-top: 10px; padding-bottom: 10px; } -
JavaScript 仅负责更新行号数量(非位置):
function updateRowNumbering() { const ta = document.querySelector('textarea'); const num = ta.value.split('\n').length; const numbers = ta.parentNode.querySelector('.numbers'); numbers.innerHTML = Array(num).fill('<span></span>').join(''); } // 绑定事件(推荐用 addEventListener 替代内联 onkeyup) document.querySelector('textarea').addEventListener('input', updateRowNumbering);
⚠️ 注意事项
- 禁用 textarea 自身滚动:textarea { overflow-y: hidden; }(已隐含在 Grid 布局中,但显式声明更安全);
- 避免设置 position: sticky 或 absolute:会破坏 Grid 的流式对齐;
- 确保 line-height 在 .editor、.numbers、textarea 三者中完全一致(如均为 21px),否则视觉错位不可避免;
- 行号生成使用 CSS 计数器(counter-increment + ::before),性能优于纯 JS 插入文本。
✅ 最终效果
无论用户在底部追加 100 行、快速滚动、还是粘贴大段代码,行号都会像“影子”一样精准附着于对应文本行左侧——无需计算、不依赖 scroll 事件监听、无重绘抖动。这是一种符合现代 CSS 布局哲学的优雅解法:用正确的工具(Grid)解决正确的问题(二维对齐),而非用复杂逻辑修补错误的抽象(Flex + 手动定位)。


















