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

contenteditable div实时语法高亮时光标位置丢失问题求助

解决contenteditable div实时语法高亮的光标偏移问题

直接替换innerHTML会完全重绘DOM结构,导致浏览器丢失原有光标位置,这就是你每次输入后光标跳转到开头的原因。强行将光标移到末尾的方案只适合在内容末尾编辑,无法满足中间内容编辑的需求。

核心解决思路是:高亮前记录光标在纯文本中的偏移量,高亮完成后根据这个偏移量恢复光标位置,具体实现如下:

const codeElement = document.getElementById('code');

// 保存光标在纯文本中的偏移量
function saveCursorPosition(element) {
  const selection = window.getSelection();
  if (selection.rangeCount === 0) return -1;
  const range = selection.getRangeAt(0);
  const preCaretRange = range.cloneRange();
  preCaretRange.selectNodeContents(element);
  preCaretRange.setEnd(range.endContainer, range.endOffset);
  return preCaretRange.toString().length;
}

// 根据偏移量恢复光标位置
function restoreCursorPosition(element, position) {
  const selection = window.getSelection();
  const range = document.createRange();
  // 递归遍历DOM,找到对应偏移量的文本节点
  function findTextNodeOffset(node, remainingOffset) {
    if (node.nodeType === Node.TEXT_NODE) {
      if (node.length >= remainingOffset) {
        return { node, offset: remainingOffset };
      } else {
        remainingOffset -= node.length;
      }
    } else {
      for (let child of node.childNodes) {
        const result = findTextNodeOffset(child, remainingOffset);
        if (result) return result;
      }
    }
    return null;
  }

  const result = findTextNodeOffset(element, position);
  if (result) {
    range.setStart(result.node, result.offset);
    range.collapse(true);
    selection.removeAllRanges();
    selection.addRange(range);
  }
}

// 实时高亮函数
function highlight() {
  // 先保存当前光标位置
  const cursorPos = saveCursorPosition(codeElement);
  if (cursorPos === -1) return;

  const text = codeElement.textContent;
  // 优化正则规则,减少误匹配
  const controlRegex = /\b(function|const|let)\b/g;
  const stringRegex = /["'].*?["']/g; // 非贪婪匹配,避免跨字符串匹配
  const functionCallRegex = /[a-zA-Z_\d]+\([^)]*\)/g; // 匹配有效函数调用格式
  const constantRegex = /[a-zA-Z_\d]+\./g; // 匹配带点的常量(如Math.)

  let highlightedHTML = text.replace(stringRegex,
    '<span style="color: #0d0; font-weight: normal;">$&</span>')
    .replace(controlRegex, '<span style="color: pink;">$&</span>')
    .replace(functionCallRegex, '<span style="color: #0bb; font-weight: bold;">$&</span>')
    .replace(constantRegex, '<span style="color: yellow;">$&</span>');

  codeElement.innerHTML = highlightedHTML;

  // 恢复光标位置
  restoreCursorPosition(codeElement, cursorPos);
}

codeElement.addEventListener('input', highlight);
highlight();

关键说明

  1. 光标位置记录:saveCursorPosition通过计算光标到元素起始位置的纯文本长度,得到一个与DOM结构无关的偏移量,确保高亮后能准确对应。
  2. 光标位置恢复:restoreCursorPosition递归遍历高亮后的DOM节点,解决了span嵌套导致的直接定位失败问题,能精准找到对应偏移的文本节点并定位光标。
  3. 正则优化:将字符串匹配改为非贪婪模式,避免错误匹配多个连续字符串;调整其他正则的匹配规则,减少不必要的误高亮。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 15:25:20