Chrome不支持正则搜索?那我们自己写一个

说在前面

🎈Ctrl + F 大家都用过了吧,最近在Chrome中使用搜索功能的时候,突然想要使用正则来进行搜索,发现Chrome浏览器自带的搜索功能并不支持正则搜索,于是便想着自己做了一个支持正则搜索的Chrome插件。

效果展示

image.png

image.png

实现步骤

1、快速生成一个插件模板

  • 安装jyeontu
npm install -g jyeontu
  • 生成插件模板
jyeontu create

image.png

之前也写过一篇文章详细介绍过怎么快速生成一个插件模板并进行二次开发,有兴趣的同学可以看下面这篇文章:

《使用vue快速开发一个带弹窗的Chrome插件》

2、完成搜索框功能

(1)快捷键打开搜索框
addCustomSearchEventListener(element) {if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {return;}element.addEventListener("keydown", this.keydownFn);for (let i = 0; i < element.children.length; i++) {this.addCustomSearchEventListener(element.children[i]);}
},
keydownFnListening(dom = window) {dom.addEventListener("keydown", function (e) {if (e.altKey && e.key === "g") {e.preventDefault();if (dom.e && dom.e.returnValue) dom.e.returnValue = false;return false;}});
},
ctrlFAction(query = "") {this.showPanel = true;if (this.showPanel) {this.$nextTick(() => {setTimeout(() => {const chromeSearchInput =document.getElementById("chromeSearchInput");chromeSearchInput.focus();this.performCustomSearch(query);}, 100);});}
},
keydownFn(event) {if (this.keydownFnTimer) clearTimeout(this.keydownFnTimer);this.keydownFnTimer = setTimeout(() => {let query = this.getSelectedText();if (query === "") {query = localStorage.getItem("chromeSearchQueryKeyJY") || "";}if (event.altKey && event.key === "g") {this.query = query;event.preventDefault();this.ctrlFAction(query);}}, 200);
},
init() {this.keydownFnListening();this.addCustomSearchEventListener(document);const iframes = document.querySelectorAll("iframe");for (let i = 0; i < iframes.length; i++) {try {const iframeDocument = iframes[i].contentDocument;this.addCustomSearchEventListener(iframeDocument);this.keydownFnListening(iframes[i].contentWindow);} catch (e) {console.error("Error accessing iframe content:", e);}}
},

这段代码是一个Chrome浏览器插件的一部分,它定义了几个方法来处理自定义搜索功能的事件监听和初始化。以下是每个方法的详细解释:

  • addCustomSearchEventListener
    这个方法用于给指定的DOM元素添加键盘事件监听器。它递归地检查元素的所有子元素,并对每个元素添加一个 keydown 事件监听器。如果元素是 INPUTTEXTAREA 类型,则不添加监听器。

  • keydownFnListening
    这个方法设置了一个全局的 keydown 事件监听器。当用户按下 Alt+g 组合键时,它会阻止默认行为,然后调用 ctrlFAction 方法来执行搜索。

  • ctrlFAction
    这个方法负责显示搜索面板并开始执行搜索。首先,它将 showPanel 设置为 true,以显示搜索面板。然后,在 $nextTick 回调中,它等待一段时间后,让页面更新完成,再获取搜索输入框的焦点,并调用 performCustomSearch 方法来执行搜索。如果 query 参数为空,则从 localStorage 中获取之前保存的查询字符串。

  • keydownFn
    这个方法是一个防抖函数,用于处理 keydown 事件。它首先清除任何已经设置的定时器,以避免多次快速按键触发多个搜索。然后,它设置一个200毫秒的延迟,以便在用户停止按键后执行搜索。如果用户选中了一些文本,该方法将使用选中的文本作为搜索查询;如果没有选中文本,则使用 localStorage 中的查询字符串。如果用户按下 Alt+g,则执行搜索。

  • init
    这个方法是初始化函数,它首先调用 keydownFnListening 来添加全局键盘事件监听器,然后调用 addCustomSearchEventListener 来为整个文档添加搜索事件监听器。此外,它还遍历页面上的所有 iframe,并尝试为每个 iframe 的内容文档添加事件监听器。

