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

React中如何获取Lexical编辑器选中/点击链接的DOM引用?

在React的Lexical编辑器中获取链接DOM引用并添加编辑浮层

要实现点击或选中链接时获取其DOM引用并显示编辑浮层,可以按照以下步骤操作:

核心思路

  1. 监听编辑器的选择变化和点击事件,识别目标链接节点
  2. 通过Lexical的API获取对应链接的DOM元素
  3. 根据DOM位置计算浮层坐标,渲染编辑浮层并实现链接更新逻辑

代码实现

import { useLexicalComposerContext, LinkNode } from '@lexical/react';
import { useEffect, useState, useRef } from 'react';
import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { Placeholder } from '@lexical/react/LexicalPlaceholder';

// 自定义链接编辑浮层组件
const LinkEditFloatingLayer = () => {
  const [editor] = useLexicalComposerContext();
  const [activeLinkDOM, setActiveLinkDOM] = useState(null);
  const floatingRef = useRef(null);

  useEffect(() => {
    // 监听编辑器选择变化
    const unregisterUpdate = editor.registerUpdateListener(({ editorState }) => {
      editorState.read(() => {
        const selection = editorState.getSelection();
        // 仅处理选中单个LinkNode的情况
        if (selection?.isCollapsed() === false) {
          const selectedNodes = selection.getNodes();
          if (selectedNodes.length === 1 && selectedNodes[0] instanceof LinkNode) {
            const linkKey = selectedNodes[0].getKey();
            const domElement = editor.getElementByKey(linkKey);
            setActiveLinkDOM(domElement);
          } else {
            setActiveLinkDOM(null);
          }
        } else {
          setActiveLinkDOM(null);
        }
      });
    });

    // 监听点击事件,处理直接点击链接的场景
    const handleDocumentClick = (e) => {
      const target = e.target;
      // 判断点击元素是否是编辑器内的链接
      if (target.tagName === 'A' && target.closest('[contenteditable="true"]')) {
        const linkKey = target.getAttribute('data-lexical-key');
        if (linkKey) {
          editor.getEditorState().read(() => {
            const node = editor.getNodeByKey(linkKey);
            if (node instanceof LinkNode) {
              setActiveLinkDOM(target);
            }
          });
        }
      } else {
        // 点击其他区域隐藏浮层
        setActiveLinkDOM(null);
      }
    };

    document.addEventListener('click', handleDocumentClick);

    return () => {
      unregisterUpdate();
      document.removeEventListener('click', handleDocumentClick);
    };
  }, [editor]);

  // 动态调整浮层位置
  useEffect(() => {
    if (activeLinkDOM && floatingRef.current) {
      const rect = activeLinkDOM.getBoundingClientRect();
      floatingRef.current.style.top = `${rect.top - 35}px`;
      floatingRef.current.style.left = `${rect.left}px`;
    }
  }, [activeLinkDOM]);

  if (!activeLinkDOM) return null;

  return (
    <div
      ref={floatingRef}
      style={{
        position: 'fixed',
        backgroundColor: '#ffffff',
        border: '1px solid #e0e0e0',
        borderRadius: '4px',
        padding: '6px 10px',
        zIndex: 1000,
        display: 'flex',
        gap: '8px',
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
      }}
    >
      <input
        type="text"
        value={activeLinkDOM.href}
        onChange={(e) => {
          // 更新链接地址
          editor.update(() => {
            const linkKey = activeLinkDOM.getAttribute('data-lexical-key');
            const linkNode = editor.getNodeByKey(linkKey);
            if (linkNode instanceof LinkNode) {
              linkNode.setURL(e.target.value);
            }
          });
        }}
        style={{
          border: '1px solid #ddd',
          borderRadius: '3px',
          padding: '4px 6px',
          minWidth: '200px'
        }}
      />
      <button
        onClick={() => setActiveLinkDOM(null)}
        style={{
          border: 'none',
          backgroundColor: '#f5f5f5',
          borderRadius: '3px',
          padding: '4px 8px',
          cursor: 'pointer'
        }}
      >
        关闭
      </button>
    </div>
  );
};

// 编辑器主组件
export default function MyLexicalEditor() {
  const initialConfig = {
    namespace: 'MyEditor',
    nodes: [LinkNode],
    onError: (error) => console.error(error),
  };

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <div style={{ maxWidth: '800px', margin: '20px auto' }}>
        <RichTextPlugin
          contentEditable={<ContentEditable style={{ padding: '15px', border: '1px solid #eee', minHeight: '200px' }} />}
          placeholder={<Placeholder>输入带链接的文本...</Placeholder>}
        />
        <LinkEditFloatingLayer />
      </div>
    </LexicalComposer>
  );
}

关键细节说明

  • 获取DOM引用:通过editor.getElementByKey(linkNode.getKey())或者直接从点击事件的target获取,Lexical会给每个节点的DOM元素添加data-lexical-key属性用于关联节点
  • 事件监听:同时监听编辑器选择变化和全局点击事件,覆盖选中链接和直接点击链接两种场景
  • 浮层定位:使用getBoundingClientRect()获取链接的位置信息,动态设置浮层的top和left值
  • 更新链接:通过editor.update()方法修改LinkNode的URL,确保编辑器状态同步

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 03:40:19