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

本文将使用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>
        <input
          type="radio" // 单选按钮
          checked={elementType === type} // 选中状态
          onChange={() => setElementType(type)} // 更改元素类型
        />
        {children} // 显示选项文本
      </label>
    );
  }

  return (
    <div>
      <ElementOption type="rectangle">Rectangle</ElementOption> // 矩形选项
      <ElementOption type="arrow">Arrow</ElementOption> // 箭头选项

      <canvas
        id="canvas" // 画布 ID
        width={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(); // 初始绘制场景

效果

posted @ 2024-10-23 13:59  Frommoon  阅读(51)  评论(0编辑  收藏  举报