这些方法共同构成了插件的键盘事件处理逻辑,允许用户通过按下 Alt+g 快捷键来快速访问和使用搜索功能。通过在 iframe 和主文档中添加事件监听器,插件确保了在多种页面结构中都能提供一致的用户体验。

(2)搜索框拖拽功能
// 初始化拖拽功能
initDrag() {const chromeSearchToolApp = document.getElementById("chromeSearchToolApp");chromeSearchToolApp.addEventListener("mousedown", this.startDrag);document.addEventListener("mousemove", this.dragging);document.addEventListener("mouseup", this.endDrag);
},
startDrag(event) {this.dragData.isDragging = true;this.dragData.startX = event.clientX - this.dragData.left;this.dragData.startY = event.clientY - this.dragData.top;
},
dragging(event) {if (this.dragData.isDragging) {this.dragData.left = event.clientX - this.dragData.startX;this.dragData.top = event.clientY - this.dragData.startY;}
},
endDrag() {this.dragData.isDragging = false;
},
  • initDrag
    这个方法用于初始化拖拽功能。它首先通过 getElementById 获取到搜索工具面板的DOM元素 chromeSearchToolApp,然后为该元素添加 mousedown 事件监听器,当用户开始拖拽时调用 startDrag 方法。同时,它还为整个文档添加 mousemovemouseup 事件监听器,以便在拖拽过程中和拖拽结束时分别调用 draggingendDrag 方法。

  • startDrag
    当用户在搜索工具面板上按下鼠标按钮时,这个方法会被调用。它首先将 dragData.isDragging 设置为 true,表示开始拖拽。然后,它计算并保存鼠标按下时的初始位置偏移量,即鼠标当前位置与面板左上角的差值。

  • dragging
    这个方法在用户拖拽面板时被调用。如果 dragData.isDraggingtrue,即确认用户正在进行拖拽操作,它会根据鼠标当前位置更新面板的 lefttop 属性,从而移动面板位置。新的 lefttop 值是根据鼠标当前位置和之前保存的初始位置偏移量计算得出的。

  • endDrag
    当用户释放鼠标按钮结束拖拽时,这个方法会被调用。它将 dragData.isDragging 设置为 false,表示拖拽操作已经结束。

这组方法通过监听鼠标事件并相应地更新面板位置,实现了搜索工具面板的拖拽功能。用户可以通过点击并拖动面板来调整其在页面上的位置,从而根据自己的喜好和需求来放置面板。这种交互方式为用户提供了更多的灵活性和便利性。

(3)在DOM节点中搜索特定文本
escapeRegExp(string) {return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
},
searchInNode(node, query) {let walker = document.createTreeWalker(node,NodeFilter.SHOW_TEXT,null,false);let regex = query;try {regex = new RegExp(query, "ig");if (regex.test("")) throw new Error("");} catch (err) {query = this.escapeRegExp(query);regex = new RegExp(query, "ig");}let matches = [];while (walker.nextNode()) {if (regex.test(walker.currentNode.nodeValue)) {matches.push(walker.currentNode);}}if (matches.length > 0) {for (const match of matches) {const textNode = match;const text = textNode.nodeValue.replace(new RegExp("(" + query + ")", "ig"),'<span class="chromeSearchResultItem" style="background-color: yellow; color: black;">$1</span>');if (match.parentNode) match.parentNode.innerHTML = text;}}
}

