背景
因为我们每天开发项目的时候需要和同事连接API、文档和UI图,所以有时候我们需要同时打开多个窗口,并在多个窗口之间切换来选择我们想要的信息。 更多的内容是可以的,但有时只是很多页。 为了提高效率,我决定自己制作一个截图工具,将我想要的信息截图固定在窗口上。
做之前我们先看一下最终的显示效果:
工具实现原理逻辑
其实也不难理解。 首先主窗体发起截图请求,然后打开另一个负责透明全屏截图的窗体。 被唤起后,透明窗体会让Electron捕获整个屏幕并发送给逻辑页面,页面将图片发送全屏绘制ip形象,达到定格效果,然后使用canvas生成绘图区域,根据生成的区域剪切导出刚才的全屏图片,最后传给主窗体显示并保存到剪贴板。
具体api可以查看官方文档:…
路由配置
这个开发使用了 electro-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来帮助我们实现截图功能,代码如下:
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之间进行通信来获取屏幕的图片。 详细内容请参考文档:…,输入代码如下:
// 截图
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%透明的玻璃漂浮在我们的电脑屏幕上。 这时候我们需要设置它的宽度:100%,高度: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 Forge,但最终放弃了。 原因有两个: 1、编译速度太慢。 不知道是不是webpack的原因,相比vite确实太慢了! ! ! 2.Electron Forge使用Electron Package来封装,定制性不太好,所以最终放弃了。 。
当然地址如下:/027xiguapi/…,有兴趣的话可以一起讨论,欢迎大家fork和star
参考
- 本文固定链接: https://wen.nuanque.com/biaoqingbao/6159.html
- 转载请注明: nuanquewen 于 吉祥物设计/卡通ip设计/卡通人物设计/卡通形象设计/表情包设计 发表
- 文章或作品为作者独立观点不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。本文之内容为用户主动投稿和用户分享产生,如发现内容涉嫌抄袭侵权,请联系在线客服举报,一经查实,本站将立刻删除。本站转载之内容为资源共享、学习交流之目的,请勿使用于商业用途。