更新了,图片美化工具迎来两大功能

news/2024/9/23 21:08:28/

最近给图片套壳美化工具更新了两个重要的功能,简而言之就是:

  1. 直接操作图片的放大缩小

  2. 快速截图后上传图片(通过浏览器的 getDisplayMedia 接口)

第一个功能是为了方便操作,之前是在左侧通过滑动区块来调节图片区域的显示大小,不够直观或不方便实现更精细化的调节。

于是采用更常规的操作方式,在图片区域的四个角上新增操作手柄,当鼠标放在操作手柄上时,显示可拖拽的弧线。

下图是对应的四个角的手柄位置和样式,和拖拽放大缩小的演示过程:

关键代码

export default function Preview({ transform, showBtn }) {const [handSide, setHandSide] = useState('left');const [clientX, setClientX] = useState(0)// 是否在拖动const [isResizing, setIsResizing] = useState(false)const handleStartResize = useCallback((e, side) => {setHandSide(side)setIsResizing(true)setClientX(e.clientX)}, [])const handleStopResize = useCallback(() => {setIsResizing(false);}, [])const didHandleResize = debounce(e => {if (!isResizing) return;const offset = e.clientX - clientX;let scale = 0;const pixelToScale = 0.01;if (handSide === 'left') {scale = config.scale - offset * pixelToScale;}if (handSide === 'right') {scale = config.scale + offset * pixelToScale;}setClientX(e.clientX);handleConfigChange(parseFloat(scale.toFixed(2)), 'scale');}, 0)const handleResize = useCallback(didHandleResize, [isResizing, clientX, didHandleResize])useEffect(() => {document.addEventListener('mouseup', handleStopResize)document.addEventListener('mousemove', handleResize)return () => {document.removeEventListener('mouseup', handleStopResize)document.removeEventListener('mousemove', handleResize)}}, [handleStopResize, handleResize])return (<div className={styles.frameContent}><div className={styles.displayContainer}><div className={clsx(styles.mockupModule, styles[config.mockup.theme])} style={{ transform: transform, display: config.hideMockup ? 'none' : 'block' }}><div className={styles.devices}><div className={styles.item}><div className={clsx(styles.itemContainer, 'item-container')}>{ /*省略*/ }</div></div><div className={styles.resizeHandle}><div className={clsx(styles.handleArea, styles.leftTop)} onMouseDown={e => handleStartResize(e, 'left')}><div className={styles.handle} /></div><div className={clsx(styles.handleArea, styles.rightTop)} onMouseDown={e => handleStartResize(e, 'right')}><div className={styles.handle} /></div><div className={clsx(styles.handleArea, styles.rightBottom)} onMouseDown={e => handleStartResize(e, 'right')}><div className={styles.handle} /></div><div className={clsx(styles.handleArea, styles.leftBottom)} onMouseDown={e => handleStartResize(e, 'left')}><div className={styles.handle} /></div></div></div></div></div></div>)
}

第二个功能是我从另一个工具上参考来的,因为我根本不知道浏览器上还能这么操作。

当调起 navigator.getDisplayMedia[1] 这个接口的时候, 会弹起浏览器的系统弹窗:有三个可选项,分别是:Chrome 标签页,窗口,整个屏幕,选中某个界面进行分享之后,我们就能获取到对应的视频流。

弹窗效果如下图所示:

cb121a12a4243a5f6097371a9d7bb97f.png
截图分享

在 Web 中我们就可以通过创建 Video 标签将视频流渲染出来。最后怎么将视频转化为图片呢?熟悉 canvas 的不难,它有一个 drawImage 方法,可以将视频绘制在画布中。

最后我们将 canvas 对象通过 toDataURL() 方法转化为图片数据编码显示在 img 标签中。