这个方法接受两个参数:nodequerynode 是要搜索的DOM节点,而 query 是要搜索的文本或正则表达式。

    1. 使用 document.createTreeWalker 创建一个 TreeWalker 对象,它允许你遍历DOM树。NodeFilter.SHOW_TEXT 指定 TreeWalker 只考虑文本节点。
    1. 尝试将 query 转换为一个不区分大小写的全局正则表达式 regex。如果 query 无效(例如,它包含正则表达式特殊字符但没有被转义),则在测试空字符串时会抛出错误。
    1. 如果正则表达式抛出错误,使用 escapeRegExp 方法转义 query 字符串中的所有正则表达式特殊字符,然后再次创建正则表达式。
    1. 初始化一个空数组 matches 来存储所有匹配的文本节点。
    1. 使用 while 循环和 TreeWalker 对象遍历所有文本节点。如果当前节点的值(nodeValue)匹配正则表达式 regex,则将该节点添加到 matches 数组中。
    1. 如果 matches 数组中有匹配项,遍历这些匹配的文本节点,并使用 replace 方法将匹配的文本替换为带有高亮样式的HTML。这里创建了一个新的正则表达式,用于匹配查询字符串,并将其包裹在一个 <span> 元素中,该元素具有黄色背景和黑色文字的样式,加上类名chromeSearchResultItem作为标记。
    1. 如果匹配的文本节点有父节点,将其 innerHTML 属性设置为修改后的文本,这样就会在页面上实际替换文本并应用高亮样式。

这个方法有效地在指定的DOM节点及其子节点中搜索查询字符串,并将所有匹配的文本高亮显示,以便用户可以轻松地识别搜索结果。

(4)获取页面上满足条件的DOM元素
getChromeSearchResultItem(doc = document) {const domList = document.querySelectorAll(".chromeSearchResultItem");let matches = [...domList];matches = matches.filter((item) => {const rect = item.getBoundingClientRect();return rect.height > 0 && rect.width > 0;});matches.sort((a, b) => {const rectA = a.getBoundingClientRect();const rectB = b.getBoundingClientRect();if (rectA.top === rectB.top) return rectA.left - rectB.left;return rectA.top - rectB.top;});return {domList,matches,};
}

获取页面上所有标记为 .chromeSearchResultItem 的DOM元素。它首先使用 querySelectorAll 选择所有匹配的元素,然后通过 getBoundingClientRect 方法检查每个元素的尺寸,只保留那些具有非零高度和宽度的元素,这意味着它排除了那些不可见的元素(例如,由于样式设置为 display: nonevisibility: hidden 而隐藏的元素)。

接下来,该方法对可见的搜索结果进行排序。排序依据是元素在页面上的垂直位置(top 值),如果有多个元素位于同一垂直位置,则按水平位置(left 值)排序。这样,搜索结果就会按照它们在页面上的自然顺序排列。

最后,方法返回一个包含两个属性的对象:domList(所有可见的搜索结果元素的数组)和 matches(经过筛选和排序的搜索结果元素的数组)。

performCustomSearch(query) {this.clearFlag();if (query === "") return;localStorage.setItem("chromeSearchQueryKeyJY", query);this.searchInNode(document.body, query); // 在主页面中搜索const { domList, matches } = this.getChromeSearchResultItem(document);this.matches = [...matches];this.domList = domList;const iframes = document.querySelectorAll("iframe");for (let i = 0; i < iframes.length; i++) {try {const iframeDocument = iframes[i].contentDocument;if (iframeDocument.body)this.searchInNode(iframeDocument.body, query); // 在每个iframe内部搜索const { domList, matches } =this.getChromeSearchResultItem(iframeDocument);this.matches.push(...matches);this.domList.push(...domList);} catch (e) {console.error("Error accessing iframe content:", e);}}this.index = -1;this.showMatch = this.changeIndex(1);
}

首先调用 clearFlag 方法来清除之前的搜索标记,然后检查传入的搜索查询 query 是否为空。如果查询为空,则直接返回;如果不为空,则将查询保存到 localStorage

接着,方法调用 searchInNode 方法在当前文档的 body 节点中搜索查询字符串,并高亮显示所有匹配的文本。之后,它调用 getChromeSearchResultItem 方法来获取当前文档中所有可见的搜索结果。

如果页面包含 iframe,该方法会遍历每个 iframe,尝试访问其内容文档,并在每个 iframebody 节点中执行相同的搜索和结果获取操作。搜索结果被合并到 this.matchesthis.domList 中。

最后,方法将 index 设置为 -1(表示没有当前选中的搜索结果),并调用 changeIndex 方法来选择第一个搜索结果(通过传递 1 作为参数,表示向前移动一个结果)。

插件使用

1、下载插件源码

git clone https://gitee.com/zheng_yongtao/chrome-plug-in

