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

DraftJs:通过Decorator和Strategy设置状态后如何调整光标并保持焦点?

解决react-draft-wysiwyg插入内容后失焦及光标定位异常问题

问题场景

  1. 输入#并选择标签后,编辑器失焦,需要在标签文本后保留光标
  2. 基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 17:50:35