背景
最近正在做一个开源的项目–pear-rec,pear-rec 是一个跨平台的截图、录屏、录音、录像软件。截图功能上篇文章已经讲过了,如果没有看过的可以去看看这篇文章————手把手教你,用electron实现截图软件,开发到现在我想要一个图片预览功能,说干就干,下面我介绍一下我的开发过程。
在做之前先看看最终展示效果吧:
工具
- nodejs
- pnpm
- electron
- vite
- react
- antd
- viewerjs
实现
原理逻辑
在上一篇文章我们已经搭建了一个vite + electron 的项目了,所以在这里我就不再讲如何搭建项目了(不会的同学可以看《手把手教你,用electron实现截图软件》),其实实现图片预览也并不难理解,首先是在主窗体选择要预览的图片,然后会打开另一个负责预览图片的窗口,然后通过web页面操作图片并实现放大、缩小等功能。
主窗体
我们先准备好主页面Home,里面很简单,就是放入一个按钮然后点击按钮开开弹框选择图片。
如图:
因为我们是用electron,所以在选择图片的组件上我选用了electron 的 dialog方法来实现,当然了,你也可以用input 标签 + change 事件来实现。
// ipcRenderer
invokeViGetImgs: () => ipcRenderer.invoke("vi:get-imgs", "选择图片"),
// ipcMain
ipcMain.handle("vi:get-img", async (event, title) => {
let res = await dialog.showOpenDialog({
title: title,
buttonLabel: "按此打开文件",
properties: ["openFile"],
filters: [
{ name: "图片", extensions: ["jpg", "jpeg", "png", "webp", "svg"] },
],
});
res.filePaths.map((filePath, index) => {
setHistoryImg(filePath);
});
const img = getHistoryImg();
return img;
});
这样我们就获取到了一个图片的地址。
预览图片窗体
-
- 路由
@/router.tsx
const ViewImage = lazy(() => import("@/pages/viewImage"));
<Route path="/viewImage" element={<ViewImage />}></Route>
- 2.安装插件
pnpm i viewerjs -S
pnpm i antd -S
-
- 页面
@/pages/viewImage
import { useEffect, useState, useRef } from "react";
import { useSearchParams } from "react-router-dom";
import { Button, Upload } from "antd";
import Viewer from "viewerjs";
import {
FileImageOutlined,
ZoomInOutlined,
ZoomOutOutlined,
RotateLeftOutlined,
RotateRightOutlined,
SyncOutlined,
DownloadOutlined,
PrinterOutlined,
LeftOutlined,
RightOutlined,
SwapOutlined,
ExpandOutlined,
CompressOutlined,
InboxOutlined,
PushpinOutlined,
EditOutlined,
CloseOutlined,
} from "@ant-design/icons";
import type { UploadProps } from "antd/es/upload/interface";
import defaultImg from "/imgs/th.webp";
import "viewerjs/dist/viewer.css";
import styles from "./index.module.scss";
const { Dragger } = Upload;
const ViewImage = () => {
const [search, setSearch] = useSearchParams();
const [imgs, setImgs] = useState([]);
const [isFull, setIsFull] = useState(false);
useEffect(() => {
initImgs();
}, []);
useEffect(() => {
imgs.length && initViewer();
}, [imgs]);
function initViewer() {
const imgList = document.getElementById("viewImgs") as any;
const viewer = new Viewer(imgList, {
// 0: 不显示
// 1:显示
// 2:width>768px
// 3: width>992px
// 4: width>1200px
inline: true,
className: "viewImgs",
toolbar: {
alwaysOnTopWin: handleToggleAlwaysOnTopWin,
zoomIn: 1,
zoomOut: 1,
oneToOne: 1,
reset: 1,
prev: 1,
// play: {
// show: 4,
// size: "large",
// },
next: 1,
rotateLeft: 1,
rotateRight: 1,
flipHorizontal: 1,
flipVertical: 1,
download: () => {
handleDownload(viewer);
},
print: () => {
window.print();
},
},
}) as any;
}
const props: UploadProps = {
accept: "image/png,image/jpeg,.webp",
name: "file",
multiple: false,
showUploadList: false,
beforeUpload: (file) => {
const imgUrl = window.URL.createObjectURL(file);
setSearch({ url: imgUrl });
setImgs([imgUrl]);
return false;
},
};
function handleDownload(viewer) {
if (window.electronAPI) {
window.electronAPI.sendViDownloadImg({
url: viewer.image.src,
name: viewer.image.alt,
});
} else {
const a = document.createElement("a");
a.href = viewer.image.src;
a.download = viewer.image.alt;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}
function handleFullScreen() {
const element = document.querySelector("#root");
if (element.requestFullscreen) {
element.requestFullscreen();
setIsFull(true);
}
}
function handleExitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
setIsFull(false);
}
}
async function handleOpenImage() {
const imgs = await window.electronAPI?.invokeViGetImgs();
// setImgs(imgs);
}
async function handleToggleAlwaysOnTopWin() {
const isAlwaysOnTop = await window.electronAPI?.invokeViSetIsAlwaysOnTop();
const icon = document.querySelector(".viewer-always-on-top-win");
isAlwaysOnTop
? icon.classList.add("current")
: icon.classList.remove("current");
}
async function initImgs() {
const imgUrl = search.get("url");
if (imgUrl) {
setImgs([imgUrl]);
} else {
const img = (await window.electronAPI?.invokeViSetImg()) || defaultImg;
setImgs([img]);
}
}
return (
<div className={styles.viewImgs} id="viewImgs">
{imgs.length ? (
imgs.map((img, key) => {
return <img className="viewImg" src={img} key={key} />;
})
) : (
<Dragger {...props} className="viewImageUpload">
<p className="ant-upload-drag-icon">
<InboxOutlined rev={undefined} />
</p>
<p className="ant-upload-text">点击或拖着图片</p>
<p className="ant-upload-hint">
支持.jpg、.jpeg、.jfif、.pjpeg、.pjp、.png、.apng、.webp、.avif、.bmp、.gif、.webp
</p>
</Dragger>
)}
</div>
);
};
export default ViewImage;
-
- 样式
.viewImgs {
width: 100vw;
height: 100vh;
overflow: hidden;
:global {
.viewImg {
display: none;
}
}
}
-
- 逻辑说明
本页面的图片预览功能就是通过viewerjs
这个插件来实现的。这个插件有丰富的图片预览功能,不需要我们来手动造轮子了。具体的功能和api
大家可以去它的网站查看。网站:fengyuanchen.github.io/viewerjs/
- 逻辑说明
总结
文章写到这里基本结束了,简单回顾下文章的内容。
- Q: 有源码吗?
当然有,地址如下:github.com/027xiguapi/…,有兴趣的话可以大家一起探讨,同时也欢迎大家fork
和star
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END