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

在JavaScript输入时为单个字符着色并保持光标位置

解决contenteditable输入实时校验时的光标丢失问题

嘿,这个问题我之前做打字练习工具的时候碰到过!核心问题就是每次我们重新替换contenteditable的innerHTML时,浏览器会重置光标位置,所以关键是先存光标,再渲染,最后恢复光标。下面给你完整的解决方案:

核心思路

  • 监听input事件,每次输入触发校验逻辑
  • 处理前先保存当前光标在纯文本中的偏移量(避开DOM结构变化的影响)
  • 对比输入内容和目标单词JAVASCRIPT,生成带红色错误标记的HTML结构
  • 更新div的innerHTML后,再把光标恢复到正确的位置

完整代码实现

<div id="editor" contenteditable="true" style="padding: 10px; border: 1px solid #ccc; font-size: 20px;"></div>

<script>
const targetWord = 'JAVASCRIPT';
const editor = document.getElementById('editor');

// 保存光标在纯文本中的偏移量
function saveCursorPosition(element) {
    const selection = window.getSelection();
    if (selection.rangeCount === 0) return 0;
    
    const range = selection.getRangeAt(0);
    const preRange = document.createRange();
    preRange.selectNodeContents(element);
    preRange.setEnd(range.startContainer, range.startOffset);
    
    return preRange.toString().length;
}

// 根据纯文本偏移量恢复光标
function restoreCursorPosition(element, position) {
    const selection = window.getSelection();
    let currentOffset = 0;

    const traverseNodes = node => {
        if (node.nodeType === Node.TEXT_NODE) {
            if (currentOffset + node.length >= position) {
                const range = document.createRange();
                range.setStart(node, position - currentOffset);
                range.collapse(true);
                selection.removeAllRanges();
                selection.addRange(range);
                return true;
            }
            currentOffset += node.length;
        } else {
            for (const child of node.childNodes) {
                if (traverseNodes(child)) return true;
            }
        }
        return false;
    };

    // 处理编辑器为空的情况
    if (!element.firstChild) {
        element.appendChild(document.createTextNode(''));
    }
    traverseNodes(element);
}

// 处理输入内容,生成带错误标记的HTML
function processInput(inputText) {
    let result = '';
    for (let i = 0; i < inputText.length; i++) {
        const char = inputText[i];
        // 超过目标长度或字符不匹配时标红,忽略大小写(可按需调整)
        if (i >= targetWord.length || char.toUpperCase() !== targetWord[i]) {
            result += `<span style="color: red;">${char}</span>`;
        } else {
            result += char;
        }
    }
    return result;
}

// 监听输入事件
editor.addEventListener('input', () => {
    // 1. 保存光标位置
    const cursorPos = saveCursorPosition(editor);
    // 2. 获取纯文本内容(过滤HTML标签)
    const inputText = editor.innerText;
    // 3. 生成带样式的HTML
    const processedHTML = processInput(inputText);
    // 4. 更新编辑器内容
    editor.innerHTML = processedHTML;
    // 5. 恢复光标位置
    restoreCursorPosition(editor, cursorPos);
});
</script>

关键细节说明

  • 光标位置的保存与恢复:我们基于纯文本的偏移量来记录光标,而不是DOM节点的位置——因为处理后的DOM会插入span标签,节点结构会变化,纯文本偏移量更稳定可靠。
  • 输入处理逻辑:不仅处理了字符不匹配的标红,还兼容了输入长度超过目标单词的情况(超出部分直接标红),同时默认忽略大小写,你可以根据需求修改判断条件。
  • 边界兼容:处理了编辑器为空的情况,避免光标恢复失败。

这样用户输入时,错误字符会实时标红,光标也不会跳走,完全不影响正常的输入体验!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:05:26