如何为textarea编辑器实现跳转到行和查找文本功能
修复textarea编辑器的跳转到行与查找文本功能
你的代码存在几个关键问题导致跳转和查找功能失效,以下是具体问题分析和修复后的完整实现:
问题分析
- 行高计算错误:原代码用
ta.scrollHeight / ta.clientHeight得到的是滚动比例,而非单行实际高度,应该直接使用CSS定义的line-height值或动态计算单行高度。 - 变量作用域问题:查找功能中使用的
lineHeight变量仅在跳转行的函数内定义,导致查找时因变量未定义无法计算滚动位置。 - 行号计算逻辑错误:查找文本时用字符索引除以第一行长度计算行号,完全不符合实际(每行长度可能不同),需遍历行数组定位目标行。
- 输入校验缺失:未判断输入为空或非有效数字的情况,空输入会触发无效操作。
- 内容获取错误:查找时用
ta.textContent获取内容,textarea的实际内容应通过ta.value获取。
修复后的完整代码
HTML
<div class="actions"> <div class="goto-line-prompt" style="display: none;"> <h3>跳转到行</h3> <input type="number" placeholder="输入行号:" min="1" /> <button type="button" class="ok-button">确定</button> </div> <div class="find-prompt" style="display: none;"> <h3>查找文本</h3> <input type="text" placeholder="输入要查找的内容:" /> <button type="button" class="ok-button">确定</button> </div> <button class="goto-line-button">跳转到行</button> <button class="find-button">查找文本</button> </div> <div class="editor"> <div class="numbers"> <span></span> </div> <textarea wrap="off" onkeyup="updateRowNumbering()"></textarea> </div>
CSS
body, textarea { font-family: Consolas, "Courier New", Courier, monospace; } .actions { margin-bottom: 10px; } .goto-line-prompt, .find-prompt { margin: 10px 0; padding: 10px; border: 1px solid #ccc; border-radius: 4px; } .goto-line-prompt input, .find-prompt input { margin: 0 10px; padding: 4px; } .editor { display: inline-grid; grid-template-columns: 3em auto; gap: 10px; line-height: 21px; background: rgb(40 42 58); border-radius: 2px; overflow-y: auto; max-height: 250px; } .editor>* { padding-top: 10px; padding-bottom: 10px; } .numbers { text-align: right; background: #333; padding-right: 5px; } .numbers span { counter-increment: linenumber; } .numbers span::before { content: counter(linenumber); display: block; color: #888; } textarea { line-height: 21px; border: 0; background: transparent; color: #fff; min-width: 500px; outline: none; resize: none; padding-right: 10px; }
JavaScript
const ta = document.querySelector('textarea'); const numbers = ta.parentNode.querySelector('.numbers'); const gotoLinePrompt = document.querySelector('.goto-line-prompt'); const findPrompt = document.querySelector('.find-prompt'); // 更新行号 function updateRowNumbering() { const lines = ta.value.split("\n"); numbers.innerHTML = Array(lines.length).fill("<span></span>").join(""); } // 初始化示例内容 ta.value = `One Two Three Four Five Six Seven Eight Nine Ten Eleven Kumi na Mbili `; updateRowNumbering(); // 默认隐藏弹窗 gotoLinePrompt.style.display = 'none'; findPrompt.style.display = 'none'; // 打开跳转行弹窗 document.querySelector('.goto-line-button').addEventListener('click', () => { gotoLinePrompt.style.display = 'block'; gotoLinePrompt.querySelector('input').focus(); }); // 打开查找弹窗 document.querySelector('.find-button').addEventListener('click', () => { findPrompt.style.display = 'block'; findPrompt.querySelector('input').focus(); }); // 跳转行逻辑 gotoLinePrompt.querySelector('.ok-button').addEventListener('click', () => { const input = gotoLinePrompt.querySelector('input'); const lineNumber = parseInt(input.value); const totalLines = ta.value.split("\n").length; if (!isNaN(lineNumber) && lineNumber >= 1 && lineNumber <= totalLines) { const lineHeight = 21; // 对应CSS中的line-height值 ta.scrollTop = (lineNumber - 1) * lineHeight + 10; // 加10是编辑器顶部内边距 numbers.scrollTop = ta.scrollTop; // 定位光标到目标行开头 const lines = ta.value.split("\n"); let pos = 0; for (let i = 0; i < lineNumber - 1; i++) pos += lines[i].length + 1; ta.setSelectionRange(pos, pos); ta.focus(); } input.value = ''; gotoLinePrompt.style.display = 'none'; }); // 查找文本逻辑 findPrompt.querySelector('.ok-button').addEventListener('click', () => { const input = findPrompt.querySelector('input'); const searchTerm = input.value.trim(); if (searchTerm) { const lines = ta.value.split("\n"); let targetLine = -1; let termIndex = -1; // 遍历找到第一个包含搜索词的行 for (let i = 0; i < lines.length; i++) { const index = lines[i].indexOf(searchTerm); if (index !== -1) { targetLine = i + 1; termIndex = index; break; } } if (targetLine !== -1) { const lineHeight = 21; ta.scrollTop = (targetLine - 1) * lineHeight + 10; numbers.scrollTop = ta.scrollTop; // 定位光标到搜索词位置 let pos = 0; for (let i = 0; i < targetLine - 1; i++) pos += lines[i].length + 1; ta.setSelectionRange(pos + termIndex, pos + termIndex + searchTerm.length); ta.focus(); } } input.value = ''; findPrompt.style.display = 'none'; });
额外优化点
- 给行号输入框加了
min="1"限制,避免无效输入 - 弹窗打开时自动聚焦输入框,提升操作体验
- 跳转或找到文本后自动定位光标并聚焦textarea
- 增加了输入内容的合法性校验
- 确保行号区域和textarea滚动同步
内容的提问来源于stack exchange,提问作者Abednego Nasila
相关产品推荐
相关产品推荐

