You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测textarea的行新增/删除事件以同步有序列表行号

实现textarea行数与有序列表行号同步的方案

核心思路

  • 放弃基于键盘事件的判断逻辑,键盘事件无法覆盖软换行、粘贴、剪切、拖拽输入、窗口resize等所有会触发行数变化的场景
  • 直接通过实时计算textarea实际渲染行数的方式,和当前有序列表元素数量做对比,动态增删行号元素即可

实现步骤

计算行数的核心逻辑要和textarea的渲染规则完全对齐,才能同时覆盖硬换行(\n)和软换行(内容超出宽度自动换行)的场景

  1. 提前获取textarea的可编辑区域高度、行高属性,避免每次计算重复查询DOM
  2. 监听input事件覆盖所有内容变更场景(输入、删除、粘贴、剪切、拖拽输入等)
  3. 监听窗口resize事件、textarea的尺寸变化事件(若textarea支持拖拽调整大小),覆盖宽度变化导致的软换行数量变化场景
  4. 每次触发事件时计算当前实际行数,和上一次存储的行数对比:
    • 当前行数 > 上一次行数:补充(当前行数-上一次行数)个li元素到有序列表
    • 当前行数 < 上一次行数:删除有序列表末尾的(上一次行数-当前行数)个li元素

完整实现代码

基础HTML结构

<div class="editor-wrap" style="display: flex; align-items: flex-start;">
  <ol id="lineNumList" style="margin: 0; padding-right: 12px; list-style-position: inside;"></ol>
  <textarea id="objtext" style="font-size: 14px; line-height: 22px; padding: 8px; border: 1px solid #eee; width: 500px; resize: vertical;"></textarea>
</div>

JS逻辑

const objtext = document.getElementById('objtext');
const lineNumList = document.getElementById('lineNumList');
// 存储上一次的行数,初始化默认1行
let lastLineCount = 1;
// 提前缓存计算需要的固定样式值,避免重复查询DOM
const computedStyle = window.getComputedStyle(objtext);
const lineHeight = parseInt(computedStyle.lineHeight);
const paddingTop = parseInt(computedStyle.paddingTop);
const paddingBottom = parseInt(computedStyle.paddingBottom);

// 计算当前实际行数的方法,同时兼容硬换行、软换行
function getCurrentLineCount() {
  // scrollHeight为textarea内容总高度,减去上下内边距后除以行高,取整即为实际行数
  const contentHeight = objtext.scrollHeight - paddingTop - paddingBottom;
  return Math.ceil(contentHeight / lineHeight);
}

// 同步有序列表行号的方法
function syncLineNum() {
  const currentLineCount = getCurrentLineCount();
  // 行数无变化直接跳过
  if (currentLineCount === lastLineCount) return;

  if (currentLineCount > lastLineCount) {
    // 新增行号元素
    const addCount = currentLineCount - lastLineCount;
    for (let i = 0; i < addCount; i++) {
      const li = document.createElement('li');
      li.textContent = lastLineCount + i + 1;
      lineNumList.appendChild(li);
    }
  } else {
    // 删除多余行号元素
    const removeCount = lastLineCount - currentLineCount;
    for (let i = 0; i < removeCount; i++) {
      lineNumList.removeChild(lineNumList.lastChild);
    }
  }
  // 更新存储的行数
  lastLineCount = currentLineCount;
}

// 绑定事件监听
objtext.addEventListener('input', syncLineNum);
window.addEventListener('resize', syncLineNum);
// 监听textarea尺寸变化,覆盖用户拖拽调整textarea大小的场景
const resizeObserver = new ResizeObserver(syncLineNum);
resizeObserver.observe(objtext);

// 初始化渲染第一行行号
syncLineNum();

注意事项

  • 需保证textarea的line-height设置为固定像素值,避免使用相对单位产生计算误差
  • 若需要实现行号和textarea内容滚动同步,额外给textarea绑定scroll事件,设置lineNumList.scrollTop = objtext.scrollTop即可

内容的提问来源于stack exchange,提问作者Kitty Craft0

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 13:18:04