温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
  • 忘记密码?
登录注册×
获取短信验证码
其他方式登录
点击 登录注册 即表示同意 《亿速云用户服务条款》
  • 服务器
  • 数据库
  • 开发技术
  • 网络安全
  • 互联网科技
登 录 注册有礼
最新更新 网站标签 地图导航
产品
  • 首页 > 
  • 教程 > 
  • 开发技术 > 
  • 纯js如何实现高度可扩展关键词高亮

纯js如何实现高度可扩展关键词高亮

发布时间:2022-08-31 09:59:59 来源:亿速云 阅读:127 作者:iii 栏目: 开发技术

今天小编给大家分享一下纯js如何实现高度可扩展关键词高亮的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

    关键词高亮

    日常需求开发中常见需要高亮的场景,本文主要记录字符串渲染时多个关键词同时高亮的实现方法,目的是实现高度可扩展的多关键词高亮方案。

    1. 实现的主要功能:

    • 关键词提取和高亮

    • 多个关键词同时高亮

    • 关键词支持正则匹配

    • 每个关键字支持独立样式配置,支持高度定制化

      • 不同标签使用不同颜色区分开

      • 使用不同标签名

      • 使用定制化CSSStyle样式

      • 自定义渲染函数,渲染成任何样式

    • 扩展性较好,可以根据解析数据自定义渲染,能很好的兼容复杂的场景

    2. 效果演示

    纯js如何实现高度可扩展关键词高亮

    高级定制用法

    • 自定义渲染,例如可以将文本变成链接

    纯js如何实现高度可扩展关键词高亮

    用法

    1. react中使用

    export default () => {
        const text = `123432123424r2`;
        const keywords = ['123'];
        return (
            <HighlightKeyword content={text} keywords=js高度可扩展关键词高亮,js 关键词高亮 />
        );
    };

    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<string, any>;
      // 高亮标签名
      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 (
            <TagName
              key={`${subString}_${i}`}
              style={{
                ...style,
                backgroundColor: bgColor || style.backgroundColor,
                color: color || style.color,
              }}>
              {subString}
            </TagName>
          );
        });
      }, [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 = ``;
      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(`<mark>${keyword}</mark>`);
      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) => <pre {...rest} className="highlight-container">
      {children}
    </pre>;
    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 (
            <TagName
              key={`${subString}_${i}`}
              style={{
                ...style,
                backgroundColor: bgColor || style.backgroundColor,
                color: color || style.color,
              }}>
              {subString}
            </TagName>
          );
        });
      }, [content, keywords]);
      return renderList;
    };
    const TabForm = ({ keyword, onChange, onCancel, onSubmit }: any) => {
      const formRef: any = useRef();
      useEffect(() => {
        formRef.current?.setFieldsValue(keyword);
      }, [keyword]);
      return (
        <Form
          ref={formRef}
          style={{ width: 300 }}
          onChange={(_, values) => {
            onChange(values);
          }}>
          <h3>编辑标签</h3>
          <Form.Item field="keyword" label="标签">
            <Input />
          </Form.Item>
          <Form.Item field="color" label="颜色">
            <Input
              prefix={
                <ColorBlock
                  color={keyword.color}
                  onChange={(color: string) =>
                    onChange({
                      ...keyword,
                      color,
                    })
                  }
                />
              }
            />
          </Form.Item>
          <Form.Item field="bgColor" label="背景色">
            <Input
              prefix={
                <ColorBlock
                  color={keyword.bgColor}
                  onChange={(color: string) =>
                    onChange({
                      ...keyword,
                      bgColor: color,
                    })
                  }
                />
              }
            />
          </Form.Item>
          <Form.Item field="tagName" label="标签名">
            <Input />
          </Form.Item>
          <Form.Item label="大小写敏感">
            <Switch
              checked={keyword.caseSensitive}
              onChange={(v: boolean) =>
                onChange({
                  ...keyword,
                  caseSensitive: v,
                })
              }
            />
          </Form.Item>
          <Form.Item>
            <Button onClick={onCancel} style={{ margin: '0 10px 0 100px' }}>
              取消
            </Button>
            <Button onClick={onSubmit} type="primary">
              确定
            </Button>
          </Form.Item>
        </Form>
      );
    };
    export default () => {
      const [text, setText] = useState(docStr);
      const [editKeyword, setEditKeyword] = useState<IKeywordOption>({
        keyword: '',
      });
      const [editTagIndex, setEditTagIndex] = useState(-1);
      const [keywords, setKeywords] = useState<IKeywordOption[]>([
        { keyword: 'antd', bgColor: 'yellow', color: '#000' },
        {
          keyword: '文件',
          bgColor: '#8600FF',
          color: '#fff',
          style: { padding: '0 4px' },
        },
        { keyword: '文件' },
        // eslint-disable-next-line no-octal-escape
        // { keyword: '\\d+' },
        {
          keyword: 'react',
          caseSensitive: false,
          renderHighlightKeyword: (str: string) => (
            <Tooltip content="点击访问链接">
              <a
                href={'https://zh-hans.reactjs.org'}
                target="_blank"
                style={{
                  textDecoration: 'underline',
                  fontStyle: 'italic',
                  color: 'blue',
                }}>
                {str}
              </a>
            </Tooltip>
          ),
        },
      ]);
      return (
        <div style={{ width: 800, margin: '0 auto' }}>
          <div style={{ display: 'flex', alignItems: 'center' }}>
            <h2>关键词高亮</h2>
            <Popover
              popupVisible={editTagIndex !== -1}
              position="left"
              content={
                <TabForm
                  keyword={editKeyword}
                  onChange={(values: any) => {
                    setEditKeyword(values);
                  }}
                  onCancel={() => {
                    setEditTagIndex(-1);
                    setEditKeyword({ keyword: '' });
                  }}
                  onSubmit={() => {
                    setKeywords((_keywords: IKeywordOption[]) => {
                      const newKeywords = [..._keywords];
                      newKeywords[editTagIndex] = { ...editKeyword };
                      return newKeywords;
                    });
                    setEditTagIndex(-1);
                    setEditKeyword({ keyword: '' });
                  }}
                />
              }>
              <Tooltip content="添加标签">
                <Button
                  type="primary"
                  icon={<IconPlus />}
                  style={{ marginLeft: 'auto' }}
                  onClick={() => {
                    setEditTagIndex(keywords.length);
                  }}>
                  添加标签
                </Button>
              </Tooltip>
            </Popover>
          </div>
          <div style={{ display: 'flex', padding: '15px 0' }}></div>
          {keywords.map((keyword, i) => (
            <Tooltip key={JSON.stringify(keyword)} content="双击编辑标签">
              <Tag
                closable={true}
                style={{
                  margin: '0 16px 16px 0 ',
                  backgroundColor: keyword.bgColor,
                  color: keyword.color,
                }}
                onClose={() => {
                  setKeywords((_keywords: IKeywordOption[]) => {
                    const newKeywords = [..._keywords];
                    newKeywords.splice(i, 1);
                    return newKeywords;
                  });
                }}
                onDoubleClick={() => {
                  setEditTagIndex(i);
                  setEditKeyword({ ...keywords[i] });
                }}>
                {typeof keyword.keyword === 'string'
                  ? keyword.keyword
                  : keyword.keyword.toString()}
              </Tag>
            </Tooltip>
          ))}
          <Card title="内容区">
            <HighlightContainer>
              <HighlightKeyword content={text} keywords=js高度可扩展关键词高亮,js 关键词高亮 />
            </HighlightContainer>
          </Card>
        </div>
      );
    };

    以上就是“纯js如何实现高度可扩展关键词高亮”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。

    向AI问一下细节
    推荐阅读:
    1. JS如何实现导航栏高亮显示
    2. vue如何实现多组关键词对应高亮显示功能

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    js
    • 上一篇新闻:
      怎么用C#实现文件与字符串互转
    • 下一篇新闻:
      怎么用Python实现自动化处理Word文档

    猜你喜欢

    • FreeBSD中如何设置网络配置
    • FreeBSD中如何管理用户和组
    • FreeBSD中如何配置防火墙
    • FreeBSD支持哪些文件系统
    • 如何在FreeBSD中进行系统更新和升级
    • FreeBSD中如何查看系统日志文件
    • FreeBSD中如何查看系统资源使用情况
    • hadoop集群参数配置的方法是什么
    • hadoop中的yarn怎么创建session集群
    • hadoop中分布式计算如何实现
    最新资讯
    • 如何使用Cocoa Touch框架提升应用的可测试性和维护性
    • 在Cocoa Touch中如何利用Core Motion追踪和响应用户运动
    • 使用Cocoa Touch如何来优化应用的启动广告或引导页
    • 如何在Cocoa Touch中通过Core Spotlight实现内容的搜索和索引
    • 在Cocoa Touch应用中如何创建和管理动态快捷方式
    • 使用Cocoa Touch如何实现数据的加密与安全传输
    • 如何使用Cocoa Touch框架开发自适应布局的应用程序
    • 在Cocoa Touch中如何实现软件包的动态加载和卸载
    • 使用Cocoa Touch如何创建支持导出到不同格式的报表
    • 如何在Cocoa Touch中处理文本的自动校正和建议输入
    相关推荐
    • react中实现搜索结果中关键词高亮显示
    • 如何使用Vue实现关键词实时搜索高亮显示关键词
    • JS实现关键词高亮显示正则匹配
    • 纯JS如何实现日历
    • js高亮显示关键词的方法是什么
    • 纯js怎样实现3d相册
    • 纯Css如何实现Div高度根据自适应宽度调整
    • js如何设置css高度
    • 微信小程序搜索关键词高亮怎么实现
    • vue如何实现搜索关键词高亮效果

    相关标签

    angularjs json对象 ajax+jsf bjs d3js json.dumps js轮播图 jsonfield json.stringify jquery.cookie.js d3.js nuxt.js json.parse main.js nprogress.js video.js app.json webpack.config.js jsx momentjs
    AI

    PHP网站源码海口网络营销多少钱坂田企业网站制作价格松岗推广网站柳州高端网站设计大理网站优化软件哪家好福州网站优化排名哪家好松原关键词排名包年推广价格潮州模板网站建设价格湘西网站设计报价张家界百度竞价推荐广安百姓网标王推广哪家好酒泉网站优化按天扣费公司宜春网站关键词优化报价朔州推广网站推荐罗湖百度关键词包年推广报价宜春百姓网标王衢州seo网站推广推荐鹤岗网站改版公司西乡网站设计模板公司湛江建站推荐榆林百度网站优化排名营口企业网站设计报价衡水关键词按天收费公司铜川网站制作设计推荐黄南外贸网站设计南平网站关键词优化价格梅州营销型网站建设报价十堰seo优化推荐衡水关键词排名哪家好达州网站优化歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

    PHP网站源码 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化