【canvas】在Vue3+ts中实现 canva内的矩形拖动操作。

前言

canvas内的显示内容如何拖动?
这里提供一个 canvas内矩形移动的解决思路。
在这里插入图片描述

在这里插入图片描述

描述

如何选中canvas里的某部分矩形内容,然后进行拖动?
我的解决思路:

  1. **画布搭建。**用一个div将canvas元素包裹,设置宽高,div设置成相对定位(relative),canvas设置绝对定位(absolute)。
  2. 在往canvas内添加内容时,请保存添加内容的相关属性,长宽、位置、样式等,以此确定这部分内容的初始状态。例如:往canvas加一个矩形 ,就要先保存一下它的宽高和原点。
  3. 确定选中的内容。并与第一步保存的相关内容数据匹配。 由于canvas内添加的内容无法进行事件绑定,我们需要靠给canvas绑定点击事件,并根据点击位置确定哪部分内容被选中了。
  4. 生成可操作盒子。通过选中内容的数据,生成一个新的Dom元素盒子,并清除canvas内当前选中内容部分。给dom盒子绑定移动事件(mouse模拟拖动)。
  5. 拖动结束后,更新选中内容数据,在结束区域,canvas重新绘制

实现

1.画布搭建

   <div class="content" ref="canvasContent">
      <canvas id="canvas" ref="canvas" @click="canvasClickFn"></canvas>
    </div>
.content {
    position: relative;
    width: 800px;
    height: 600px;
  }
  #canvas {
    position: absolute;
    width: 800px;
    height: 600px;
    border: 1px solid #000;
    background-color: #fafafa;
  }

这一步要保证外层盒子和canvas大小一致。

2. 初始化canvas内容

