React实现画布——可绘制矩形和箭头

news/2024/10/23 14:02:28

目录
  • 思路
  • 代码
  • 效果

本文将使用React、JSX、Rough.js实现一个简单的画布,可以绘制矩形和箭头。

思路

  • 每一个图形包括:绘制的类型、起点的x坐标、起点的y坐标、宽、高。调用rough的generator()函数传入图形信息进行绘制,其中对于箭头需要进一步处理:根据宽高确定终点,并且定义角度等生成箭头的另外两条短线。
  • 使用 React 的状态管理来跟踪当前拖动的元素和选中的元素类型。创建一个组件提供单选按钮供用户选择绘制的元素类型。添加画布元素:鼠标按下:创建新元素并开始拖动。鼠标抬起:结束拖动并保存元素状态。鼠标移动:更新当前元素的宽度和高度,实时反映在画布上。

代码

import React from "react";
import ReactDOM from "react-dom";
import rough from "roughjs/dist/rough.umd.js"; // 导入 Rough.js 库用于绘制粗糙图形import "./styles.css";var elements = []; // 定义一个数组用于存储绘制的元素// 创建一个新元素的函数
function newElement(type, x, y) {const element = { // 定义元素对象type: type, // 元素类型x: x, // 元素的 x 坐标y: y, // 元素的 y 坐标width: 0, // 元素的宽度height: 0 // 元素的高度};generateShape(element); // 生成元素的形状return element; // 返回元素对象
}// 旋转函数,围绕指定点旋转线段
function rotate(x1, y1, x2, y2, angle) {// 旋转公式:// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, // 计算新的 x 坐标(x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2  // 计算新的 y 坐标];
}var generator = rough.generator(); // 创建 Rough.js 生成器实例// 生成元素形状的函数
function generateShape(element) {if (element.type === "rectangle") { // 如果元素是矩形element.shapes = [ // 生成矩形形状并存储generator.rectangle(element.x, element.y, element.width, element.height)];}if (element.type === "arrow") { // 如果元素是箭头const x1 = element.x; // 起点 x 坐标const y1 = element.y; // 起点 y 坐标const x2 = element.x + element.width; // 终点 x 坐标const y2 = element.y + element.height; // 终点 y 坐标const size = 30; // 箭头大小const distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); // 计算起点与终点的距离const minSize = Math.min(size, distance / 2); // 确保箭头不会太小const xs = x2 - ((x2 - x1) / distance) * minSize; // 箭头基础线的 x 坐标const ys = y2 - ((y2 - y1) / distance) * minSize; // 箭头基础线的 y 坐标const angle = 20; // 箭头的角度const [x3, y3] = rotate(xs, ys, x2, y2, (-angle * Math.PI) / 180); // 计算箭头左侧的点const [x4, y4] = rotate(xs, ys, x2, y2, (angle * Math.PI) / 180); // 计算箭头右侧的点element.shapes = [ // 生成箭头的形状并存储generator.line(x1, y1, x2, y2), // 主线generator.line(x3, y3, x2, y2), // 左侧箭头线generator.line(x4, y4, x2, y2)  // 右侧箭头线];}
}// 主应用组件
function App() {var [draggingElement, setDraggingElement] = React.useState(null); // 用于跟踪拖动的元素var [elementType, setElementType] = React.useState("arrow"); // 当前选择的元素类型// 元素选项组件function ElementOption({ type, children }) {return (<label><inputtype="radio" // 单选按钮checked={elementType === type} // 选中状态onChange={() => setElementType(type)} // 更改元素类型/>{children} // 显示选项文本</label>);}return (<div><ElementOption type="rectangle">Rectangle</ElementOption> // 矩形选项<ElementOption type="arrow">Arrow</ElementOption> // 箭头选项<canvasid="canvas" // 画布 IDwidth={window.innerWidth} // 画布宽度height={window.innerHeight} // 画布高度onMouseDown={e => { // 鼠标按下事件const element = newElement( // 创建新元素elementType, // 使用当前选择的类型e.clientX - e.target.offsetLeft, // 计算 x 坐标e.clientY - e.target.offsetTop // 计算 y 坐标);elements.push(element); // 将元素添加到数组setDraggingElement(element); // 设置当前拖动的元素drawScene(); // 绘制场景}}onMouseUp={e => { // 鼠标抬起事件setDraggingElement(null); // 清空拖动的元素drawScene(); // 绘制场景}}onMouseMove={e => { // 鼠标移动事件if (!draggingElement) return; // 如果没有拖动的元素,退出draggingElement.width = // 更新元素宽度e.clientX - e.target.offsetLeft - draggingElement.x;draggingElement.height = // 更新元素高度e.clientY - e.target.offsetTop - draggingElement.y;generateShape(draggingElement); // 生成更新后的形状drawScene(); // 绘制场景}}/></div>);
}const rootElement = document.getElementById("root"); // 获取根元素// 绘制场景的函数
function drawScene() {ReactDOM.render(<App />, rootElement); // 渲染应用到根元素const canvas = document.getElementById("canvas"); // 获取画布const rc = rough.canvas(canvas); // 创建 Rough.js 画布实例canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height); // 清空画布elements.forEach(element => { // 遍历所有元素element.shapes.forEach(shape => rc.draw(shape)); // 绘制每个形状});
}drawScene(); // 初始绘制场景

效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/75031.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

使用URP后,unity内置渲染材质丢失解决

1.打开渲染管道转换器 2.选择built-in to URP,勾选下面所有选项,最后转换。

高途课堂视频课程资料下载工具,如何在电脑端下载高途和途途视频课程资料到本地?

一. 安装高途/途途课程下载器 1.获取学无止下载器 https://www.xuewuzhi.cn/gaotu_downloader 2.下载安装后,然后点击桌面快捷方式运行即可。 注意:杀毒软件可能会阻止外部exe文件运行,并将其当做成病毒,直接添加信任即可,本软件绝对没有木马病毒。 二. 使用说明 1.学无止…

在win10安装和使用wsl

WSL wsl(Windows Subsystem for Linux)是微软在Windows操作系统中引入的一个功能,它允许用户直接在Windows上运行Linux发行版的二进制可执行文件,而无需使用虚拟机或双启动系统。WSL提供了一个兼容层,使得Linux应用程序能够运行在Windows的内核上。wsl有以下特点:兼容性:…

Oracle认证证书的考试费用是多少

近期有学员咨询时问到:他大学学的是it和计算机方面的课程,在投简历时经常会看到Oracle认证优先,所以来问问Oracle证书的事情。 新接触数据库行业的毕业生或者转行的人可能不清楚Oracle认证的含金量,Oracle是非常有名的数据库产品,在db-ranking统计中,Oracle数据库一直霸占…

Nuxt.js 应用中的 builder:generateApp 事件钩子详解

title: Nuxt.js 应用中的 builder:generateApp 事件钩子详解 date: 2024/10/23 updated: 2024/10/23 author: cmdragon excerpt: builder:generateApp 是 Nuxt.js 的一个生命周期钩子,它在生成应用程序之前被调用。这个钩子为开发者提供了一个机会,可以在生成过程开始之前修…

回溯法求解简单组合优化问题

此为课题组所指导本科生和低年级硕士生学习组合优化问题汇报 所用教材:北京大学屈婉玲教授《算法设计与分析》 课程资料:https://www.icourse163.org/course/PKU-1002525003 承诺不用于任何商业用途,仅用于学术交流和分享更多内容请关注许志伟课题组官方中文主页:https://J…

【VMware VCF】使用 PowerShell 脚本管理 SDDC Manager 中的软件包。

SDDC Manager 中有两种类型的软件包,分别是“升级/修补包(PATCH)”和“安装包(INSTALL)”。“升级/修补包”用于执行 VCF 环境中组件的升级/修补,这个已经在前面的文章中使用过了;而另外一种“安装包”,这种包用于在 VCF 环境中部署其他集成解决方案,比如 VMware Aria…

AOT漫谈专题(第六篇): C# AOT 的泛型,序列化,反射问题

一:背景 1. 讲故事 在 .NET AOT 编程中,难免会在 泛型,序列化,以及反射的问题上纠结和反复纠错尝试,这篇我们就来好好聊一聊相关的处理方案。 二:常见问题解决 1. 泛型问题 研究过泛型的朋友应该都知道,从开放类型上产下来的封闭类型往往会有单独的 MethodTable,并共用…