DraftJs:通过Decorator和Strategy设置状态后如何调整光标并保持焦点?
解决react-draft-wysiwyg插入内容后失焦及光标定位异常问题
问题场景
- 输入
#并选择标签后,编辑器失焦,需要在标签文本后保留光标 - 基于react-draft-wysiwyg开发,按下组合键唤起下拉框选择自定义React组件,通过Decorator+Strategy插入组件后编辑器失焦;调用
editor.focus()会让光标跳转到编辑器开头,尝试Draft.js官方的forceSelection方法未解决问题,相关代码如下:
const addEntityAndComponent = (editorState, content) => { const contentState = editorState.getCurrentContent(); const selection = editorState.getSelection(); const contentStateWithEntity = contentState.createEntity(content, 'IMMUTABLE', { content }); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); const newContentState = Modifier.insertText(contentStateWithEntity, selection, content, null, entityKey); const newEditorState = EditorState.push(editorState, newContentState); return EditorState.forceSelection(newEditorState, newContentState.getSelectionAfter()); };
核心原因
- 下拉框/弹窗唤起时夺取了焦点,导致编辑器失焦
- 插入内容后,
newContentState.getSelectionAfter()未正确计算光标位置,或forceSelection的时机不对
修复方案
1. 修正光标位置计算逻辑
原代码依赖getSelectionAfter()可能无法正确获取插入后的光标位置,手动计算偏移量更可靠:
const addEntityAndComponent = (editorState, content) => { const contentState = editorState.getCurrentContent(); const selection = editorState.getSelection(); const startOffset = selection.getStartOffset(); // 创建实体并插入内容 const contentStateWithEntity = contentState.createEntity( content, 'IMMUTABLE', { content } ); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); const newContentState = Modifier.insertText( contentStateWithEntity, selection, content, null, entityKey ); // 手动计算插入后的光标位置:原起始偏移 + 插入内容长度 const newSelection = selection.merge({ anchorOffset: startOffset + content.length, focusOffset: startOffset + content.length, isBackward: false }); // 指定编辑类型,帮助Draft.js正确处理状态 const newEditorState = EditorState.push( editorState, newContentState, 'insert-entity' ); return EditorState.forceSelection(newEditorState, newSelection); };
2. 确保焦点恢复时机正确
不要直接调用editor.focus(),而是在editorState更新完成后,结合forceSelection恢复焦点和光标:
类组件示例
componentDidUpdate(prevProps, prevState) { if (prevState.editorState !== this.state.editorState) { const editorInstance = this.editor; if (editorInstance) { // 先让编辑器获取焦点 editorInstance.focus(); // 强制同步光标位置 const syncedEditorState = EditorState.forceSelection( this.state.editorState, this.state.editorState.getSelection() ); this.setState({ editorState: syncedEditorState }); } } }
函数组件示例
import { useEffect, useRef } from 'react'; import { EditorState } from 'draft-js'; // ... const editorRef = useRef(null); useEffect(() => { if (editorRef.current) { editorRef.current.focus(); const syncedEditorState = EditorState.forceSelection( editorState, editorState.getSelection() ); setEditorState(syncedEditorState); } }, [editorState]);
3. 避免弹窗夺取焦点
如果是自定义下拉弹窗组件,添加tabIndex="-1"阻止其获取焦点,或在弹窗打开时调用event.preventDefault()避免焦点切换。
4. 检查Decorator与Strategy
确保Strategy仅匹配目标实体对应的文本,避免额外的选区干扰;Decorator渲染的组件不要设置contentEditable属性,防止干扰编辑器的选区计算。
内容的提问来源于stack exchange,提问作者iozeey
相关产品推荐
相关产品推荐

