electron 实现浏览器全局搜索文本
electron 中查找文本无快捷键,使用浏览器Ctrl + F 实现搜索文本功能
1、 安装electron electron-builder
安装electron 和打包工具electron-builder
npm install electron electron-builder
.npmrc
# ELECTRON_MIRROR="https://repo.huaweicloud.com/electron/"
# ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
# ELECTRON_CUSTOM_DIR="{{ version }}"
# electron_mirror=https://npm.taobao.org/mirrors/electron/
shamefully-hoist=true
2、创建 启动文件以及 监听事件(Ctrl + F)
实现electron/main.js
import { app, BroeserWindow, ipcMain, globalShortcut, BrowserView } from 'electron'
import path from 'node:path'
let win;
let searchView;
function createWindow () {
win = new BrowserWindow({
title: '工具箱',
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
webSecurity: false,
preload: path.join(__dirname, './preload.js')
}
})
win.setMenuBarVisibility(false) // 隐藏菜单栏
// 加载文件
mainWindow.loadFile('dist/index.html')
}
// 当主窗口获得焦点时
mainWindow.on('focus', () => {
// 注册全局快捷键,打开搜索视图
globalShortcut.register('CommandOrControl+F', function () {
// 如果已经存在搜索视图,则直接返回
if (searchView) return
// 创建一个新的BrowserView
searchView = new BrowserView({
webPreferences: {
// nodeIntegration: true,
webviewTag: true,
devTools: true,
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
}
})
// console.log(searchView)
// 设置搜索视图的位置和大小
searchView.setBounds({ x: 0, y: 0, width: 360, height: 70 })
// 将搜索视图添加到主窗口中
mainWindow?.addBrowserView(searchView)
// 加载搜索视图中的HTML文件
// const indexHtml = path.join(RENDERER_DIST, 'index.html')
// searchView.webContents.loadFile(path.join(RENDERER_DIST, 'search.html'))
searchView.webContents.loadFile('./electron/search.html')
// 打开搜索视图的开发者工具
// searchView.webContents.openDevTools()
})
})
// 当主窗口失去焦点时
mainWindow.on('blur', () => {
// 取消注册全局快捷键
globalShortcut.unregister('CommandOrControl+F')
})
// 当在页面中找到内容时
mainWindow.webContents.on('found-in-page', (event, arg) => {
// console.log('found-in-page', event, arg)
// ipcMain
// console.log(searchView?.webContents)
// 向搜索视图发送找到的内容
searchView?.webContents.send('found-in-page', arg)
})
}
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
}
app.whenReady().then(() => {
createWindow();
})
//允许跨域访问
app.commandLine.appendSwitch('ignore-certificate-errors')
// 在主进程中添加chrome的禁用同源策略的方法
app.commandLine.appendSwitch('disable-site-isolation-trials')
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') app.quit()
})
ipcMain.on('search', (event, arg) => {
const searchObj = JSON.parse(arg)
mainWindow?.webContents.findInPage(searchObj.value, {
findNext: searchObj.start,
forward: searchObj.next
})
})
ipcMain.on('stop-search', (event, arg) => {
mainWindow?.webContents.stopFindInPage('clearSelection')
})
ipcMain.on('searchClose', (event, arg) => {
mainWindow?.webContents.stopFindInPage('clearSelection')
if (searchView) {
mainWindow?.removeBrowserView(searchView)
searchView = null
}
})
设置预加载页面 electron/preload.js
const { ipcRenderer , contextBridge} = require('electron')
// app中可直接使用 ipcRenderer 变量,和客户端进行通信
contextBridge.exposeInMainWorld('ipcRenderer', {
on(...args) {
const [channel, listener] = args
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
},
off(...args) {
const [channel, ...omit] = args
return ipcRenderer.off(channel, ...omit)
},
send(...args) {
const [channel, ...omit] = args
return ipcRenderer.send(channel, ...omit)
},
invoke(...args) {
const [channel, ...omit] = args
return ipcRenderer.invoke(channel, ...omit)
}
// You can expose other APTs you need here.
// ...
})
搜索页面 electron/search.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
.search-wrap {
padding: 10px;
background: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
border-radius: 4px;
}
.input {
width: 200px;
height: 30px;
padding-right: 10px;
border: 0;
outline: none;
border-radius: 5px;
}
.button {
display: inline-block;
background: transparent;
border: 0;
}
.icon {
width: 26px;
height: 26px;
font-size: 26px;
margin: 0;
padding: 0;
border-radius: 50%;
position: relative;
display: inline-block;
vertical-align: middle;
line-height: 1;
}
.icon:hover {
background-color: #e5e2e2;
}
.icon.next:hover::after {
border-color: #e5e2e2 transparent transparent transparent;
}
.icon.prev:hover::after {
border-color: transparent transparent #e5e2e2 transparent;
}
.prev::before,
.next::before,
.prev::after,
.next::after {
content: '';
position: absolute;
width: 0;
height: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-style: solid;
}
.prev::before {
border-width: 0 6px 6px 6px;
border-color: transparent transparent #000 transparent;
}
.prev::after {
border-width: 0 6px 6px 6px;
border-color: transparent transparent #fff transparent;
transform: translate(-50%, calc(-50% + 2px));
}
.next::before {
border-width: 6px 6px 0 6px;
border-color: #000 transparent transparent transparent;
}
.next::after {
border-width: 6px 6px 0 6px;
border-color: #fff transparent transparent transparent;
transform: translate(-50%, calc(-50% - 2px));
}
.count {
display: inline-block;
font-size: 12px;
margin-right: 6px;
}
</style>
</head>
<body>
<div class="search-wrap">
<input id="search" class="input" />
<div class="count" id="count">0/0</div>
<!-- <button class="button" id="searchBtn">搜索</button> -->
<button class="button icon prev" id="prev"></button>
<button class="button icon next" id="next"></button>
<button class="button icon" id="close">×</button>
</div>
<script>
window.onload = () => {
postMessage({ payload: 'removeLoading' }, '*')
document.getElementById('search').focus()
document.getElementById('search').oninput = () => {
const value = document.getElementById('search').value
if (!value) return
ipcRenderer.send('search', JSON.stringify({ value, start: true }))
}
document.getElementById('prev').onclick = () => {
const value = document.getElementById('search').value
if (!value) return
ipcRenderer.send('search', JSON.stringify({ value, next: false }))
}
document.getElementById('next').onclick = () => {
const value = document.getElementById('search').value
if (!value) return
ipcRenderer.send('search', JSON.stringify({ value, next: true }))
}
document.getElementById('close').onclick = () => {
ipcRenderer.send('searchClose')
}
ipcRenderer.on('found-in-page', (event, arg) => {
document.getElementById('count').innerText = `${arg.activeMatchOrdinal}/${arg.matches}`
})
}
</script>
</body>
</html>
打包时需将electron文件打入安装包
electron-builder.json5
{
asar: true,
// 需要打入安装包的文件
files: ['dist', 'electron'],
directories:{
output: 'dist-client' // 输出 客户端安装包位置
}
}