最资讯丨纯js实现高度可扩展关键词高亮方案详解
【资料图】
目录
关键词高亮1. 实现的主要功能:2. 效果演示高级定制用法用法1. react中使用2. 原生js使用innerHTML源码核心源码渲染方案1. react组件渲染2. innerHTML渲染showcase演示组件关键词高亮
日常需求开发中常见需要高亮的场景,本文主要记录字符串渲染时多个关键词同时高亮的实现方法,目的是实现高度可扩展的多关键词高亮方案。
1. 实现的主要功能:
关键词提取和高亮多个关键词同时高亮关键词支持正则匹配每个关键字支持独立样式配置,支持高度定制化不同标签使用不同颜色区分开使用不同标签名使用定制化CSSStyle样式自定义渲染函数,渲染成任何样式扩展性较好,可以根据解析数据自定义渲染,能很好的兼容复杂的场景2. 效果演示
体验地址:链接
高级定制用法
自定义渲染,例如可以将文本变成链接用法
1. react中使用
export default () => { const text = `123432123424r2`; const keywords = ["123"]; return (); };
2. 原生js使用innerHTML
const div = document.querySelector("#div"); div.innerHTML = getHighlightKeywordsHtml(templateStr, [keyword]);
源码
核心源码
// 关键词配置 export interface IKeywordOption { keyword: string | RegExp; color?: string; bgColor?: string; style?: Record; // 高亮标签名 tagName?: string; // 忽略大小写 caseSensitive?: boolean; // 自定义渲染高亮html renderHighlightKeyword?: (content: string) => any; } export type IKeyword = string | IKeywordOption; export interface IMatchIndex { index: number; subString: string; } // 关键词索引 export interface IKeywordParseIndex { keyword: string | RegExp; indexList: IMatchIndex[]; option?: IKeywordOption; } // 关键词 export interface IKeywordParseResult { start: number; end: number; subString?: string; option?: IKeywordOption; } /** ***** 以上是类型,以下是代码 ********************************************************/ /** * 多关键词的边界情况一览: * 1. 关键词之间存在包含关系,如: "12345" 和 "234" * 2. 关键词之间存在交叉关系,如: "1234" 和 "3456" */ // 计算 const getKeywordIndexList = ( content: string, keyword: string | RegExp, flags = "ig", ) => { const reg = new RegExp(keyword, flags); const res = (content as any).matchAll(reg); const arr = [...res]; const allIndexArr: IMatchIndex[] = arr.map(e => ({ index: e.index, subString: e["0"], })); return allIndexArr; }; // 解析关键词为索引 const parseHighlightIndex = (content: string, keywords: IKeyword[]) => { const result: IKeywordParseIndex[] = []; keywords.forEach((keywordOption: IKeyword) => { let option: IKeywordOption = { keyword: "" }; if (typeof keywordOption === "string") { option = { keyword: keywordOption }; } else { option = keywordOption; } const { keyword, caseSensitive = true } = option; const indexList = getKeywordIndexList( content, keyword, caseSensitive ? "g" : "gi", ); const res = { keyword, indexList, option, }; result.push(res); }); return result; }; // 解析关键词为数据 export const parseHighlightString = (content: string, keywords: IKeyword[]) => { const result = parseHighlightIndex(content, keywords); const splitList: IKeywordParseResult[] = []; const findSplitIndex = (index: number, len: number) => { for (let i = 0; i < splitList.length; i++) { const cur = splitList[i]; // 有交集 if ( (index > cur.start && index < cur.end) || (index + len > cur.start && index + len < cur.end) || (cur.start > index && cur.start < index + len) || (cur.end > index && cur.end < index + len) || (index === cur.start && index + len === cur.end) ) { return -1; } // 没有交集,且在当前的前面 if (index + len <= cur.start) { return i; } // 没有交集,且在当前的后面的,放在下个迭代处理 } return splitList.length; }; result.forEach(({ indexList, option }: IKeywordParseIndex) => { indexList.forEach(e => { const { index, subString } = e; const item = { start: index, end: index + subString.length, option, }; const splitIndex = findSplitIndex(index, subString.length); if (splitIndex !== -1) { splitList.splice(splitIndex, 0, item); } }); }); // 补上没有匹配关键词的部分 const list: IKeywordParseResult[] = []; splitList.forEach((cur, i) => { const { start, end } = cur; const next = splitList[i + 1]; // 第一个前面补一个 if (i === 0 && start > 0) { list.push({ start: 0, end: start, subString: content.slice(0, start) }); } list.push({ ...cur, subString: content.slice(start, end) }); // 当前和下一个中间补一个 if (next?.start > end) { list.push({ start: end, end: next.start, subString: content.slice(end, next.start), }); } // 最后一个后面补一个 if (i === splitList.length - 1 && end < content.length - 1) { list.push({ start: end, end: content.length - 1, subString: content.slice(end, content.length - 1), }); } }); console.log("list:", keywords, list); return list; };
渲染方案
1. react组件渲染
// react组件 const HighlightKeyword = ({ content, keywords, }: { content: string; keywords: IKeywordOption[]; }): any => { const renderList = useMemo(() => { if (keywords.length === 0) { return <>{content}>; } const splitList = parseHighlightString(content, keywords); if (splitList.length === 0) { return <>{content}>; } return splitList.map((item: IKeywordParseResult, i: number) => { const { subString, option = {} } = item; const { color, bgColor, style = {}, tagName = "mark", renderHighlightKeyword, } = option as IKeywordOption; if (typeof renderHighlightKeyword === "function") { return renderHighlightKeyword(subString as string); } if (!item.option) { return <>{subString}>; } const TagName: any = tagName; return ({subString} ); }); }, [content, keywords]); return renderList; };
2. innerHTML渲染
/** ***** 以上是核心代码部分,以下渲染部分 ********************************************************/ // 驼峰转换横线 function humpToLine(name: string) { return name.replace(/([A-Z])/g, "-$1").toLowerCase(); } const renderNodeTag = (subStr: string, option: IKeywordOption) => { const s = subStr; if (!option) { return s; } const { tagName = "mark", bgColor, color, style = {}, renderHighlightKeyword, } = option; if (typeof renderHighlightKeyword === "function") { return renderHighlightKeyword(subStr); } style.backgroundColor = bgColor; style.color = color; const styleContent = Object.keys(style) .map(k => `${humpToLine(k)}:${style[k]}`) .join(";"); const styleStr = `style="${styleContent}"`; return `<${tagName} ${styleStr}>${s}${tagName}>`; }; const renderHighlightHtml = (content: string, list: any[]) => { let str = ""; list.forEach(item => { const { start, end, option } = item; const s = content.slice(start, end); const subStr = renderNodeTag(s, option); str += subStr; item.subString = subStr; }); return str; }; // 生成关键词高亮的html字符串 export const getHighlightKeywordsHtml = ( content: string, keywords: IKeyword[], ) => { // const keyword = keywords[0] as string; // return content.split(keyword).join(`${keyword}`); const splitList = parseHighlightString(content, keywords); const html = renderHighlightHtml(content, splitList); return html; };
showcase演示组件
/* eslint-disable @typescript-eslint/no-shadow */ import React, { useEffect, useMemo, useRef, useState } from "react"; import { Card, Tag, Button, Tooltip, Popover, Form, Input, Switch, } from "@arco-design/web-react"; import { IconPlus } from "@arco-design/web-react/icon"; import ColorBlock from "./color-block"; import { parseHighlightString, IKeywordOption, IKeywordParseResult, } from "./core"; import "./index.less"; import { docStr, shortStr } from "./data"; const HighlightContainer = ({ children, ...rest }: any) =>{children}; const HighlightKeyword = ({ content, keywords, }: { content: string; keywords: IKeywordOption[]; }): any => { const renderList = useMemo(() => { if (keywords.length === 0) { return <>{content}>; } const splitList = parseHighlightString(content, keywords); if (splitList.length === 0) { return <>{content}>; } return splitList.map((item: IKeywordParseResult, i: number) => { const { subString, option = {} } = item; const { color, bgColor, style = {}, tagName = "mark", renderHighlightKeyword, } = option as IKeywordOption; if (typeof renderHighlightKeyword === "function") { return renderHighlightKeyword(subString as string); } if (!item.option) { return <>{subString}>; } const TagName: any = tagName; return ({subString} ); }); }, [content, keywords]); return renderList; }; const TabForm = ({ keyword, onChange, onCancel, onSubmit }: any) => { const formRef: any = useRef(); useEffect(() => { formRef.current?.setFieldsValue(keyword); }, [keyword]); return (
关键词高亮
以上就是纯js实现高度可扩展关键词高亮方案详解的详细内容,更多关于js高度可扩展关键词高亮的资料请关注脚本之家其它相关文章!
X 关闭
X 关闭
- 1联想拯救者Y70发布最新预告:售价2970元起 迄今最便宜的骁龙8+旗舰
- 2亚马逊开始大规模推广掌纹支付技术 顾客可使用“挥手付”结账
- 3现代和起亚上半年出口20万辆新能源汽车同比增长30.6%
- 4如何让居民5分钟使用到各种设施?沙特“线性城市”来了
- 5AMD实现连续8个季度的增长 季度营收首次突破60亿美元利润更是翻倍
- 6转转集团发布2022年二季度手机行情报告:二手市场“飘香”
- 7充电宝100Wh等于多少毫安?铁路旅客禁止、限制携带和托运物品目录
- 8好消息!京东与腾讯续签三年战略合作协议 加强技术创新与供应链服务
- 9名创优品拟通过香港IPO全球发售4100万股 全球发售所得款项有什么用处?
- 10亚马逊云科技成立量子网络中心致力解决量子计算领域的挑战