
通过分离输入层(透明 textarea)与渲染层(contenteditable 或 pre),避免 innerhtml 重写导致的光标跳转,实现流畅的实时语法高亮。
通过分离输入层(透明 textarea)与渲染层(contenteditable 或 pre),避免 innerhtml 重写导致的光标跳转,实现流畅的实时语法高亮。
在实现“实时语法高亮”编辑器时,一个常见却棘手的问题是:每当调用 element.innerHTML = ... 更新高亮内容,浏览器会重建 DOM 子树,导致原生
推荐解法:分层架构——输入与渲染解耦
核心思想是将用户输入与视觉渲染彻底分离:
- ✅ 输入层:使用
- ✅ 渲染层:使用
或 <div> 仅负责显示高亮后的 HTML,不接受直接输入;<li>✅ <strong>样式叠加</strong>:将 textarea 设置为 color: transparent; caret-color: white; background: transparent;,并精确覆盖在渲染层上方,形成“所见即所写”的视觉一致性。</li> <p>以下是可直接运行的完整实现:</p> <pre class="brush:php;toolbar:false;"><textarea id="input" spellcheck="false"></textarea><pre class="brush:php;toolbar:false;" id="code">
#input, #code { position: absolute; top: 20px; left: 20px; right: 20px; bottom: 20px; padding: 16px; font: 1rem/1.5 monospace; color: white; background-color: #141414; border: none; outline: none; resize: none; overflow-wrap: break-word; } #input { color: transparent; /* 隐藏文字,保留光标 */ caret-color: #fff; /* 白色光标 */ background: transparent; /* 透出下方 pre 的背景 */ z-index: 2; } #code { z-index: 1; pointer-events: none; /* 确保点击穿透到 textarea */ }const inputEl = document.getElementById('input'); const codeEl = document.getElementById('code'); function highlight(text) { // 注意:需对特殊字符进行 HTML 转义,防止 XSS 和标签解析错误 const escapeHtml = str => str .replace(/&/g, '&') .replace(/, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); const escaped = escapeHtml(text); // 关键:先转义再匹配,确保正则作用于纯文本而非 HTML 标签 return escaped .replace(/\b(function|const|let)\b/g, '<span style="color:pink">$&</span>') .replace(/(["']).*?\1/g, '<span style="color:#0d0;font-weight:normal">$&</span>') .replace(/([a-zA-Z_\d]+)\s*\([^)]*\)/g, '<span style="color:#0bb;font-weight:bold">$1</span>$2') .replace(/([a-zA-Z_\d]+\.)+/g, '<span style="color:yellow">$&</span>'); } inputEl.addEventListener('input', () => { codeEl.innerHTML = highlight(inputEl.value); }); // 初始化 & 自动聚焦 inputEl.value = 'const greet = () => "Hello";\nconsole.log(greet());'; codeEl.innerHTML = highlight(inputEl.value); inputEl.focus();✅ 关键注意事项:
- 必须 HTML 转义输入内容:否则用户输入 <script>alert(1)</script> 会被执行,且正则匹配可能被破坏;
- 正则需适配转义后文本:例如字符串匹配 /(["']).*?\1/ 应基于原始引号,而非 ";
- pointer-events: none 不可省略:否则 #code 会拦截鼠标事件,导致无法聚焦 textarea;
- z-index 与定位需严格对齐:确保光标视觉位置与高亮文本完全重合;
- 如需支持 Tab 缩进,需监听 keydown 拦截 Tab 并手动插入空格(textarea 默认会切换焦点)。
该方案规避了所有 selection 恢复的复杂逻辑,性能更优、兼容性更好,是构建代码编辑器高亮功能的工业级实践。











