如何检测textarea的行新增/删除事件以同步有序列表行号
实现textarea行数与有序列表行号同步的方案
核心思路
- 放弃基于键盘事件的判断逻辑,键盘事件无法覆盖软换行、粘贴、剪切、拖拽输入、窗口resize等所有会触发行数变化的场景
- 直接通过实时计算textarea实际渲染行数的方式,和当前有序列表元素数量做对比,动态增删行号元素即可
实现步骤
计算行数的核心逻辑要和textarea的渲染规则完全对齐,才能同时覆盖硬换行(\n)和软换行(内容超出宽度自动换行)的场景
- 提前获取textarea的可编辑区域高度、行高属性,避免每次计算重复查询DOM
- 监听
input事件覆盖所有内容变更场景(输入、删除、粘贴、剪切、拖拽输入等) - 监听窗口
resize事件、textarea的尺寸变化事件(若textarea支持拖拽调整大小),覆盖宽度变化导致的软换行数量变化场景 - 每次触发事件时计算当前实际行数,和上一次存储的行数对比:
- 当前行数 > 上一次行数:补充(当前行数-上一次行数)个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
相关产品推荐
相关产品推荐

