contenteditable div实现Python代码高亮时文字反转问题求助
问题根源分析
你遇到的文字“反转”其实是光标位置被强制重置到内容开头导致的假象:
- 每次
input事件触发时,直接用innerHTML替换整个div的内容,会完全重建内部DOM结构,浏览器会自动把光标定位到内容的起始位置。 - 当你继续输入字符时,新内容会插入到光标所在的开头,比如输入
d后光标在开头,接着输入e就变成ed,再输入f就成了fed,看起来像是def被反转了。 - 用highlight.js时出现同样问题,也是因为它默认更新DOM时没有处理光标位置,导致光标被重置。
解决方案
要解决这个问题,核心是在更新DOM前后保存并恢复光标位置,同时优化内容更新逻辑避免重复破坏DOM结构:
1. 保存与恢复光标位置
在更新innerHTML前,先记录当前光标在内容中的偏移量,更新后再把光标移回原来的位置:
const writableDiv = document.getElementById("code-editor"); const pythonKeywords = ['def', 'class', 'if', 'else']; // 示例关键字 const keywordRegex = new RegExp('\\b(' + pythonKeywords.join('|') + ')\\b', 'g'); writableDiv.addEventListener('input', () => { // 保存光标位置 const selection = window.getSelection(); const range = selection.getRangeAt(0); const preCursorOffset = range.startOffset; const preCursorContainer = range.startContainer; // 计算光标在文本中的绝对位置(处理嵌套元素) const getCursorPosition = (element) => { const range = document.createRange(); range.selectNodeContents(element); range.setEnd(preCursorContainer, preCursorOffset); return range.toString().length; }; const cursorPos = getCursorPosition(writableDiv); // 更新内容并高亮 let text = writableDiv.innerText; text = text.replace(keywordRegex, '<span style="color: #f200ff;">$&</span>'); writableDiv.innerHTML = text; // 恢复光标位置 const restoreCursor = (element, pos) => { const range = document.createRange(); const selection = window.getSelection(); let currentPos = 0; const traverse = (node) => { if (node.nodeType === Node.TEXT_NODE) { const textLength = node.textContent.length; if (currentPos + textLength >= pos) { range.setStart(node, pos - currentPos); range.collapse(true); return true; } currentPos += textLength; } else { for (let i = 0; i < node.childNodes.length; i++) { if (traverse(node.childNodes[i])) return true; } } return false; }; traverse(element); selection.removeAllRanges(); selection.addRange(range); }; restoreCursor(writableDiv, cursorPos); });
2. 优化高亮逻辑(可选)
避免每次都用innerText获取内容再转innerHTML,可以直接操作DOM节点,减少不必要的DOM重建:
- 只对新增的内容进行高亮处理,而不是整个文本重新替换
- 使用
documentFragment来构建更新的DOM,一次性插入,提升性能
3. 使用highlight.js的正确姿势
如果用highlight.js,同样需要处理光标位置:
// 初始化highlight.js hljs.registerLanguage('python', hljs.getLanguage('python')); writableDiv.addEventListener('input', () => { // 先保存光标位置(逻辑同上) const selection = window.getSelection(); const range = selection.getRangeAt(0); const preCursorOffset = range.startOffset; const preCursorContainer = range.startContainer; const getCursorPosition = (element) => { const range = document.createRange(); range.selectNodeContents(element); range.setEnd(preCursorContainer, preCursorOffset); return range.toString().length; }; const cursorPos = getCursorPosition(writableDiv); // 用highlight.js处理高亮 const code = writableDiv.innerText; const result = hljs.highlight(code, {language: 'python'}); writableDiv.innerHTML = result.value; // 恢复光标位置(逻辑同上) const restoreCursor = (element, pos) => { const range = document.createRange(); const selection = window.getSelection(); let currentPos = 0; const traverse = (node) => { if (node.nodeType === Node.TEXT_NODE) { const textLength = node.textContent.length; if (currentPos + textLength >= pos) { range.setStart(node, pos - currentPos); range.collapse(true); return true; } currentPos += textLength; } else { for (let i = 0; i < node.childNodes.length; i++) { if (traverse(node.childNodes[i])) return true; } } return false; }; traverse(element); selection.removeAllRanges(); selection.addRange(range); }; restoreCursor(writableDiv, cursorPos); });
内容的提问来源于stack exchange,提问作者Jam
相关产品推荐
相关产品推荐

