contenteditable编辑器修改innerHTML渲染markdown时如何保留光标位置
光标重置问题原因
你每次更新编辑器内容时直接重写了整个div的innerHTML,会触发容器内DOM树全量重绘,原有光标关联的DOM节点被销毁,浏览器只能将光标重置到容器起始位置。仅将光标固定到末尾的方案无法支持中间位置编辑的场景。
解决思路
通过 Selection + Range API 实现光标位置的记忆和还原:
- 触发输入事件后、更新DOM前,计算当前光标在编辑器纯文本内容中的偏移量
- 执行Markdown渲染更新内容后,根据记录的偏移量重新定位光标到对应位置
完整实现代码
HTML
<div contenteditable></div>
CSS
div { height: 100vh; width: 100vw; padding: 16px; box-sizing: border-box; outline: none; }
JS
const editor = document.querySelector('div'); editor.innerHTML = parse('**dlob** *cilati*'); editor.addEventListener('input', () => { // 记录当前光标在纯文本中的偏移量 const caretOffset = getTextCaretOffset(editor); // 渲染Markdown语法 editor.innerHTML = parse(editor.innerText); // 还原光标位置 setCaretByTextOffset(editor, caretOffset); }); function parse(text) { return text // 替换为非贪婪匹配避免多个同格式串被合并 .replace(/\*\*(.*?)\*\*/g, '**<strong>$1</strong>**') // 加粗 .replace(/\*(.*?)\*/g, '*<em>$1</em>*'); // 斜体 } // 获取光标在编辑器纯文本内容中的偏移量 function getTextCaretOffset(editor) { const selection = window.getSelection(); if (selection.rangeCount === 0) return 0; const range = selection.getRangeAt(0); const preRange = document.createRange(); preRange.selectNodeContents(editor); preRange.setEnd(range.endContainer, range.endOffset); return preRange.toString().length; } // 根据纯文本偏移量设置光标位置 function setCaretByTextOffset(editor, offset) { const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(editor); let currentOffset = 0; // 遍历节点找到偏移量对应的文本节点位置 const traverseNode = (node) => { if (node.nodeType === Node.TEXT_NODE) { const textLen = node.length; if (currentOffset + textLen >= offset) { range.setStart(node, offset - currentOffset); range.collapse(true); return true; } currentOffset += textLen; } else { for (const child of node.childNodes) { if (traverseNode(child)) return true; } } return false; }; traverseNode(editor); selection.removeAllRanges(); selection.addRange(range); }
可选优化
如果要进一步提升性能和稳定性,可以参考Quill等编辑器的增量更新逻辑:不直接全量替换innerHTML,而是先对比渲染前后的DOM差异,只修改发生变化的节点,减少重绘范围,也能避免极端场景下的光标偏移问题。
内容的提问来源于stack exchange,提问作者ADAMJR
相关产品推荐
相关产品推荐