2、插件初始化

  • 进入插件目录
cd .\chrome-plug-in\Chrome-searchTool\
  • 安装依赖
npm run init

3、插件打包

npm run build

打包完成后会生成一个dist包:

image.png

4、将生成的dist包载入浏览器

image.png

image.png

5、刷新页面,alt + g 唤出搜索框

image.png

源码地址

Gitee:https://gitee.com/zheng_yongtao/chrome-plug-in/tree/master/Chrome-searchTool

公众号

关注公众号『前端也能这么有趣』,获取更多有趣内容。

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/757363.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python3入门机器学习,知识点全面总结与代码实操示例

目录 写在前面的话 一、机器学习的基本任务与方法分类 机器学习的概念和定义 基本任务 二分类任务&#xff08;Binary Classification&#xff09;&#xff1a; 多分类任务&#xff08;Multi-class Classification&#xff09;&#xff1a; 多标签分类任务&#xff08;Mu…

unity 学习笔记 4.坐标系

下载源码 UnityPackage 目录 1.基础知识 1.1.世界坐标和局部坐标 1.2.屏幕坐标 2.坐标系转换 3.练习&#xff1a;判断鼠标单击的位置 1.基础知识 1.1.世界坐标和局部坐标 1.2.屏幕坐标 2.坐标系转换 3.练习&#xff1a;判断鼠标单击的位置 步骤&#xff1a; 将脚本挂载到小…

JVM 垃圾回收机制:探秘对象生死判定与高效回收算法

目录 一、JVM 对象生死判定 1.1 引用技术算法 1.2 可达性分型算法 二、引用 三、 回收方法区 四、垃圾回收算法 4.1 标记-清楚算法 4.2 标记-复制算法 4.3 标记-整理算法 JVM 程序计数器、虚拟机栈、本地方法栈随着线程而生&#xff0c;随着线程而灭。栈中的栈帧随着方法的…

选数异或 (AcWing 4645)

题目链接: https://www.acwing.com/problem/content/description/4648/ 题目描述: 评价: 这道题感觉还是蛮有意思的&#xff0c;难度适中&#xff0c;而且有一定的思维含量&#xff0c;值得反复品味。 思路: 首先我们定义一个数组g[N], 其中的每个元素g[i] 表示在所有 i<j…

STM32通信协议

STM32通信协议 STM32通信协议 STM32通信协议一、通信相关概念二、通信协议引脚作用三、通信方式四、采样方式五、电平信号六、通信对象 一、通信相关概念 通信接口 通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统 通信协议&#xff1a;制定…

Python 全栈体系【四阶】(十六)

第五章 深度学习 一、基本理论 2. 深度神经网络结构 2.1 感知机 2.1.1 生物神经元 感知机&#xff08;Perceptron&#xff09;&#xff0c;又称人工神经元&#xff08;Artificial neuron&#xff09;&#xff0c;它是生物神经元在计算机中的模拟。下图是一个生物神经元示意…

wayland(xdg_wm_base) + egl + opengles 使用 Assimp 加载带光照信息的材质文件Mtl 实现光照贴图的最简实例(十七)

文章目录 前言一、3d 立方体 model 属性相关文件1. cube1.obj2. cube1.Mtl3. 纹理图片 cordeBouee4.jpg二、实现光照贴图的效果1. 依赖库和头文件1.1 assimp1.2 stb_image.h2. egl_wayland_obj_cube1.cpp3. Matrix.h 和 Matrix.cpp4. xdg-shell-client-protocol.h 和 xdg-shell…

小程序跨端组件库 Mpx-cube-ui 开源:助力高效业务开发与主题定制

Mpx-cube-ui 是一款基于 Mpx 小程序框架的移动端基础组件库&#xff0c;一份源码可以跨端输出所有小程序平台及 Web&#xff0c;同时具备良好的拓展能力和可定制化的能力来帮助你快速构建 Mpx 应用项目。 Mpx-cube-ui 提供了灵活配置的主题定制能力&#xff0c;在组件设计开发阶…

【计算机视觉】Gaussian Splatting源码解读补充

