记录--手把手教你,用electron实现截图软件
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
背景
因为我们日常开发项目的时候,需要和同事对接api和文档还有UI图,所以有时候要同时打开多个窗口,并在多个窗口中切换,来选择自己要的信息,如果api和文档不多的情况还好,但是有时候就是要做大量的页面,为了提升效率我决定自己做一个截图工具,并把自己要的信息截图钉在窗口上。
在做之前先看看最终展示效果吧:
- 先是截图

- 截图后的图片展示

工具
- nodejs
- pnpm
- electron
- vite
- react
实现
原理逻辑
其实也并不难理解,首先是主窗体发起截图请求,然后会打开另一个负责截图透明且全屏的窗体,唤起后透明窗体会让electron截取整个屏幕发给逻辑页面,页面会把图片绘制满屏实现定格效果,然后再用canvas做绘制区域的生成,根据生成出的区域对刚才满屏图片进行裁切导出,最后传递给主窗体去显示还可以存到剪贴板种。
具体的api可以看看官方文档: www.electronjs.org/zh/docs/lat…
路由配置
本次开发使用了electron-vite-react,具体构建和配置就是用它的默认配置就好了,值得注意的是,本次需要要做三个窗体,一个主窗体,一个截屏窗体,一个是图片展示窗体,于是索性就引入react-router-dom了。
先来安装一下:
git clone https://github.com/electron-vite/electron-vite-react pnpm add react-router-dom pnpm add antd
但是要注意的是,我们需要把路由设置成hash模式,不然本地打包时会无法找到。
import type { FC } from "react";
import { Routes, Route } from "react-router-dom";
import { Provider } from "react-redux";
import { store } from "@/stores";
import "./index.scss";
import Home from "@/pages/home";
import ShotScreen from "@/pages/shotScreen";
import ViewImage from "@/pages/viewImage";
const App: FC = () => (
<Provider store={store}>
<div className="app">
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/shotScreen" element={<ShotScreen />}></Route>
<Route path="/viewImage" element={<ViewImage />}></Route>
</Routes>
</div>
</Provider>
);
export default App;
主窗体
我们先准备好主页面Home,里面很简单,就是放入一个按钮然后点击按钮开打截屏页
import React, {
useEffect,
useState,
useImperativeHandle,
forwardRef,
} from "react";
import { ScissorOutlined } from "@ant-design/icons";
import { Button, Card } from "antd";
import { ipcRenderer } from "electron";
const ShotScreenCard = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
handleCutScreen,
}));
const [isCutScreen, setIsCutScreen] = useState(true);
function handleCutScreen() {
ipcRenderer.send("ss:open-win");
}
return (
<Card
title="截屏"
hoverable
bordered={false}
extra={<a href="#">更多</a>}
style={{ maxWidth: 300 }}
onClick={handleCutScreen}
>
<div className="cardContent">
<ScissorOutlined />
</div>
</Card>
);
});
export default ShotScreenCard;
截图页
在这里我也尝试过自己用Konva自己手写一个截图页,但是功能实在太多了,最后还是放弃了,如果大家有兴趣可以自己尝试,在这里我介绍两个不多的插件:
- react-screenshots: github.com/nashaofu/sc…
- js-web-screen-shot github.com/likaia/js-s…
这样截图页很简单,我们使用react-screenshots来帮我们实现截图功能,代码如下:
import React, { useCallback, useEffect, useState } from "react";
import Screenshots, { Bounds } from "react-screenshots";
import { ipcRenderer } from "electron";
import "react-screenshots/lib/style.css";
import "./index.scss";
export default function ShotScreen() {
const [screenShotImg, setScreenShotImg] = useState("");
useEffect(() => {
getShotScreenImg();
}, []);
async function getShotScreenImg() {
const img = await ipcRenderer.invoke("ss:get-shot-screen-img");
setScreenShotImg(img);
return img;
}
const onSave = useCallback((blob: Blob, bounds: Bounds) => {
const downloadUrl = URL.createObjectURL(blob);
ipcRenderer.send("ss:download-img", downloadUrl);
}, []);
const onCancel = useCallback(() => {
ipcRenderer.send("ss:close-win");
}, []);
const onOk = useCallback((blob: Blob, bounds: Bounds) => {
const downloadUrl = URL.createObjectURL(blob);
ipcRenderer.send("ss:save-img", downloadUrl);
}, []);
return (
<Screenshots
url={screenShotImg}
width={window.innerWidth}
height={window.innerHeight}
onSave={onSave}
onCancel={onCancel}
onOk={onOk}
/>
);
}
electron 通讯
web页面和electron 之间需要通讯,来获取屏幕的图片,具体可以看文档:www.electronjs.org/zh/docs/lat…, 代码入下:
// 截图
ipcMain.handle("ss:get-shot-screen-img", async () => {
const { width, height } = getScreenSize();
const sources = [
...(await desktopCapturer.getSources({
types: ["screen"],
thumbnailSize: {
width,
height,
},
})),
];
const source = sources.filter((e: any) => e.id == "screen:0:0")[0];
const img = source.thumbnail.toDataURL();
return img;
}); ipcMain.on("ss:open-win", () => {
closeShotScreenWin();
hideMainWin();
openShotScreenWin();
}); ipcMain.on("ss:close-win", () => {
closeShotScreenWin();
}); ipcMain.on("ss:save-img", async (e, downloadUrl) => {
downloadURLShotScreenWin(downloadUrl);
await openViewImageWin(true);
}); ipcMain.on("ss:download-img", async (e, downloadUrl) => {
downloadURLShotScreenWin(downloadUrl, true);
}); ipcMain.handle("ss:get-desktop-capturer-source", async () => {
return [
...(await desktopCapturer.getSources({ types: ["screen"] })),
...(await selfWindws()),
];
});
截图窗口的设置
截图窗口就像一个100%透明的玻璃浮在我们的电脑屏幕上,这时候我们就要设置他的 width:100%,height:100%,不可移动,并且透明,具体配置如下:
import {
app,
BrowserWindow,
shell,
dialog,
DownloadItem,
WebContents,
clipboard,
nativeImage,
} from "electron";
import path from "node:path";
import { getScreenSize, preload, url, indexHtml, PUBLIC } from "./utils";
import { getFilePath, setHistoryImg } from "./store";
let shotScreenWin: BrowserWindow | null = null;
let savePath: string = "";
function createShotScreenWin(): BrowserWindow {
const { width, height } = getScreenSize();
shotScreenWin = new BrowserWindow({
title: "pear-rec 截屏",
icon: path.join(PUBLIC, "logo@2x.ico"),
width, // 宽度(px), 默认值为 800
height, // 高度(px), 默认值为 600
autoHideMenuBar: true, // 自动隐藏菜单栏
useContentSize: true, // width 和 height 将设置为 web 页面的尺寸
movable: false, // 是否可移动
frame: false, // 无边框窗口
resizable: false, // 窗口大小是否可调整
hasShadow: false, // 窗口是否有阴影
transparent: true, // 使窗口透明
fullscreenable: true, // 窗口是否可以进入全屏状态
fullscreen: true, // 窗口是否全屏
simpleFullscreen: true, // 在 macOS 上使用 pre-Lion 全屏
alwaysOnTop: false, // 窗口是否永远在别的窗口的上面
webPreferences: {
preload,
nodeIntegration: true,
contextIsolation: false,
},
});
// shotScreenWin.webContents.openDevTools();
if (url) {
shotScreenWin.loadURL(url + "#/shotScreen");
} else {
shotScreenWin.loadFile(indexHtml, {
hash: "shotScreen",
});
}
shotScreenWin.maximize();
shotScreenWin.setFullScreen(true);
shotScreenWin?.webContents.session.on(
"will-download",
(e: any, item: DownloadItem, webContents: WebContents) => {
const fileName = item.getFilename();
const filePath = getFilePath() as string;
const ssFilePath = path.join(savePath || `${filePath}/ss`, `${fileName}`);
item.setSavePath(ssFilePath);
item.once("done", (event: any, state: any) => {
if (state === "completed") {
copyImg(ssFilePath);
setHistoryImg(ssFilePath);
setTimeout(() => {
closeShotScreenWin();
// shell.showItemInFolder(ssFilePath);
}, 1000);
}
});
},
);
return shotScreenWin;
}
// 打开关闭录屏窗口
function closeShotScreenWin() {
shotScreenWin?.isDestroyed() || shotScreenWin?.close();
shotScreenWin = null;
}
function openShotScreenWin() {
if (!shotScreenWin || shotScreenWin?.isDestroyed()) {
shotScreenWin = createShotScreenWin();
}
shotScreenWin?.show();
}
function showShotScreenWin() {
shotScreenWin?.show();
}
function hideShotScreenWin() {
shotScreenWin?.hide();
}
function minimizeShotScreenWin() {
shotScreenWin?.minimize();
}
function maximizeShotScreenWin() {
shotScreenWin?.maximize();
}
function unmaximizeShotScreenWin() {
shotScreenWin?.unmaximize();
}
async function downloadURLShotScreenWin(
downloadUrl: string,
isShowDialog?: boolean,
) {
savePath = "";
isShowDialog && (savePath = await showOpenDialogShotScreenWin());
shotScreenWin?.webContents.downloadURL(downloadUrl);
}
async function showOpenDialogShotScreenWin() {
let res = await dialog.showOpenDialog({
properties: ["openDirectory"],
});
const savePath = res.filePaths[0] || "";
return savePath;
}
function copyImg(filePath: string) {
const image = nativeImage.createFromPath(filePath);
clipboard.writeImage(image);
}
export {
createShotScreenWin,
closeShotScreenWin,
openShotScreenWin,
showShotScreenWin,
hideShotScreenWin,
minimizeShotScreenWin,
maximizeShotScreenWin,
unmaximizeShotScreenWin,
downloadURLShotScreenWin,
};
效果图
总结Q&A
文章写到这里基本结束了,简单回顾下文章的内容。
- Q:为什么没有用
Electron Forge?
一开始我是使用Electron Forge,但是最后放弃了,原因有两个:1. 编译太慢,不知道是不是webpack的原因,但是和vite比真的太慢了!!!2.Electron Forge使用的是Electron Package打包,也不太自定义,所以最后放弃。。
- Q: 有源码吗?
当然有,地址如下:github.com/027xiguapi/…,有兴趣的话可以大家一起探讨,同时也欢迎大家fork和star
本文转载于:
https://juejin.cn/post/7239514481755127845
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--手把手教你,用electron实现截图软件的更多相关文章
- 手把手教你创建electron+vue项目
electron可以结合前端的很多框架来进行快速的开发.在这里只讲一种electron结合vue并且使用前端的elementui来快速搭建一个项目页面. 第一步.创建electron 以管理员身份运行 ...
- 干货!手把手教你使用数据可视化BI软件创建企业变更流程监控大屏
灯果数据可视化BI软件是新一代人工智能数据可视化大屏软件,内置丰富的大屏模板,可视化编辑操作,无需任何经验就可以创建属于你自己的大屏.大家可以在他们的官网下载软件. 本文以企业变更流程监控大屏为例 ...
- 不懂怎么创建可视化大屏?手把手教你使用数据可视化BI软件创建工厂车间数据监控大屏
灯果数据可视化BI软件是新一代人工智能数据可视化大屏软件,内置丰富的大屏模板,可视化编辑操作,无需任何经验就可以创建属于你自己的大屏.大家可以在他们的官网下载软件. 本文以工厂车间数据监控大屏为例 ...
- 手把手教你使用数据可视化BI软件创建仓库可视化管理大屏
灯果数据可视化BI软件是新一代人工智能数据可视化大屏软件,内置丰富的大屏模板,可视化编辑操作,无需任何经验就可以创建属于你自己的大屏.大家可以在他们的官网下载软件. 本文以仓库可视化管理大屏为例为 ...
- 手把手教你用思维导图软件iMindMap制作计划表
在日常生活中小编也经常使用思维导图软件iMindMap来创建思维导图以规划工作及学习的安排.尤其是时间安排类型的思维导图,能极大程度的节约我们的时间,接下来就由小编以自己假期的社会实践向大家分享一下怎 ...
- 手把手教你调试Linux C++ 代码(一步到位包含静态库和动态库调试)
手把手教你调试Linux C++ 代码 软件调试本身就是一项相对复杂的活动,他不仅要求调试者有着清晰的思路,而且对调试者本身的技能也有很高的要求.Windows下Visual Studio为我们做了很 ...
- 每天记录一点:NetCore获得配置文件 appsettings.json vue-router页面传值及接收值 详解webpack + vue + node 打造单页面(入门篇) 30分钟手把手教你学webpack实战 vue.js+webpack模块管理及组件开发
每天记录一点:NetCore获得配置文件 appsettings.json 用NetCore做项目如果用EF ORM在网上有很多的配置连接字符串,读取以及使用方法 由于很多朋友用的其他ORM如S ...
- 《手把手教你》系列技巧篇(五十九)-java+ selenium自动化测试 - 截图三剑客 -上篇(详细教程)
1.简介 今天本来是要介绍远程测试的相关内容的,但是宏哥在操作服务器的时候干了件糊涂的事,事情经过是这样的:本来申请好的Windows服务器用来做演示的,可是服务器可能是局域网的,连百度都不能访问,宏 ...
- 《手把手教你》系列技巧篇(六十)-java+ selenium自动化测试 - 截图三剑客 -中篇(详细教程)
1.简介 前面我们介绍了Selenium中TakeScreenshot类来截图,得到的图片是浏览器窗口内的截图.有时候,只截浏览器窗口内的图是不够的,而且TakeScreenshot截图只针对浏览器的 ...
- 《手把手教你》系列技巧篇(六十一)-java+ selenium自动化测试 - 截图三剑客 -下篇(详细教程)
1.简介 按照计划宏哥今天将介绍java+ selenium自动化测试截图操作实现的第三种截图方法,也就是截图的第三剑客 - 截取某个元素(或者目标区域)的图片.在测试的过程中,有时候不需要截取整个屏 ...
随机推荐
- JS Leetcode 503. 下一个更大元素 II 题解分析,依旧单调栈做法解决此题
壹 ❀ 引 我在JS Leetcode 496. 下一个更大元素 I 更清晰的图解单调栈做法一文中,介绍了单调栈做法解决下一个更大元素的问题,比较巧的是这道题还有升级版,题目来自Leetcode503 ...
- NC14419 线路规划
题目链接 题目 题目描述 Q国的监察院是一个神秘的组织. 这个组织掌握了整个帝国的地下力量,监察着Q国的每一个人. 监察院一共有N个成员,每一个成员都有且仅有1个直接上司,而他只听从其上直接司的命令. ...
- Ehcache 3.x 笔记
现在Ehcache版本已经到3.10了, 网上查到的大部分还是2.x版本的使用说明, 把基础用法记了一下, 以后有时间再翻译. 基础使用, 创建 CacheManager CacheManager c ...
- C++ 多线程的错误和如何避免(8)
不要重复获取同一个锁 问题:在获得一个锁并且没有释放该锁的前提下,再次尝试获取该锁会报错. 比如, #include <iostream> #include <thread> ...
- Taurus.MVC WebMVC 入门开发教程2:一个简单的页面呈现
前言: 在上一篇中,我们了解了如何下载.配置和运行 Taurus.MVC WebMVC 框架. 现在,让我们开始编写一个简单的页面并进行呈现. 步骤1:创建控制器 首先,我们需要创建一个控制器来处理页 ...
- pexpect模块(替代subprocess)
https://blog.csdn.net/pcn01/article/details/104993742/
- 06-Redis系列之-哨兵(Redis-Sentinel)和集群详解和搭建
主从架构高可用 主从架构存在的问题 主从复制,主节点发生故障,需要做故障转移.(可以手动转移:让其中一个slave变成master) 主从复制,只有主写数据,所以写能力和存储能力有限 总结:redis ...
- ASP.NET Core MVC应用模型的构建[3]: Controller的收集
从编程的角度来看,一个MVC应用是由一系列Controller类型构建而成的,所以对于一个代表应用模型的ApplicationModel对象来说,它的核心就是Controllers属性返回的一组Con ...
- consul 的 HTTP API 和使用方法
目录 搭建起 consul Consul 的 HTTP API Service API 简单地注册服务 健康检查的配置和查询 建议读者先学习笔者的另一篇文章 学习搭建 Consul 服务发现与服务网格 ...
- Linux_Centos_yum报错总结
此篇适用于yum报错[尝试其他镜像]并且[curl 外网]不通的情况,此时一般考虑是网络的问题 一,出现的报错信息: 此时测试curl / ping www.baidu.com会发现无法连通 二, ...
