基于electron、vite、viewerjs 和 react,我做了一个预览图片桌面软件

背景

最近正在做一个开源的项目–pear-rec,pear-rec 是一个跨平台的截图、录屏、录音、录像软件。截图功能上篇文章已经讲过了,如果没有看过的可以去看看这篇文章————手把手教你,用electron实现截图软件,开发到现在我想要一个图片预览功能,说干就干,下面我介绍一下我的开发过程。

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

动画5.gif

工具

  • nodejs
  • pnpm
  • electron
  • vite
  • react
  • antd
  • viewerjs

实现

原理逻辑

在上一篇文章我们已经搭建了一个vite + electron 的项目了,所以在这里我就不再讲如何搭建项目了(不会的同学可以看《手把手教你,用electron实现截图软件》),其实实现图片预览也并不难理解,首先是在主窗体选择要预览的图片,然后会打开另一个负责预览图片的窗口,然后通过web页面操作图片并实现放大、缩小等功能。

主窗体

我们先准备好主页面Home,里面很简单,就是放入一个按钮然后点击按钮开开弹框选择图片。

如图:

image.png

因为我们是用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;
	});

这样我们就获取到了一个图片的地址。

预览图片窗体

image.png

    1. 路由

@/router.tsx

const ViewImage = lazy(() => import("@/pages/viewImage"));

<Route path="/viewImage" element={<ViewImage />}></Route>
  • 2.安装插件
pnpm i viewerjs -S 
pnpm i antd -S 
    1. 页面

@/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;

    1. 样式
.viewImgs {
	width: 100vw;
	height: 100vh;
	overflow: hidden;
	:global {
		.viewImg {
			display: none;
		}
	}
}
    1. 逻辑说明
      本页面的图片预览功能就是通过viewerjs这个插件来实现的。这个插件有丰富的图片预览功能,不需要我们来手动造轮子了。具体的功能和api大家可以去它的网站查看。网站:fengyuanchen.github.io/viewerjs/

总结

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

  • Q: 有源码吗?

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

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

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

昵称

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