记录--手把手教你,用electron实现截图软件

这篇具有很好参考价值的文章主要介绍了记录--手把手教你,用electron实现截图软件。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--手把手教你,用electron实现截图软件

背景

因为我们日常开发项目的时候,需要和同事对接api文档还有UI图,所以有时候要同时打开多个窗口,并在多个窗口中切换,来选择自己要的信息,如果api文档不多的情况还好,但是有时候就是要做大量的页面,为了提升效率我决定自己做一个截图工具,并把自己要的信息截图钉在窗口上。

在做之前先看看最终展示效果吧:

  • 先是截图

记录--手把手教你,用electron实现截图软件

  • 截图后的图片展示

记录--手把手教你,用electron实现截图软件

工具

  • nodejs
  • pnpm
  • electron
  • vite
  • react

实现

原理逻辑

其实也并不难理解,首先是主窗体发起截图请求,然后会打开另一个负责截图透明且全屏的窗体,唤起后透明窗体会让electron截取整个屏幕发给逻辑页面,页面会把图片绘制满屏实现定格效果,然后再用canvas做绘制区域的生成,根据生成出的区域对刚才满屏图片进行裁切导出,最后传递给主窗体去显示还可以存到剪贴板种。

具体的api可以看看官方文档: www.electronjs.org/zh/docs/lat…

记录--手把手教你,用electron实现截图软件

路由配置

本次开发使用了electron-vite-react,具体构建和配置就是用它的默认配置就好了,值得注意的是,本次需要要做三个窗体,一个主窗体,一个截屏窗体,一个是图片展示窗体,于是索性就引入react-router-dom了。

记录--手把手教你,用electron实现截图软件

先来安装一下:

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,里面很简单,就是放入一个按钮然后点击按钮开打截屏页

记录--手把手教你,用electron实现截图软件

        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…

记录--手把手教你,用electron实现截图软件

记录--手把手教你,用electron实现截图软件

这样截图页很简单,我们使用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,
};

效果图

记录--手把手教你,用electron实现截图软件

总结Q&A

文章写到这里基本结束了,简单回顾下文章的内容。

  • Q:为什么没有用Electron Forge?

一开始我是使用Electron Forge,但是最后放弃了,原因有两个:1. 编译太慢,不知道是不是webpack的原因,但是和vite比真的太慢了!!!2.Electron Forge使用的是Electron Package打包,也不太自定义,所以最后放弃。。

  • Q: 有源码吗?

当然有,地址如下:github.com/027xiguapi/…,有兴趣的话可以大家一起探讨,同时也欢迎大家forkstar

本文转载于:

https://juejin.cn/post/7239514481755127845

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录--手把手教你,用electron实现截图软件文章来源地址https://www.toymoban.com/news/detail-529783.html

到了这里,关于记录--手把手教你,用electron实现截图软件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 手把手教你用video实现视频播放功能

    哈喽。大家好啊 今天需要做一个视频播放列表,让我想到了video的属性 下面让我们先看看实现效果 这里是我的代码 width是当前播放页面的宽度 height是当前播放页面的高度 Controls属性用就是控制栏那些了 比如播放按钮 暂停按钮 autoplay是指的是自动播放 poster是指的是初始化进

    2024年02月12日
    浏览(35)
  • 手把手教你快速实现内网穿透

    前言 要想实现在公网访问到本地的项目,除了将其部署到云服务器上外,还可以通过内网穿透来实现,避免了项目出现问题修改后需要重新部署到云服务器上比较繁琐的步骤,也不需要公网IP以及服务器。 内网穿透,简单来说就是通过公网IP服务器进行数据流量转发,将原本

    2024年02月13日
    浏览(29)
  • 深入浅出:手把手教你实现单链表

    链表是一种 链状数据结构 。简单来说,要存储的数据在内存中分别独立存放,它们之间通过某种方式相互关联。 如果我们使用C语言来实现链表,需要声明一个 结构体 作为链表的结点,结点之间使用指针关联。 单向链表的每个结点内都有一个指针指向下一个结点,从而把所

    2024年02月10日
    浏览(32)
  • 从0到1,手把手带你开发截图工具ScreenCap------002实现设置默认保存的图片位置

    在ScreenCap实现截图功能后增加设置图片默认保存位置的功能 实现选择文件夹作为截图的默认保存位置 注:博主所有资源永久免费,若有帮助,请点赞转发是对我莫大的帮助 注:博主本人学习过程的分享,引用他人的文章皆会标注原作者 注:本人文章非盈利性质,若有侵权请

    2024年02月05日
    浏览(62)
  • 手把手教你实现一个循环队列(C语言)

    这是一道leetcode关于队列的经典题: 622. 设计循环队列 https://leetcode.cn/problems/design-circular-queue/  大家注意这个题目要求,这个队列是定长的,如果满了则不能再添加数据。那么我们设计一个队头front和队尾rear,每次添加数据rear向后走,这时就有一个问题, 怎么区分空和满呢

    2024年02月04日
    浏览(45)
  • 手把手教你用Python实现2048小游戏

    感觉好久没有写小游戏玩了,今天恰巧有空.这次我来用Python做个2048小游戏吧.废话不多说,文中有非常详细的代码示例,需要的朋友可以参考下 目录 一、开发环境 二、环境搭建 三、原理介绍 四、效果图 Python版本:3.6.4 相关模块: pygame模块; 以及一些Python自带的模块。 安装

    2024年04月28日
    浏览(43)
  • 手把手教你Shiro整合JWT实现登录认证

    SpringBoot Mybatis-plus Shiro JWT Redis Shiro: Shiro 是一个基于 Java 的开源的安全框架。 在 Shiro 的核心架构里面,Subject 是访问系统的用户。SecurityManager 是安全管理器,负责用户的认证和授权,相当于 Shiro 的老大哥。 Realm 相当于数据源,用户的认证和授权都在 Realm 的方法中进行。

    2023年04月17日
    浏览(30)
  • 从0到1,手把手带你开发截图工具ScreenCap------003实现最小化程序到托盘运行

    为了方便截图干净,实现最小化程序到托盘运行,简洁,勿扰 实现最小化程序到托盘运行 实现托盘菜单功能 实现回显主窗体 实现托盘开始截屏 实现气泡信息提示 实现托盘程序提示 实现托盘退出程序 封装完好,可复用 注:博主所有资源永久免费,若有帮助,请点赞转发是

    2024年02月05日
    浏览(39)
  • 【Linux】手把手教你实现udp服务器

    网络套接字~ 文章目录 前言 一、udp服务器的实现 总结 上一篇文章中我们讲到了很多的网络名词以及相关知识,下面我们就直接进入udp服务器的实现。 一、udp服务器的实现 首先我们需要创建五个文件(文件名可以自己命名也可以和我一样),分别是makefile,udpclient.cc,udpclient.hpp

    2024年02月13日
    浏览(30)
  • 应用实践|基于Python手把手教你实现雪花算法

    📫 作者简介:「六月暴雪飞梨花」,专注于研究Java,就职于科技型公司后端工程师 🏆 近期荣誉:华为云云享专家、阿里云专家博主、 🔥 三连支持:欢迎 ❤️关注、👍点赞、👉收藏三连,支持一下博主~ 分布式策略ID的主要应用在互联网网站、搜索引擎、社交媒体、在线

    2024年02月21日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包