electron 实现浏览器全局搜索文本

1,126 阅读3分钟

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">&times;</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' // 输出 客户端安装包位置
	}
}