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

Code Editor组件语法高亮实现后光标跳转至底部问题求助

解决ContentEditable语法高亮时光标跳转问题

你的问题出在每次输入都直接把带高亮标签的HTML更新到state,导致ContentEditable组件重新渲染后光标位置丢失,默认跳到内容末尾。要解决这个问题,核心是在更新高亮内容前后,手动保存并恢复光标位置。

具体实现步骤

  1. 用useRef获取ContentEditable的DOM节点,方便操作光标
  2. 在处理输入前,记录光标在原始纯文本中的位置(高亮后的HTML会插入额外标签,直接用DOM光标位置不对应)
  3. 生成高亮HTML后,计算光标在新HTML中的对应位置并恢复

修改后的完整代码

import { useState, useRef } from 'react';
import ContentEditable from '@draft-js-plugins/content-editable'; // 按实际使用的包调整

const CodeEditor = () => {
    const [currCode, setCurrCode] = useState('');
    const editorRef = useRef(null);
    const keywords = ['class', 'int', 'string', 'public', 'static', 'void', 'return'];

    // 获取光标在纯文本中的位置(忽略HTML标签)
    const getPlainTextCursorPos = () => {
        const element = editorRef.current;
        if (!element) return 0;
        
        const selection = window.getSelection();
        if (selection.rangeCount === 0) return 0;
        
        const range = selection.getRangeAt(0);
        const preRange = range.cloneRange();
        preRange.selectNodeContents(element);
        preRange.setEnd(range.startContainer, range.startOffset);
        
        return preRange.toString().length;
    };

    // 恢复光标到对应位置
    const restoreCursor = (targetPos) => {
        const element = editorRef.current;
        if (!element) return;
        
        const selection = window.getSelection();
        const range = document.createRange();
        let currentLength = 0;
        let found = false;

        const traverseNodes = (node) => {
            if (found) return;
            if (node.nodeType === Node.TEXT_NODE) {
                if (currentLength + node.textContent.length >= targetPos) {
                    range.setStart(node, targetPos - currentLength);
                    range.setEnd(node, targetPos - currentLength);
                    found = true;
                    return;
                }
                currentLength += node.textContent.length;
            } else {
                for (let i = 0; i < node.childNodes.length; i++) {
                    traverseNodes(node.childNodes[i]);
                }
            }
        };

        traverseNodes(element);
        selection.removeAllRanges();
        selection.addRange(range);
    };

    const highlightKeywords = (text) => {
        return keywords.reduce((acc, keyword) => {
            const regex = new RegExp(`\\b${keyword}\\b`, 'g');
            return acc.replace(regex, (match) => `<span style="color: yellow;">${match}</span>`);
        }, text);
    };

    const handleCodeChange = (e) => {
        const plainText = e.target.value;
        // 1. 保存当前光标位置
        const cursorPos = getPlainTextCursorPos();
        // 2. 生成高亮内容
        const highlightedCode = highlightKeywords(plainText);
        // 3. 更新状态
        setCurrCode(highlightedCode);
        // 4. 延迟恢复光标(等待组件渲染完成)
        setTimeout(() => restoreCursor(cursorPos), 0);
    };

    return (
        <ContentEditable
            ref={editorRef}
            html={currCode}
            tagName="pre"
            onChange={handleCodeChange}
        />
    );
};

export default CodeEditor;

关键说明

  • getPlainTextCursorPos:计算光标在纯文本中的真实位置,不受HTML高亮标签干扰
  • restoreCursor:遍历高亮后的DOM节点,把光标定位到对应纯文本位置
  • setTimeout延迟恢复光标,确保组件完成重新渲染后再操作光标

这样修改后,输入时光标不会再跳到底部,语法高亮功能也能正常生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 05:25:08