function getDisplayMedia(options) {if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {return navigator.mediaDevices.getDisplayMedia(options);}if (navigator.getDisplayMedia) {return navigator.getDisplayMedia(options);}if (navigator.webkitGetDisplayMedia) {return navigator.webkitGetDisplayMedia(options);}if (navigator.mozGetDisplayMedia) {return navigator.mozGetDisplayMedia(options);}throw new Error('getDisplayMedia is not defined');
}async function takeScreenshotStream() {const width = screen.width * (window.devicePixelRatio || 1);const height = screen.height * (window.devicePixelRatio || 1);const errors = [];let stream;const mediaStreamConstraints = {audio: false,video: { width, height, frameRate: 1 }};try {stream = await getDisplayMedia(mediaStreamConstraints);} catch (ex) {errors.push(ex);}if (errors.length) {console.debug(...errors);if (!stream) {throw errors[errors.length - 1];}}return stream;
}async function takeScreenshotCanvas() {const stream = await takeScreenshotStream();const video = document.createElement('video');const result = await new Promise((resolve, reject) => {video.onloadedmetadata = () => {video.play();video.pause();const canvas = document.createElement('canvas');canvas.width = video.videoWidth;canvas.height = video.videoHeight;const context = canvas.getContext('2d');context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);resolve(canvas);}video.srcObject = stream;})stream.getTracks().forEach(function (track) {track.stop();})if (result == null) {throw new Error('Cannot take canvas screenshot');}return result;
}

不过它有个小小的缺点,就是点击分享后会离开当前页面,导致整个操作不连贯。在用户不知道该特性的情况下使用的时候会有点迷惑。

不过这个功能还是大大简化了当你需要在其它页面或者窗口中截图来美化时,需要先隐藏当前窗口,然后手动调起截屏,选取截屏区域,保存截图后额外再点击上传图片。有了该功能,瞬间流程简化了不少。

欢迎大家将使用过程中的问题反馈出来,助力我将这个工具做的越来越好用。为有图片美化和设备套壳需求的人带来便利。


参考资料

[1]

navigator.getDisplayMedia: https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getDisplayMedia


http://www.ppmy.cn/news/1529495.html

相关文章

【LLM论文日更】| 俄罗斯套娃嵌入模型

论文&#xff1a;https://proceedings.neurips.cc/paper_files/paper/2022/file/c32319f4868da7613d78af9993100e42-Paper-Conference.pdf代码&#xff1a;GitHub - RAIVNLab/MRL: Code repository for the paper - "Matryoshka Representation Learning"机构&#x…

MATLAB数据文件读写:1.格式化读写文件

格式化读写文件 matlab提供了对数据文件建立、打开、读取、写入、关闭等操作的函数。 数据文件可以分为两类&#xff1a; 文本文件&#xff1a;以ASCII码形式存储的文本文件&#xff1b;编码基于字符定长&#xff0c;译码相对容易二进制文件&#xff1a;以二进制形式存储的文…

Allow anonymous access to my Azure OpenAI chat bot

题意&#xff1a;允许匿名访问我的 Azure OpenAI 聊天机器人 问题背景&#xff1a; I have an Azure OpenAI chat bot using my own data (I configured an OpenAI resource and chose Deploy as Web App) . Members of my domain can access it by logging in. Now I want it…

C++学习笔记(32)

三十四、双链表 示例&#xff1a; #include <iostream> using namespace std; typedef int ElemType; // 自定义链表的数据元素为整数。 struct LNode // 双链表的结点。 { ElemType data; // 存放结点的数据元素。 struct LNode* prior,*next; // 前驱和后继结点的指针。…

Nginx实用篇:实现负载均衡、限流与动静分离

Nginx实用篇&#xff1a;实现负载均衡、限流与动静分离 | 原创作者/编辑&#xff1a;凯哥Java | 分类&#xff1a;Nginx学习系列教程 Nginx 作为一款高性能的 HTTP 服务器及反向代理解决方案&#xff0c;在互联网架构中扮演着至关重要的角色。它…

计算机网络33——文件系统

1、chmod 2、chown 需要有root权限 3、link 链接 4、unlink 创建临时文件&#xff0c;用于非正常退出 5、vi vi可以打开文件夹 ../是向外一个文件夹 6、ls ls 可以加很多路径&#xff0c;路径可以是文件夹&#xff0c;也可以是文件 ---------------------------------…

vue选项式写法项目案例(购物车)

一、初始化项目结构 1.初始化vite项目 npm create vite cd vite-project npm install 2.清理项目结构 清空App.vue 删除components目录下的HelloWorld.vue组件 3.为组件的样式启用sacc或less组件 npm i sass4.初始化index.css全局样式 :root{font-size:12px } 二、封装…

2024自学手册——网络安全(黑客技术)

前言 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 如何成为一名黑客 很多朋友在学习安全方面都会半路转行&#xff0c…