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…

Java中的JVM加载机制。

在Java中&#xff0c;JVM&#xff08;Java虚拟机&#xff09;的类加载机制是Java平台的核心组件之一。它负责在运行时动态地将类的.class文件加载到内存中&#xff0c;并为这些类创建对应的java.lang.Class对象。这个机制确保了类的正确性和安全性&#xff0c;并为Java的“一次…

flink重温笔记(十八): flinkSQL 顶层 API ——实时数据Table化(涵盖全面实用的 API )

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 18 天啦&#xff01;很多小伙伴私信说&#xff0c;自己只会SQL语法来编写flinkSQL&#xff0c;如何使用代码来操作呢&#xff1f;因为工作中都是要用到代码编写的。还有小伙伴说&#xff0c;想要实现表是动态变化的&#…

STM32通信协议

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

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

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

JavaScript高级(十六)---Iterator迭代器/Generator生成器

什么是迭代器&#xff1f; 迭代器是一种特殊的对象&#xff0c;这个对象需要符合迭代协议&#xff08;iterator protocol&#xff09;&#xff0c;这个对象具有以下特点。 该对象有一个特定的next方法 const obj { next() { } } const obj { …

FFmpeg-- c++实现:音频流aac和视频流h264封装

文章目录 流程api核心代码muxer.hmuxer.cpp aac 和 h264 封装为视频流&#xff0c;封装为c的Muxter类 流程 分配视频文件上下文 int Init(const char *url); 创建流&#xff0c;赋值给视频的音频流和视频流 int AddStream(AVCodecContext *codec_ctx); 写视频流的head int Se…

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…

推免保研夏令营/预推免面试记录—北航网安

目录 0x00简述 0x01 面试经历 0x02 相关资料下载 0x00简述 没有夏令营,直接就是预推免,门槛基本等同于华五,或者说略弱于华五。招生规模较小,而且感觉导师方向一半是密码学,一半是那种类似于电信工的偏硬件方面。该校导师教授可以带两个学生,而副教授可以带一个。考…

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

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

网络工程师练习题4

网络工程师 通过拨号远程配置Cisco路由器时&#xff0c;应使用的接口是&#xff08;&#xff09;。 A.AUXB.ConsoleC. EthernetD.VTY 【答案】A 【解析】使用AUX端口连接一台Modem&#xff0c;通过拨号远程配置路由器。 在Cisco路由器上配置RIPv1路由协议&#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;利…

知识点拓展——rsync同步命令的应用

Linux目录下有100万个文件&#xff0c;如何快速删除 当然了&#xff0c;首先我们想到的一定是rm -f删除&#xff0c;但是rm -f删除的话 会报错&#xff0c;提示无法删除&#xff0c;因为太长了&#xff0c;rm命令无法删除 [rootlocalhost opt]#mkdir files [rootlocalhost o…

Windows10蓝屏报错 inaccess boot device

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