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

React富文本编辑器:contentEditable光标跳转至起始位置问题排查

解决React富文本编辑器光标跳转问题(保留HTML清理函数)

问题核心:每次通过dangerouslySetInnerHTML重新渲染内容时,React会完全替换contentEditable元素的内部HTML,导致输入光标被重置到起始位置。要保留freeAllAttributeHTML的同时解决这个问题,需要手动管理光标位置。

解决步骤

  • 保存光标位置:在更新内容状态前,记录当前光标在编辑区域的选区范围
  • 恢复光标位置:内容更新完成、组件重新渲染后,将光标恢复到之前记录的位置
  • 确保HTML结构一致性:freeAllAttributeHTML清理时尽量保持标签结构不变,避免因结构差异导致光标定位失效

修改后的完整代码

import React, { useState, useRef, useEffect } from 'react'

function App() {
    const [content, setContent] = useState('')
    const editableRef = useRef(null)
    let cursorRange = null

    // 保存当前光标/选区位置
    const saveCursorPosition = () => {
        const selection = window.getSelection()
        if (selection.rangeCount > 0) {
            cursorRange = selection.getRangeAt(0)
        }
    }

    // 恢复光标/选区位置
    const restoreCursorPosition = () => {
        if (!cursorRange || !editableRef.current) return
        const selection = window.getSelection()
        selection.removeAllRanges()
        selection.addRange(cursorRange)
    }

    const freeAllAttributeHTML = (htmlString) => {
        let cleanedHtml = String(htmlString).replaceAll(/<div[^>]*>/g, '<div>');
        cleanedHtml = cleanedHtml.replaceAll(/<p[^>]*>/g, '<p>');
        cleanedHtml = cleanedHtml.replaceAll(/<span[^>]*>/g, '<span>');
        cleanedHtml = cleanedHtml.replaceAll(/<button[^>]*>/g, '<button>');
        cleanedHtml = cleanedHtml.replaceAll(/<a[^>]*>/g, '<a>');
        cleanedHtml = cleanedHtml.replaceAll(/<svg[^>]*>/g, '<svg>');
        cleanedHtml = cleanedHtml.replaceAll(/<img[^>]*>/g, '');
   
        return cleanedHtml;
    }

    const handleContentChange = (e) => {
        // 更新前先保存光标位置
        saveCursorPosition()
        const cleanedContent = freeAllAttributeHTML(e.target.innerHTML)
        setContent(cleanedContent)
    }

    // 内容更新后恢复光标
    useEffect(() => {
        restoreCursorPosition()
    }, [content])

    return (
        <>
            <div>
                <strong>hello one</strong>
                <em> hello two</em>
            </div>
            <div 
                ref={editableRef}
                className='editable'
                contentEditable={true}
                dangerouslySetInnerHTML={{ __html: content }}
                onInput={handleContentChange}
            />
        </>
    )
}

export default App

关键说明

  • editableRef:获取contentEditable元素的DOM引用,用于后续光标操作
  • saveCursorPosition:利用window.getSelection()记录当前光标所在的Range对象
  • useEffect:监听content状态变化,每次内容更新完成后调用restoreCursorPosition恢复光标
  • 注意:如果freeAllAttributeHTML对HTML结构修改较大(比如删除了某些标签),可能导致光标无法准确定位,需要调整清理规则尽量保留原有标签层级

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 11:17:33