React中如何获取Lexical编辑器选中/点击链接的DOM引用?
在React的Lexical编辑器中获取链接DOM引用并添加编辑浮层
要实现点击或选中链接时获取其DOM引用并显示编辑浮层,可以按照以下步骤操作:
核心思路
- 监听编辑器的选择变化和点击事件,识别目标链接节点
- 通过Lexical的API获取对应链接的DOM元素
- 根据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
相关产品推荐
相关产品推荐

