
本文介绍一种基于 css grid 的轻量级解决方案,无需 javascript 动态计算偏移量,即可让行号区域与 textarea 内容自然同步滚动,彻底解决传统 flex + 定位方案中行号错位、滚动不同步的问题。
本文介绍一种基于 css grid 的轻量级解决方案,无需 javascript 动态计算偏移量,即可让行号区域与 textarea 内容自然同步滚动,彻底解决传统 flex + 定位方案中行号错位、滚动不同步的问题。
要构建一个视觉上精准对齐、滚动行为完全一致的带行号编辑器,关键在于放弃手动控制 .numbers 元素位置(如 top 偏移)的思路——这类方案极易因行高计算误差、滚动事件时机、textarea 内部渲染机制等原因导致错位,尤其在快速输入、粘贴多行或触底新增内容时表现异常。
✅ 正确解法是利用 CSS Grid 的隐式对齐能力:将 .numbers 和 <textarea> 置于同一网格容器中,共享相同的行高(line-height)和垂直内边距,使浏览器原生将二者按行“对齐”为一组单元格。此时,滚动容器(.editor)的 overflow-y: auto 会同时作用于两个子元素,确保它们以完全相同的滚动偏移量移动。
以下是核心实现要点:
1. 使用 display: grid 替代 inline-flex
.editor {
display: inline-grid;
grid-template-columns: 3em auto; /* 固定宽度行号列 + 自适应文本列 */
gap: 10px;
line-height: 21px; /* 统一基准行高 */
max-height: 250px;
overflow-y: auto;
background: #282a3a;
border-radius: 2px;
}⚠️ 注意:grid-template-columns 明确划分列宽,避免 flex 中 align-items 或 justify-content 引发的不可控对齐。
2. 移除所有手动定位逻辑
删除原代码中 numbers.style.top = offset + "px" 和高度动态设置。行号容器 .numbers 不再需要 position: sticky 或任何 top/transform 操作——它只需作为普通网格项存在:
.numbers {
text-align: right;
background: #333;
padding-right: 5px;
/* ❌ 删除:position: sticky; top: 0; */
}3. 行号生成保持简洁(纯 CSS 计数器)
JavaScript 仅负责生成对应数量的 <span> 占位符,样式由 CSS counter-increment 和 ::before 自动渲染:
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('');
}配合 CSS:
.numbers span {
counter-increment: linenumber;
}
.numbers span::before {
content: counter(linenumber);
display: block;
color: #888;
}4. textarea 样式优化
- 移除 overflow-y: hidden(交由父容器统一控制滚动)
- 背景设为 transparent,避免遮挡行号背景
- 统一 padding-top/padding-bottom(推荐通过 .editor > * 统一设置),确保每行文字基线对齐
.editor > * {
padding-top: 10px;
padding-bottom: 10px;
}
textarea {
background: transparent;
border: 0;
color: #fff;
outline: none;
resize: none;
padding-right: 10px;
}✅ 最终效果优势
- 零 JS 偏移计算:无 scrollTop、offsetHeight 等易出错操作
- 完美滚动同步:Grid 天然保证两列滚动帧率、起始位置、惯性行为完全一致
- 响应式友好:行高变更、字体缩放、窗口重绘均自动适配
- 性能优异:避免频繁 DOM 样式重排(style.top 触发 layout)
? 提示:若需支持空行计数(如末尾换行符),可将 split('\n') 改为 match(/\n/g)?.length || 0 + 1,但通常 split 已满足绝大多数场景。
该方案已被广泛应用于 CodeMirror、Monaco 等编辑器的轻量级行号实现中——用标准 CSS 解决复杂对齐问题,正是现代 Web 开发「少即是多」的典范实践。

