本文旨在补充gwpscut创作的博文学习笔记之——3D Gaussian Splatting源码解读。 Gaussian Splatting Github地址&#xff1a;https://github.com/graphdeco-inria/gaussian-splatting 论文地址&#xff1a;https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/3d_gauss…

Minio的安装和Java使用示例

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Minio 是个基于 Go…

三段提交的理解

三阶段提交是在二阶段提交上的改进版本&#xff0c;3PC 最关键要解决的就是协调者和参与者同时挂掉的问题&#xff0c;所以3PC把2PC的准备阶段再次一分为二&#xff0c;这样三阶段提交。 处理流程如下 &#xff1a; 阶段一 协调者向所有参与者发出包含事务内容的 canCommit …

【MySQL】知识点 + 1

# &#xff08;1&#xff09;查询当前日期、当前时间以及到2022年1月1日还有多少天&#xff0c;然后通过mysql命令执行命令。 select curdate() AS 当前日期,curtime() AS 当前时间,datediff(2022-01-01, curdate()) AS 距离2022年1月1日还有天数;# &#xff08;2&#xff09;利…

Windows10蓝屏报错 inaccess boot device

当Windows 10操作系统启动时出现蓝屏错误“INACCESSIBLE_BOOT_DEVICE”时&#xff0c;这通常表明系统在尝试启动过程中无法识别或访问引导设备上的基本系统文件。这个问题可能是由以下几个原因引起的&#xff1a; 硬件兼容性问题&#xff1a; 硬盘控制器驱动不正确或丢失&#…

Go语言学习04~05 函数和面向对象编程

Go语言学习04-函数 函数是一等公民 <font color"Blue">与其他主要编程语言的差异</font> 可以有多个返回值所有参数都是值传递: slice, map, channel 会有传引用的错觉函数可以作为变量的值函数可以作为参数和返回值 学习函数式编程 可变参数 func s…

三种方式,浅谈 Cocos Creator 的动画添加

前言 虽然 Cocos 的官方文档对动画系统做了较详细的介绍&#xff0c;但是对于刚接触的同学&#xff08;比如我&#xff09;来说还是不太友好。尽管如此&#xff0c;我就按文档加社区帖子一起实践了一下。为了方便忘记后能快速捡起&#xff0c;所以就用我的方式结合使用场景&am…

python内存管理和垃圾回收一文详解(基于c语言源码底层逻辑)

引用计数器 首先我们大概回忆一下C语言中的环状双向链表&#xff0c;如图&#xff0c;在双向链表中对于一个结点来说会有前驱和后继&#xff1a; C语言中基本的定义方式如下&#xff1a; typedef struct {ElemType data; // 数据域Lnode* prior; // 前驱指针域Lnode* next;…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Hyperlink)

超链接组件&#xff0c;组件宽高范围内点击实现跳转。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。该组件仅支持与系统浏览器配合使用。 需要权限 使用网络时&#xff0c;需要申请权限ohos.per…

【PyTorch][chapter 22][李宏毅深度学习][ WGAN]【实战三】

前言&#xff1a; 本篇主要讲两个WGAN的两个例子&#xff1a; 1 高斯混合模型 WGAN实现 2 MNIST 手写数字识别 -WGAN 实现 WGAN 训练起来蛮麻烦的,如果要获得好的效果很多超参数需要手动设置 1&#xff1a; 噪声的维度 2: 学习率 3&#xff1a; 生成器&#xff0c;鉴别器…

【深度学习与神经网络】MNIST手写数字识别1

简单的全连接层 导入相应库 import torch import numpy as np from torch import nn,optim from torch.autograd import Variable import matplotlib.pyplot as plt from torchvision import datasets, transforms from torch.utils.data import DataLoader读入数据并转为ten…

[Python人工智能] 四十三.命名实体识别 (4)利用bert4keras构建Bert+BiLSTM-CRF实体识别模型

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解如何实现中文命名实体识别研究,构建BiGRU-CRF模型实现。这篇文章将继续以中文语料为主,介绍融合Bert的实体识别研究,使用bert4keras和kears包来构建Bert+BiLSTM-CRF模型。然而,该代码最终结…