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

contenteditable的code元素结合highlight.js输入文本反转问题求助

解决方案:contenteditable + highlight.js 实时高亮无异常实现

核心问题原因

直接在input事件中调用hljs.highlightElement()会完全重写<code>标签的内部HTML,破坏原生编辑的光标状态、换行逻辑,进而出现光标错位、输入反转、无法换行等问题。

具体解决步骤


1. 缓存并恢复光标位置

执行高亮前先记录当前光标在文本中的偏移量,高亮完成后根据偏移量重新定位光标,避免光标丢失或错位。

2. 适配换行逻辑

给<code>标签添加CSS样式,确保原生换行和手动输入的回车能正常显示;同时在高亮处理时保留换行符对应的格式。

3. 防抖优化

对高亮操作添加防抖,避免输入时频繁触发重渲染,提升编辑流畅度。

完整实现代码

<div class="code-editor">
  <pre><code contenteditable="true" class="language-javascript">// 在这里输入代码试试
function test() {
  console.log("hello");
}
</code></pre>
</div>

<style>
.code-editor pre {
  background: #f5f5f5;
  padding: 1rem;
  border-radius: 4px;
}
.code-editor code[contenteditable="true"] {
  display: block;
  white-space: pre-wrap; /* 支持自动换行和手动换行 */
  outline: none; /* 去掉默认编辑框线 */
  min-height: 100px;
}
</style>

<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>
const codeEl = document.querySelector('code[contenteditable="true"]');
let debounceTimer = null;

// 保存光标位置
function saveCursorPosition(el) {
  const selection = window.getSelection();
  if (selection.rangeCount === 0) return null;
  const range = selection.getRangeAt(0);
  const preRange = range.cloneRange();
  preRange.selectNodeContents(el);
  preRange.setEnd(range.startContainer, range.startOffset);
  return preRange.toString().length;
}

// 恢复光标位置
function restoreCursorPosition(el, pos) {
  const selection = window.getSelection();
  const range = document.createRange();
  range.setStart(el.firstChild, pos);
  range.collapse(true);
  selection.removeAllRanges();
  selection.addRange(range);
}

// 实时高亮处理
function handleHighlight() {
  const cursorPos = saveCursorPosition(codeEl);
  const originalText = codeEl.textContent;
  
  // 用highlight手动处理文本,避免直接重写元素
  const result = hljs.highlight(originalText, { language: codeEl.classList[0].replace('language-', '') });
  codeEl.innerHTML = result.value;
  
  // 恢复光标位置
  if (cursorPos !== null) {
    restoreCursorPosition(codeEl, cursorPos);
  }
}

// 绑定输入事件并添加防抖
codeEl.addEventListener('input', () => {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(handleHighlight, 150);
});

// 初始高亮
handleHighlight();
</script>

关键细节说明

  • 用hljs.highlight()代替hljs.highlightElement():手动处理文本高亮后再替换内容,相比直接调用highlightElement更可控,避免元素被完全重写。
  • white-space: pre-wrap:确保代码中的换行符和手动输入的回车能正常显示,解决无法换行的问题。
  • 150ms防抖:平衡实时性和性能,避免每输入一个字符就触发一次高亮。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 21:21:28