如何在Lexical JS中查找匹配文本并正确应用高亮样式?
在Lexical JS中实现文本匹配高亮的解决方案
需求场景
借助write-good检测语法错误并高亮匹配文本(示例简化为查找"Hello"并高亮),当前尝试代码如下:
import {useLexicalComposerContext} from "@lexical/react/LexicalComposerContext"; import {$createRangeSelection, $getRoot, $isParagraphNode, ParagraphNode} from "lexical"; const HighlightSearchButton = () => { const [editor] = useLexicalComposerContext(); const handleClick = async () => { editor.update(() => { const searchStr = 'Hello'; const regex = new RegExp(searchStr, 'gi') const children = $getRoot().getChildren(); for (const child of children) { if (!$isParagraphNode(child)) continue; const paragraphNode = child as ParagraphNode; const text = child.getTextContent(); const indexes = []; let result; while (result = regex.exec(text)) { indexes.push(result.index); } for (const index of indexes) { const selection = $createRangeSelection(); selection.anchor.key = paragraphNode.getKey(), selection.anchor.offset = index, selection.focus.key = paragraphNode.getKey(), selection.focus.offset = index + searchStr.length // Note: This makes the entire paragraph bold selection.formatText('bold'); // Note: actually want to apply a css style } } }); }; return <button onClick={handleClick}>Highlight Search</button> }; export default HighlightSearchButton;
遇到的问题
- 使用
$createRangeSelection时,指定段落节点key后无法正确定位文本的锚点/焦点偏移 - 通过
getTextContent()获取段落全文后,无法映射选择范围到对应的文本节点 - 段落中存在多格式(粗体、斜体等)文本节点时,无法跟踪节点状态并正确高亮
解决方案
1. 自定义高亮标记节点
Lexical推荐通过自定义节点实现样式高亮,而非直接使用内置格式工具。先创建一个自定义HighlightNode:
import { TextNode } from 'lexical'; export class HighlightNode extends TextNode { static getType() { return 'highlight'; } static clone(node) { return new HighlightNode(node.__text, node.__key); } createDOM(config) { const dom = super.createDOM(config); dom.style.backgroundColor = '#ffff00'; // 自定义高亮背景色 return dom; } exportJSON() { return { ...super.exportJSON(), type: 'highlight', }; } } // 注册节点到编辑器配置 const editorConfig = { nodes: [HighlightNode], // 其他编辑器配置项... };
2. 遍历文本节点精准定位匹配范围
不再依赖段落的getTextContent(),改为递归遍历每个文本节点,维护全局偏移量来匹配搜索范围:
const handleClick = async () => { editor.update(() => { const searchStr = 'Hello'; const regex = new RegExp(searchStr, 'gi'); let globalOffset = 0; // 递归遍历所有节点,定位文本节点 const traverseNodes = (node) => { if (node.isTextNode()) { const text = node.getTextContent(); let match; // 在当前文本节点内查找匹配项 while ((match = regex.exec(text)) !== null) { const nodeStart = match.index; const nodeEnd = nodeStart + searchStr.length; // 创建范围选择并替换为高亮节点 const selection = $createRangeSelection(); selection.setStart(node, nodeStart); selection.setEnd(node, nodeEnd); selection.replaceWith(new HighlightNode(match[0])); } globalOffset += text.length; } else if (node.getChildren) { node.getChildren().forEach(traverseNodes); } }; $getRoot().getChildren().forEach(traverseNodes); }); };
3. 处理跨格式节点的匹配
当匹配范围跨越多个已拆分的格式节点时,需要拆分节点并逐个转换为高亮节点:
// 处理跨节点匹配的工具函数 const handleCrossNodeMatch = (startNode, startOffset, endNode, endOffset) => { // 拆分起始节点,分离出匹配起始部分 const [_, highlightStart] = startNode.splitText(startOffset); // 拆分结束节点,分离出匹配结束部分 const [highlightEnd, __] = endNode.splitText(endOffset); // 转换起始和结束节点为高亮类型 highlightStart.setType('highlight'); highlightEnd.setType('highlight'); // 遍历中间节点,统一转换为高亮类型 let currentNode = highlightStart.getNextSibling(); while (currentNode !== highlightEnd) { if (currentNode.isTextNode()) { currentNode.setType('highlight'); } currentNode = currentNode.getNextSibling(); } };
4. 实时高亮:使用Node Transforms
如果需要在文本输入时实时检测并高亮(比如实时语法检查),可以注册Node Transforms监听文本变化:
import { $isTextNode } from 'lexical'; import { HighlightNode } from './HighlightNode'; editor.registerNodeTransform(TextNode, (node) => { if ($isTextNode(node)) { const text = node.getTextContent(); const regex = new RegExp('Hello', 'gi'); let match; while ((match = regex.exec(text)) !== null) { const start = match.index; const end = start + match[0].length; // 拆分文本节点,替换匹配部分为高亮节点 const [before, highlightSegment, after] = node.splitText(start, end); highlightSegment.replace(new HighlightNode(highlightSegment.getTextContent())); } } });
关键注意事项
- Lexical的文本偏移是基于单个文本节点的,而非整个段落,避免直接用段落偏移定位
- 自定义节点是实现自定义样式高亮的标准方案,比
formatText更灵活可控 - 递归遍历节点时维护全局偏移量,确保匹配位置精准对应到文本节点
内容的提问来源于stack exchange,提问作者OrderAndChaos
相关产品推荐
相关产品推荐

