Electron 项目实战 03: 实现一个截图功能

实现效果

20240110195937.gif

实现思路

  1. 创建两个window,一个叫mainWindow,一个叫cutWindow
  2. mainWindow:主界面用来展示截图结果
  3. cutWindow:截图窗口,加载截图页面和截图交互逻辑
  4. mainWindow 页面点击截图,让cutWIndow 来实现具体截图逻辑
  5. cutWindow:截图完后把截图send给mainWindow页面

截图过程-时序图

%E6%88%AA%E5%9B%BE%E8%BF%87%E7%A8%8B.png

创建项目

我在网上找了一大圈,没有找到一个合适的模板,要么环境太老、要么配置各种缺失不完善、要么打包出来各种问题等等,说实话坑还真不少,无意间找到一个特别好的脚手架,它简单又完善。推荐给大家:electron-vite ,所以接下来直接用创建命令

yarn create @quick-start/electron

安装依赖

  • vue-router:切换加载首页和截图页面
  • konva:完成截图交互的库
yarn add konva vue-router

核心代码

为了更好的展示添加的内容,提供如下目录结构图方便理解

目录结构

Untitled.png

主进程

  • src/main/index.js

    import {app,shell,BrowserWindow,ipcMain,screen,desktopCapturer,globalShortcut
    } from 'electron'
    import { join } from 'path'
    import { electronApp, optimizer, is } from '@electron-toolkit/utils'
    import icon from '../../resources/icon.png?asset'let mainWindow
    let cutWindowfunction closeCutWindow() {cutWindow && cutWindow.close()cutWindow = null
    }function createMainWindow() {mainWindow = new BrowserWindow({width: 900,height: 670,show: false,autoHideMenuBar: true,...(process.platform === 'linux' ? { icon } : {}),webPreferences: {preload: join(__dirname, '../preload/index.js'),sandbox: false}})mainWindow.on('ready-to-show', () => {mainWindow.show()})mainWindow.webContents.setWindowOpenHandler((details) => {shell.openExternal(details.url)return { action: 'deny' }})console.log('loadURL:', process.env['ELECTRON_RENDERER_URL'])if (is.dev && process.env['ELECTRON_RENDERER_URL']) {mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])} else {mainWindow.loadFile(join(__dirname, '../renderer/index.html'))}mainWindow.on('closed', () => {closeCutWindow()})
    }function registerShortcut() {//! 截图快捷键globalShortcut.register('CommandOrControl+Alt+C', () => {openCutScreen()})globalShortcut.register('Esc', () => {closeCutWindow()mainWindow.show()})globalShortcut.register('Enter', sendFinishCut)
    }app.whenReady().then(() => {// Set app user model id for windowselectronApp.setAppUserModelId('com.electron')// Default open or close DevTools by F12 in development// and ignore CommandOrControl + R in production.// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils//! 开发模式:win 环境F12 和 mac os 环境:CommandOrControl + R 打开 DevToolsapp.on('browser-window-created', (_, window) => {optimizer.watchWindowShortcuts(window)})createMainWindow()registerShortcut()openMainListener()app.on('activate', function () {if (BrowserWindow.getAllWindows().length === 0) createMainWindow()})
    })app.on('window-all-closed', () => {if (process.platform !== 'darwin') {globalShortcut.unregisterAll()app.quit()}
    })function getSize() {const { size, scaleFactor } = screen.getPrimaryDisplay()return {width: size.width * scaleFactor,height: size.height * scaleFactor}
    }function createCutWindow() {const { width, height } = getSize()cutWindow = new BrowserWindow({width,height,autoHideMenuBar: true,useContentSize: true,movable: false,frame: false,resizable: false,hasShadow: false,transparent: true,fullscreenable: true,fullscreen: true,simpleFullscreen: true,alwaysOnTop: false,webPreferences: {preload: join(__dirname, '../preload/index.js'),nodeIntegration: true,contextIsolation: false}})console.log('createCutWindow:', is.dev, process.env['ELECTRON_RENDERER_URL'])if (is.dev && process.env['ELECTRON_RENDERER_URL']) {let url = process.env['ELECTRON_RENDERER_URL'] + '/#/cut'console.log('createCutWindow: loadURL=', url)cutWindow.loadURL(url)} else {cutWindow.loadFile(path.join(__dirname, '../renderer/index.html'))}cutWindow.maximize()cutWindow.setFullScreen(true)
    }function sendFinishCut() {cutWindow && cutWindow.webContents.send('FINISH_CUT')
    }function openCutScreen() {closeCutWindow()mainWindow.hide()createCutWindow()cutWindow.show()
    }function openMainListener() {ipcMain.on('OPEN_CUT_SCREEN', openCutScreen)ipcMain.on('SHOW_CUT_SCREEN', async (e) => {let sources = await desktopCapturer.getSources({types: ['screen'],thumbnailSize: getSize()})cutWindow && cutWindow.webContents.send('GET_SCREEN_IMAGE', sources[0])})ipcMain.on('FINISH_CUT_SCREEN', async (e, cutInfo) => {closeCutWindow()mainWindow.webContents.send('GET_CUT_INFO', cutInfo)mainWindow.show()})ipcMain.on('CLOSE_CUT_SCREEN', async (e) => {closeCutWindow()mainWindow.show()})
    }
    

