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

背景

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

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

  • 先是截图
    image.png
  • 截图后的图片展示
    image.png

工具

  • nodejs
  • pnpm
  • electron
  • vite
  • react

实现

原理逻辑

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

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

image.png

路由配置

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

image.png
先来安装一下:

git clone https://github.com/electron-vite/electron-vite-react
pnpm add react-router-dom
pnpm add antd
git clone https://github.com/electron-vite/electron-vite-react

pnpm add react-router-dom

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

image.png

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

image.png

image.png

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

效果图

动画.gif

总结Q&A

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

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

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

  • Q: 有源码吗?

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

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYFiiiJH' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片