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

  • 记录–手把手教你,用electron实现截图软件已关闭评论
  • 138 次浏览
  • A+
所属分类:Web前端
摘要

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


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

记录--手把手教你,用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自己手写一个截图页,但是功能实在太多了,最后还是放弃了,如果大家有兴趣可以自己尝试,在这里我介绍两个不多的插件:

记录--手把手教你,用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实现截图软件