渲染器

  • scr/main.js

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'const app = createApp(App)
    app.use(router)
    app.mount('#app')
    
  • src/router/index.js

    import { createRouter, createWebHashHistory } from 'vue-router'const routes = [{ path: '/', redirect: '/home' },{path: '/home',name: 'home',component: () => import('../pages/Home/index.vue')},{path: '/cut',name: 'cut',component: () => import('../pages/Cut/index.vue')}
    ]const router = createRouter({history: createWebHashHistory(),routes
    })export default router
    
  • src/App.vue

    <template><router-view></router-view>
    </template><script setup>
    </script><style lang="less">
    @import './assets/css/styles.less';
    </style>
    
  • src/pages/index.vue:首页

    <template><div class="container"><button @click="handleCutScreen">截屏</button><div><img :src="previewImage"style="max-width: 100%" /></div></div>
    </template><script setup>
    import { ref } from "vue";
    const { ipcRenderer } = window.electron;
    const previewImage = ref("");async function handleCutScreen() {await ipcRenderer.send("OPEN_CUT_SCREEN");ipcRenderer.removeListener("GET_CUT_INFO", getCutInfo);ipcRenderer.on("GET_CUT_INFO", getCutInfo);
    }function getCutInfo(event, pic) {previewImage.value = pic;
    }
    </script>
    
  • src/pages/cut.vue:截图界面

    <template><div class="container":style="'background-image:url(' + bg + ')'"ref="container"@mousedown="onMouseDown"@mousemove="onMouseMove"@mouseup="onMouseUp"></div>
    </template>
    <script setup>
    import Konva from "konva";
    import { ref, onMounted } from "vue";const { ipcRenderer } = window.electron;
    let container = ref(null);
    let bg = ref("");
    let stage, layer, rect, transformer;onMounted(() => {ipcRenderer.send("SHOW_CUT_SCREEN");ipcRenderer.removeListener("GET_SCREEN_IMAGE", getSource);ipcRenderer.on("GET_SCREEN_IMAGE", getSource);ipcRenderer.on("FINISH_CUT", confirmCut);
    });async function getSource(event, source) {const { thumbnail } = source;const pngData = await thumbnail.toDataURL("image/png");bg.value = pngData;render();
    }function render() {stage = createStage();layer = createLayer(stage);
    }function createStage() {return new Konva.Stage({container: container.value,width: window.innerWidth,height: window.innerHeight,});
    }function createLayer(stage) {let layer = new Konva.Layer();stage.add(layer);layer.draw();return layer;
    }function createRect(layer, x, y, width, height, alpha, draggable) {let rect = new Konva.Rect({x,y,width,height,fill: `rgba(0,0,255,${alpha})`,draggable});layer.add(rect);return rect;
    }let isDown = false;
    let rectOption = {};
    function onMouseDown(e) {if (rect || isDown) {return;}isDown = true;const { pageX, pageY } = e;rectOption.x = pageX || 0;rectOption.y = pageY || 0;rect = createRect(layer, pageX, pageY, 0, 0, 0.25, false);rect.draw();
    }function onMouseMove(e) {if (!isDown) return;const { pageX, pageY } = e;let w = pageX - rectOption.x;let h = pageY - rectOption.y;rect.remove();rect = createRect(layer, rectOption.x, rectOption.y, w, h, 0.25, false);rect.draw();
    }function onMouseUp(e) {if (!isDown) {return;}isDown = false;const { pageX, pageY } = e;let w = pageX - rectOption.x;let h = pageY - rectOption.y;rect.remove();rect = createRect(layer, rectOption.x, rectOption.y, w, h, 0, true);rect.draw();transformer = createTransformer(rect);layer.add(transformer);
    }function createTransformer(rect) {let transformer = new Konva.Transformer({nodes: [rect],rotateAnchorOffset: 60,enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right']});return transformer
    }/*** 根据选择区域生成图片* @param {*} info */
    async function getCutImage(info) {const { x, y, width, height } = info;let img = new Image();img.src = bg.value;let canvas = document.createElement("canvas");let ctx = canvas.getContext("2d");canvas.width = ctx.width = width;canvas.height = ctx.height = height;ctx.drawImage(img, -x, -y, window.innerWidth, window.innerHeight);return canvas.toDataURL("image/png");
    }/*** 确认截图*/
    async function confirmCut() {const { width, height, x, y, scaleX = 1, scaleY = 1 } = rect.attrs;let _x = width > 0 ? x : x + width * scaleX;let _y = height > 0 ? y : y + height * scaleY;let pic = await getCutImage({x: _x,y: _y,width: Math.abs(width) * scaleX,height: Math.abs(height) * scaleY,});ipcRenderer.send("FINISH_CUT_SCREEN", pic);
    }/*** 关闭截图*/
    function closeCut() {ipcRenderer.send("CLOSE_CUT_SCREEN");
    }
    </script><style lang="scss" scoped>
    .container {position: fixed;top: 0;bottom: 0;left: 0;right: 0;width: 100%;height: 100%;overflow: hidden;background-color: transparent;background-size: 100% 100%;background-repeat: no-repeat;border: 2px solid blue;box-sizing: border-box;
    }
    </style>
    

总结

虽然实现了核心功能,但是仅支持主屏幕截图,不支持多屏幕截图,同时还遗留诸多问题,后面单独一篇更新解决

完整demo :传送门,顺便帮忙点个star,感谢~

参考文献

  • https://juejin.cn/post/7111115472182968327
  • https://www.electronjs.org/docs/latest/tutorial/keyboard-shortcuts
  • https://konvajs.org/docs/select_and_transform/Basic_demo.html
  • https://stackoverflow.com/questions/40360109/content-security-policy-img-src-self-data/62213224#62213224

更多

家人们,我最近花了2个多月开源了一个文章发布助手artipub,可以帮你一键将markdown发布至多平台(发布和更新),方便大家更好的传播知识和分享你的经验。
目前已支持平台:个人博客、Medium、Dev.to(未来会支持更多平台)
官网地址:https://artipub.github.io/artipub/
仓库地址:https://github.com/artipub/artipub

目前库已可以正常使用,欢迎大家体验、如果你有任何问题和建议都可以在Issue给我进行反馈。
如果你感兴趣,特别欢迎你的加入,让我们一起完善好这个工具。
帮忙点个star⭐,让更多人知道这个工具,感谢大家🙏

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

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

相关文章

‌智慧公厕:城市文明的智慧新篇章‌@卓振思众

在日新月异的城市化进程中&#xff0c;公共设施的智能化升级已成为不可逆转的趋势。其中&#xff0c;智慧公厕作为城市智慧化建设的重要组成部分&#xff0c;正悄然改变着我们的生活。智慧公厕&#xff0c;这一融合了物联网、大数据、云计算等现代信息技术的创新产物&#xff0…

Django Admin管理后台导入CSV

修改管理模型&#xff0c;代码如下&#xff1a; class CsvImportForm(forms.Form):csv_file forms.FileField() admin.register(Hero) class HeroAdmin(admin.ModelAdmin, ExportCsvMixin):...change_list_template "entities/heroes_changelist.html"def get_url…

Opencv中的直方图(2)计算图像的直方图函数calcHist()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算一组数组的直方图。 函数 cv::calcHist 计算一个或多个数组的直方图。用于递增直方图bin的元组的元素是从相同位置的相应输入数组中获取的。…

MATLAB中cond函数用法

目录 语法 说明 示例 矩阵的条件数 1-范数条件数 cond函数的功能是返回逆运算的条件数。 语法 C cond(A) C cond(A,p) 说明 C cond(A) 返回 2-范数逆运算的条件数&#xff0c;等于 A 的最大奇异值与最小奇异值之比。 C cond(A,p) 返回 p-范数条件数&#xff0c;其中…

虚幻地形高度图生成及测试

虚幻地形高度图生成及测试 虚幻引擎地形系统将高度数据存储在高度图中&#xff0c;这是一个灰阶图像&#xff0c;使用黑白色值来存储地貌高程。在高度图中&#xff0c;纯黑色值表示最低点&#xff0c;纯白色值表示最高点。支持16位灰阶PNG、8位灰阶r8及16位灰阶r16格式。 本文…

C++设计模式——Template Method模板方法模式

一&#xff0c;模板方法模式的定义 模板方法模式是一种行为型设计模式&#xff0c;它先定义了一个算法的大致框架&#xff0c;然后将算法的具体实现步骤分解到多个子类中。 模板方法模式为算法设计了一个抽象的模板&#xff0c;算法的具体代码细节由子类来实现&#xff0c;从…

springboot高校实验室教学管理系统的设计和实现

基于springbootvue高校实验室教学管理系统的设计和实现(源码L文ppt)4-045 4 系统总体设计 此次高校实验室教学管理系统通过springboot框架。springboot适合快速构建Web应用。springboot将B/S设计模式中的视图分成了View模块和Template模块两部分&#xff0c;将动态的逻辑处理…

传统CV算法——基于opencv的答题卡识别判卷系统

基于OpenCV的答题卡识别系统&#xff0c;其主要功能是自动读取并评分答题卡上的选择题答案。系统通过图像处理和计算机视觉技术&#xff0c;自动化地完成了从读取图像到输出成绩的整个流程。下面是该系统的主要步骤和实现细节的概述&#xff1a; 1. 导入必要的库 系统首先导入…

亚信安全荣获“2024年网络安全优秀创新成果大赛”优胜奖

近日&#xff0c;由中央网信办网络安全协调局指导、中国网络安全产业联盟&#xff08;CCIA&#xff09;主办的“2024年网络安全优秀创新成果大赛”评选结果公布。亚信安全信舱ForCloud荣获“创新产品”优胜奖&#xff0c;亚信安全“宁波市政务信息化网络数据安全一体化指挥系统…

C语言 | Leetcode C语言题解之第392题判断子序列

题目&#xff1a; 题解&#xff1a; bool isSubsequence(char* s, char* t) {int n strlen(s), m strlen(t);int f[m 1][26];memset(f, 0, sizeof(f));for (int i 0; i < 26; i) {f[m][i] m;}for (int i m - 1; i > 0; i--) {for (int j 0; j < 26; j) {if (t…

初级python代码编程学习----简单的图形化聊天工具

创建一个图形化的聊天工具通常需要使用编程语言和图形用户界面库。以下是一个使用Python和Tkinter库创建的基本图形化聊天工具的代码示例&#xff1a; 代码 import tkinter as tk from tkinter import scrolledtext # 创建主窗口 root tk.Tk() root.title("图形化聊天…

Echarts 绘制地图省、市、区、县(以及点击显示下级,支持坐标定位)

** Echarts 绘制地图省、市、区、县&#xff08;以及点击显示下级&#xff0c;支持坐标定位&#xff09; ** 上代码 <template><div class"mapCont"><div id"mapSelf" contextmenu.prevent"disableContextMenu"></div&g…

光盘安全隔离与信息单向导入系统-信刻

信刻从用户需求出发&#xff0c;为更多用户提供安全可靠的跨网数据单向导入/导出光盘摆渡系统解决方案&#xff0c;解决内外网数据交换的问题&#xff0c;确保数据交换过程的安全性。 公司所研发出的光盘安全隔离与信息单向导入系统依托软硬件相结合的技术&#xff0c;集策略摆…

热点王炸模型!准确率高达100%!Transformer+GASF+RP-1D-2D-GRU 小白也能发一区!创新性拉满!

适用平台&#xff1a;Matlab2023b版及以上 参考文献一&#xff1a;中文EI期刊 《西安交通大学学报》 文献&#xff1a;《采用格拉姆角场-卷积神经网络-时序卷积网络混合模型的锂离子电池健康状态估计》中的①时序图像融合模型&#xff1b;②一维时序转格拉姆角场模型。参考文献…

演示:基于WPF的DrawingVisual和谷歌地图瓦片开发的地图(完全独立不依赖第三方库)

一、目的&#xff1a;基于WPF的DrawingVisual和谷歌地图瓦片开发的地图 二、预览 三、环境 VS2022&#xff0c;Net7,DrawingVisual&#xff0c;谷歌地图瓦片 四、主要功能 地图缩放&#xff0c;平移&#xff0c;定位 真实经纬度 显示瓦片信息 显示真实经纬度和经纬线 省市县…

[工具使用]git

git fetch 获取远程仓库内容&#xff0c;但未合入本地仓库&#xff1b; git rebase 获取远程仓库内容&#xff0c;并更改基地合入本地仓库&#xff1b; 将master分支的内容合入feature分支&#xff1a; 当在feature分支上执行git rebase master时&#xff0c;git会从master…

结构型设计模式—桥接模式

结构型设计模式—桥接模式 欢迎长按图片加好友&#xff0c;我会第一时间和你分享持续更多的开发知识&#xff0c;面试资源&#xff0c;学习方法等等。 假设你要买一张新桌子&#xff0c;你有两个选择&#xff1a;一种是木制的桌子&#xff0c;另一种是金属制的桌子。 无论你选…

PostgreSQL的repmgr工具介绍

PostgreSQL的repmgr工具介绍 repmgr&#xff08;Replication Manager&#xff09;是一个专为 PostgreSQL 设计的开源工具&#xff0c;用于管理和监控 PostgreSQL 的流复制及实现高可用性。它提供了一组工具和实用程序&#xff0c;简化了 PostgreSQL 复制集群的配置、维护和故障…

成本最高可降40%!揭秘DataWorks资源组的Serverless进化之路

导读 在当今数据驱动的时代&#xff0c;高效、灵活地处理和分析海量数据成为企业数字化转型的关键。阿里云一站式智能大数据开发治理平台DataWorks具备强大的数据建模、数据集成、数据开发、数据分析和数据治理等功能&#xff0c;为企业提供了从数据接入到价值洞察的全链路解决…

Socket编程---TCP篇

目录 一. TCP协议 二. 服务端模块代码实现 三. 服务端调用模块代码实现 四. 客户端模块代码实现 五. 初始版本结果展示 六. 多进程版服务端 七. 多线程版服务端 八. 线程池版服务端 前文已经讲了UDP的知识&#xff08;点此查看&#xff09;。今天来讲讲…