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

如何为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 03:02:09