在往canvas内添加内容时,请保存添加内容的相关属性,长宽、位置、样式等,以此确定这部分内容的初始状态。

  import { onMounted, reactive, ref, type Ref } from 'vue';
  interface DivStyle {
    boder?: string;
    backgroundColor?: string;
    width?: string;
    height?: string;
  }
  interface DiagramObj {
    id: string | number;
    path: Float32Array;
    origin: Array<number>;
    type: string;
    width?: number;
    height?: number;
    r?: number;
    style?: DivStyle;
  }
  let ctx: CanvasRenderingContext2D | null | undefined;
  const canvasContent: Ref<HTMLElement | null> = ref(null);
  const canvas: Ref<HTMLCanvasElement | null> = ref(null);
  const diagramObjArr: Array<DiagramObj> = reactive([]);
  const initCanvas = () => {
    if (canvas.value) {
      ctx = canvas.value?.getContext('2d');
      canvas.value.width = 800;
      canvas.value.height = 600;
    }
  };
  onMounted(() => {
    initCanvas();
    if (ctx) {
      let rect1 = new Float32Array([1, 1, 50, 1, 50, 30, 1, 30]);
      let rectObj = {
        id: 'rect1',
        path: rect1,
        origin: [1, 1],
        width: 50,
        height: 30,
        type: 'rect',
        style: {
          boder: '1px solid #000',
          backgroundColor: '#fff',
          width: '50px',
          height: '30px',
        },
        children: [],
      };
      drawRect(ctx, rect1);
      diagramObjArr.push(rectObj);
    }
  });
  //  绘制图形
  function drawRect(ctx: CanvasRenderingContext2D, array: Float32Array) {
    if (array.length % 2 !== 0) {
      console.error('drwaRect函数Float32Array参数长度需要偶数位');
      return;
    }
    ctx.beginPath();

    for (let i = 0; i < array.length; i += 2) {
      let x = array[i];
      let y = array[i + 1];
      if (i === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
    }
    ctx.closePath();
    ctx.stroke();
  }

rectObj是一个原点1,1;宽50,高30的盒子,然后 根据canvas路径api绘制图形。

3. 选中内容

绑定canvas点击事件,却定点击位置和点击位置下的内容。

  const canvasClickFn = (e: MouseEvent) => {
    let point = [e.offsetX, e.offsetY];
    let res = isGraphIstersection(point, diagramObjArr[0]);
    if (res && ctx) {
      console.log('在内部::', res.width);
      //  在图形正上方创建可操作图形
      createElementFn(canvasContent.value, res);
      //  清除该区域
      clearRect(ctx, [...res.origin, res.width, res.height]);
    }
  };
   function clearRect(ctx: CanvasRenderingContext2D, array: Array<number | undefined>) {
    const [x, y, width, height] = array as Array<number>;
    //  把1px 的边框算上
    ctx.clearRect(x - 1, y - 1, width + 2, height + 2);
  }
  // 圆点 和 多边形相交检测
  function isGraphIstersection(point: Array<number>, target: DiagramObj) {
    const { origin, width, height, r } = target;
    let apogee = [0, 0];
    //  求两矩形形中心点距离
    switch (target.type) {
      case 'rect':
        //  矩形  坐标轴法,不考虑矩形旋转
        if (!width || !height) return false;
        //  最远点
        apogee = [origin[0] + width, origin[1] + height];
        if (
          point[0] >= origin[0] &&
          point[0] <= apogee[0] &&
          point[1] >= origin[1] &&
          point[1] <= apogee[1]
        ) {
          return target;
        }
        return false;
      case 'circle':
        if (!r) return false;
        if (
          Math.pow(Math.abs(point[0] - origin[0]), 2) +
            Math.pow(Math.abs(point[1] - origin[0]), 2) <
          r * r
        ) {
          return target;
        }
        return false;
      case 'polygon':
        return false;
    }
  }

圆点 和 多边形相交检测 这个函数我只简单实现了矩形和圆形的检测(不考虑旋转)。如果想多检测其他的形状,需要自行实现。

4. 生成可操作盒子

根据选中的数据生成可操作盒子,盒子绑定事件,实现拖动
 function createElementFn(source: HTMLElement | null, obj: DiagramObj) {
    const { width, height, origin, style } = obj;
    if (!source || !width || !height) return;

    const div = document.createElement('div');
    div.setAttribute(
      'style',
      `
    position:absolute;
    top:${origin[1]}px;
    left:${origin[0]}px;
    width:${style?.width};
    height:${style?.height};
    border:${style?.boder};
    background-color:${style?.backgroundColor};
    box-shadow: 0px 0px 3px skyblue;
    `,
    );
    let divClickLeft = 0,
      divClickTop = 0; //  元素点击时本身偏移量
    let isStart = false;
    let finallyLeft = origin[0],
      finallyTop = origin[1]; // 最终偏移量
    div.onmousedown = (e: MouseEvent) => {
      divClickLeft = e.offsetX as number;
      divClickTop = e.offsetY as number;
      isStart = true;
    };
    div.onmousemove = (e: MouseEvent): void => {
      if (!isStart) return;
      const parentV = source.getBoundingClientRect();
      const [left, top] = [
        e.pageX - parentV.left - divClickLeft,
        e.pageY - parentV.top - divClickTop,
      ];
      if (
        left < 0 ||
        top < 0 ||
        left > parentV.width - (width as number) ||
        top > parentV.height - (height as number)
      )
        return;
      e.target.style.top = top + 'px';
      e.target.style.left = left + 'px';
      finallyLeft = left;
      finallyTop = top;
    };
    div.onmouseup = div.onmouseleave = (e: MouseEvent) => {
      if (!isStart) return;
      isStart = false;
      //  拖动好后在新区域重新绘画
      let newRectObj: DiagramObj = obj;
      let pw = finallyLeft + width;
      let ph = finallyTop + height;
      Object.assign(newRectObj, {
        path: new Float32Array([finallyLeft, finallyTop, pw, finallyTop, pw, ph, finallyLeft, ph]),
        origin: [finallyLeft, finallyTop],
      } as DiagramObj);
      if (ctx) {
        drawRect(ctx, newRectObj.path);
        let index = diagramObjArr.findIndex((item) => item.id === newRectObj.id);
        diagramObjArr.splice(index, 1, newRectObj);
        source.removeChild(div);
      }
    };

5.拖动完成后重新绘制图形

拖动完成后,在新的位置重新绘制图形。需要在生成盒子的鼠标抬起和鼠标移出实现。

div.onmouseup = div.onmouseleave = (e: MouseEvent) => {
      if (!isStart) return;
      isStart = false;
      //  拖动好后在新区域重新绘画
      let newRectObj: DiagramObj = obj;
      let pw = finallyLeft + width;
      let ph = finallyTop + height;
      Object.assign(newRectObj, {
        path: new Float32Array([finallyLeft, finallyTop, pw, finallyTop, pw, ph, finallyLeft, ph]),
        origin: [finallyLeft, finallyTop],
      } as DiagramObj);
      if (ctx) {
        drawRect(ctx, newRectObj.path);
        let index = diagramObjArr.findIndex((item) => item.id === newRectObj.id);
        diagramObjArr.splice(index, 1, newRectObj);
        source.removeChild(div);
      }
    };

效果

canvas移动


效果地址:

由于是模拟的拖动,不能拖动过快,下次想办法优化下,下次一定。

结语

结束了。 这个canvas拖动如果封装好的话,感觉是很有用的。

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

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

相关文章

净利暴跌9成,主力业务下滑,这家全球知名CIS供应商如何“翻身”?

消费电子寒冬对上游供应链的影响还在持续。 近日&#xff0c;全球知名的CMOS图像传感器&#xff08;CIS&#xff09;供应商格科微发布三季报显示&#xff0c;前三季度共实现营业收入32.45亿元&#xff0c;同比下降29.01%&#xff1b;实现净利润4972.57万元&#xff0c;同比下降…

国际阿里云:无法ping通ECS实例公网IP的排查方法!!!

无法ping通ECS实例的原因较多&#xff0c;您可以参考本文进行排查。 问题现象 本地客户端无法ping通目标ECS实例公网IP&#xff0c;例如&#xff1a; 本地客户端为Linux系统&#xff0c;ping目标ECS实例公网IP时无响应&#xff0c;如下所示&#xff1a; 本地客户端为Windo…

双网卡多网卡时win11如何设置网卡优先级

背景&#xff1a; 电脑需要同时使用多个网卡&#xff0c;一个用于被远程、另一个用于打开网页。 电脑打开网页时&#xff0c;走的是哪个网卡&#xff0c;是根据网卡优先级来的。 打开控制面板、网络和Internet、网络和共享中心 点击左侧 更改适配器设置。我这里可见两个网卡…

云栖大会-简单易用的智能云网络

云布道师 10 月 31 日&#xff0c;杭州云栖大会&#xff0c;在阿里云网络技术分论坛&#xff0c;阿里云网络产品线负责人祝顺民带来《Leadership&#xff1a;简单易用的智能云网络——阿里云网络持续演进之路》的主题演讲&#xff0c;全面阐释阿里云飞天洛神云网络&#xff08;…

NodeJs - 单线程模型和高并发处理原理

NodeJs - 单线程模型和高并发处理原理 前言一. NodeJs 线程模型1.1 NodeJs 模型分析1.2 NodeJs处理事件请求的流程1.3 NodeJs 和传统 Server 的对比 二. Cluster 模块利用多核CPU处理三. 总结 前言 我们都知道JavaScript是单线程的处理。但是我们在Node开发、Egg开发下&#x…

(动手学习深度学习)第7章 残差网络---ResNet

目录 ResNet总结 ResNet代码实现ResNet的梯度计算 ResNet 总结 残差块使得很深的网络更加容易训练 甚至可以训练一千层的网络 残差网络对随后的深层神经网络设计产生了深远影响&#xff0c;无论是卷积类网络还是全连接类网络。 ResNet代码实现 导入相关库 import torch fro…

【优选算法系列】【专题二滑动窗口】第二节.1004. 最大连续1的个数 III和1658. 将 x 减到 0 的最小操作数

文章目录 前言一、最大连续1的个数 III 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写二、将 x 减到 0 的最小操作数 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写总结 前言 一、最大连…

Centos配置邮件发送

在CentOS Linux上配置邮件发送 在这个指南中&#xff0c;我们将讨论如何配置CentOS Linux系统以通过外部邮件服务器发送电子邮件&#xff0c;使用自己的邮件账户进行发送。 第一步&#xff1a;开启SMTP授权码 首先&#xff0c;我们以QQ邮箱为例&#xff0c;需要开启SMTP授权…

state 和 props 有什么区别?

一、state 一个组件的显示形态可以由数据状态和外部参数所决定&#xff0c;而数据状态就是 state&#xff0c;一般在 constructor 中初始化 当需要修改里面的值的状态需要通过调用 setState 来改变&#xff0c;从而达到更新组件内部数据的作用&#xff0c;并且重新调用组件 r…

滚珠螺杆的精度和使用场景之间的关系?

滚珠螺杆的精度和使用场景之间有着密切的关系&#xff0c;不同精度的滚珠螺杆被应用于不同的机械设备和制造工艺中&#xff0c;以满足不同的精度要求和生产效率。 在机床加工行业中&#xff0c;高精度的滚珠螺杆被广泛应用于数控机床、加工中心和磨床等高精度加工设备中。这些设…

WebGL-Vue3-TS-Threejs:基础练习 / Javascript 3D library / demo

一、理解Three.js Three.js是一个用于WebGL渲染的JavaScript库。它提供了一组工具和类&#xff0c;用于创建和渲染3D图形和动画。简单理解&#xff08;并不十分准确&#xff09;&#xff0c;Three.js之于WebGL&#xff0c;好比&#xff0c;jQuery.js之于JavaScript。 OpenGL …

ROS Motion Planning运动规划库安装方法及进阶使用方法详细介绍

今天偶然发现了一个优质的运动规划库&#xff1a;ai-winter/ros_motion_planning&#xff0c;比较适合从事ROS移动机器人运动规划研究领域的小伙伴学习和使用&#xff0c;相比于莱斯大学Kavraki实验室提供的开源的著名运动规划库OMPL、或着我之前介绍过的zhm-real开源的zhm-rea…

2011年09月01日 Go生态洞察:Go语言词法扫描与App Engine演示

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

蓝桥杯每日一题2023.11.10

“蓝桥杯”练习系统 (lanqiao.cn) 题目描述 题目分析 对于此题&#xff1a;我们看到题目要求尽可能大&#xff0c;会联想到二分&#xff0c;注意切出的一定为正方形&#xff0c;其能切出的个数为(h[i] / x) * (w[i] / x)&#xff0c;将所有的个数与要求的个数进行对比&#x…

sql学习笔记(三)

目录 1.四舍五入 2.向上取整 3.向下取整 4.Hive 分区 5.case when条件语句 6.日期函数 7.字符串函数 8.窗口函数 1️⃣排序函数 1.四舍五入 round select round(3.14) —>3 2.向上取整 ceiling select ceiling(12.15) —>13 3.向下取整 floor select floor(12…

RFID携手制造业升级,为锂电池生产带来前所未有的可靠性

应用背景 随着科技的发展和全球化的推进&#xff0c;产品的生产过程越来越复杂&#xff0c;且对品质的要求也越来越高。在锂电池生产领域&#xff0c;由于其高能量密度、长寿命和环保特性&#xff0c;已被广泛应用于电动汽车、储能系统等领域。然而&#xff0c;锂电池的安全性和…

Android T窗口动画显示和退出流程(更新中)

序 如何创建一个窗口动画&#xff1f;我们通过先从APP创建一个窗口&#xff0c;以这个窗口的创建过程的窗口动画为例 这个demo就是点击BUTTON显示窗口&#xff0c;点击CLOSE WINDOW关闭窗口&#xff0c;下面简述关键代码 //定义WindowManager和LayoutParams private Window…

redis数据倾斜如何解决

Redis数据倾斜主要是由于数据访问热点导致的&#xff0c;通常在执行事务操作或范围查询时发生。这会导致大量数据集中在某个实例上&#xff0c;使得集群负载不均衡。以下是一些解决Redis数据倾斜的方法&#xff1a; 避免在同一个键值对上保存过多的数据。可以将大的键值对拆分…

Java 算法篇-深入理解递归(递归实现:青蛙爬楼梯)

&#x1f525;博客主页&#xff1a; 小扳_-CSDN博客 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 递归的说明 2.0 用递归来实现相关问题 2.1 递归 - 阶乘 2.2 递归 - 反向打印字符串 2.3 递归 - 二分查找 2.4 递归 - 冒泡排序 2.5 递归 - 冒泡排序2.0 2.6 递归 - 插…

启动Docker服务后显示Docker Engine stopped

1、重新启动Docker服务&#xff1a;打开Windows服务管理器&#xff08;可以在开始菜单中搜索&#xff09;&#xff0c;找到"Docker Desktop Service"或类似命名的服务&#xff0c;右键单击并选择"重启"。稍等片刻&#xff0c;看看是否重新启动成功 2、尝试…