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

如何获取textarea、input及contenteditable div中文本位置(类Grammarly)

实现文本元素中特定单词的位置获取(类似Grammarly)

要获取input、textarea或contenteditable="true"的div里目标单词的left/top坐标,核心是先定位文本范围,再将其转化为页面绝对位置。以下分场景给出具体实现:

1. 处理 input/textarea 表单控件

这类控件无法直接用Range API操作文本,我们可以通过克隆元素模拟布局的方式计算位置:

function getWordPositionInInput(element, targetWord) {
  const fullText = element.value;
  const wordStartIdx = fullText.indexOf(targetWord);
  if (wordStartIdx === -1) return null;

  // 创建和原元素样式完全一致的克隆体,隐藏在页面外
  const clone = element.cloneNode(false);
  Object.assign(clone.style, {
    position: 'absolute',
    visibility: 'hidden',
    top: '0',
    left: '0',
    // 确保继承原元素的所有样式,避免布局偏差
    font: getComputedStyle(element).font,
    padding: getComputedStyle(element).padding,
    border: getComputedStyle(element).border,
    boxSizing: getComputedStyle(element).boxSizing
  });
  // 填充目标单词之前的文本,用来计算起始位置的宽度
  clone.value = fullText.slice(0, wordStartIdx);
  document.body.appendChild(clone);

  // 计算最终位置:原元素的页面坐标 + 克隆体的宽度(即单词起始left)
  const elementRect = element.getBoundingClientRect();
  const cloneWidth = clone.getBoundingClientRect().width;
  const position = {
    top: elementRect.top + window.scrollY,
    left: elementRect.left + cloneWidth + window.scrollX,
    // 可选:获取单词本身的宽度
    wordWidth: (clone.value = targetWord).getBoundingClientRect().width
  };

  document.body.removeChild(clone);
  return position;
}

2. 处理 contenteditable="true" 的 div

这类元素支持原生Range API,可以直接定位文本范围并获取坐标:

function getWordPositionInContentEditable(element, targetWord) {
  // 递归遍历元素,找到包含目标单词的文本节点
  function findTextNode(node) {
    if (node.nodeType === Node.TEXT_NODE) {
      if (node.textContent.includes(targetWord)) return node;
    }
    for (const child of node.childNodes) {
      const result = findTextNode(child);
      if (result) return result;
    }
    return null;
  }

  const textNode = findTextNode(element);
  if (!textNode) return null;

  const text = textNode.textContent;
  const wordStartIdx = text.indexOf(targetWord);
  if (wordStartIdx === -1) return null;

  // 创建Range并选中目标单词
  const range = document.createRange();
  range.setStart(textNode, wordStartIdx);
  range.setEnd(textNode, wordStartIdx + targetWord.length);

  // 获取单词的页面坐标(加上滚动偏移量得到绝对位置)
  const rect = range.getBoundingClientRect();
  return {
    top: rect.top + window.scrollY,
    left: rect.left + window.scrollX,
    width: rect.width,
    height: rect.height
  };
}

3. 统一调用入口

封装一个函数,自动识别元素类型并调用对应逻辑:

function getWordPosition(element, targetWord) {
  if (!element || typeof targetWord !== 'string' || targetWord.length === 0) {
    return null;
  }

  const tagName = element.tagName.toLowerCase();
  if (tagName === 'input' || tagName === 'textarea') {
    return getWordPositionInInput(element, targetWord);
  } else if (element.isContentEditable) {
    return getWordPositionInContentEditable(element, targetWord);
  }
  return null;
}

关键注意事项

  • 克隆input/textarea时,必须完全同步原元素的样式(字体、行高、内边距等),否则位置计算会出现偏差
  • 对于嵌套复杂的contenteditable div,递归遍历文本节点的逻辑可以确保找到目标单词
  • 页面滚动时,必须加上window.scrollX和window.scrollY,将相对视口的坐标转为页面绝对坐标
  • 上述代码默认返回目标单词第一次出现的位置,若需要获取所有出现位置,只需遍历文本中所有匹配的索引即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 15:17:42