這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
背景
因為我們日常開發專案的時候,需要和同事對接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="https://www.cnblogs.com/smileZAZ/archive/2023/07/07/#">更多</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, "[email protected]"),
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
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/556806.html
標籤:其他
上一篇:資料型別
下一篇:返回列表

