
通过分离输入层(透明 textarea)与渲染层(高亮 pre),可避免 innerhtml 重写导致的光标跳失问题,实现稳定、响应式的实时语法高亮。
通过分离输入层(透明 textarea)与渲染层(高亮 pre),可避免 innerhtml 重写导致的光标跳失问题,实现稳定、响应式的实时语法高亮。
在基于 contenteditable 的富文本编辑器或代码编辑器中,直接操作 innerHTML 实现语法高亮虽直观,但会破坏 DOM 结构,导致浏览器重置光标位置——这是开发者常遇到的痛点。根本原因在于:每次调用 element.innerHTML = ... 都会销毁并重建子节点,原生光标锚点(selection)随之丢失。
一个健壮且轻量的解决方案是职责分离:
✅ 使用 <textarea></textarea>(或 <input type="text">)专责捕获用户输入与维护光标/选区;
✅ 使用 <pre class="brush:php;toolbar:false;"></pre> 或 <div>(非 contenteditable)专责<strong>纯展示与语法高亮渲染</strong>;<br>
✅ 二者视觉叠加,形成“所见即所编”的体验。<p>以下为完整实现示例:</p>
<pre class="brush:php;toolbar:false;"><textarea id="input" spellcheck="false"></textarea><pre class="brush:php;toolbar:false;" id="code"></pre></pre>
<pre class="brush:php;toolbar:false;">#input, #code {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
font: 1rem monospace;
padding: 60px 20px;
margin: 0;
box-sizing: border-box;
background-color: #141414;
color: white;
overflow-wrap: break-word;
}
#input {
color: transparent; /* 隐藏文字,保留光标 */
caret-color: white; /* 显式设置光标颜色 */
background: transparent;
border: none;
outline: none;
resize: none;
line-height: 1.5;
}
#code {
pointer-events: none; /* 确保点击穿透到 textarea */
padding-top: calc(60px - 1em); /* 补偿行高偏移 */
}</pre>
<pre class="brush:php;toolbar:false;">const inputEl = document.getElementById('input');
const codeEl = document.getElementById('code');
function applyHighlight(text) {
// 注意:正则需全局且非贪婪,避免匹配重叠(如嵌套引号)
return text
.replace(/(["'])(?:(?!)[^\]|\.)*/g, '<span style="color:#0d0">$&</span>') // 字符串(支持转义)
.replace(/(function|const|let|if|else|for|while|return)/g, '<span style="color:pink">$&</span>')
.replace(/([a-zA-Z_$][w$]*)s*(/g, '<span style="color:#0bb;font-weight:bold">$1</span>(') // 函数调用
.replace(/([a-zA-Z_$][w$]*.)+/g, '<span style="color:yellow">$&</span>'); // 命名空间链
}
function onInput() {
codeEl.innerHTML = applyHighlight(inputEl.value);
}
inputEl.addEventListener('input', onInput);
inputEl.focus();</pre>
<p>⚠️ <strong>关键注意事项</strong>: </p>
<ul>
<li>正则表达式务必使用 <code>g 标志并注意匹配边界(如字符串需处理转义引号),否则高亮可能错位或漏匹配;
textarea 的 spellcheck="false" 可禁用拼写检查干扰; pointer-events: none 应用于高亮层,确保所有交互(包括鼠标定位、拖拽选区)均由底层 textarea 处理; keydown 拦截 Tab 并插入空格(event.preventDefault(); inputEl.setRangeText(' ', start, end, 'end')); 该方案规避了 DOM 重绘引发的光标丢失,同时保持逻辑清晰、易于扩展(如增加主题切换、错误标记等),是构建轻量级代码编辑器的推荐实践。










