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

news2024/11/23 9:18:37

前言

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拖动如果封装好的话,感觉是很有用的。

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

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

相关文章

漏洞扫描-nuclei-poc编写

0x00 nuclei Nuclei是一款基于YAML语法模板的开发的定制化快速漏洞扫描器。它使用Go语言开发&#xff0c;具有很强的可配置性、可扩展性和易用性。 提供TCP、DNS、HTTP、FILE 等各类协议的扫描&#xff0c;通过强大且灵活的模板&#xff0c;可以使用Nuclei模拟各种安全检查。 …

vue+iView实现下载zip文件导出多个excel表格

1&#xff0c;需求&#xff1a;在vue项目中&#xff0c;实现分月份导出多个Excel表格。 点击导出&#xff0c;下载zip文件&#xff0c;解压出多张表数据。 2&#xff0c;关键代码&#xff1a; <Button class"export button-style button-space" click"ex…

MPC-模型预测控制笔记

线性mpc 凸优化 二次优化问题 1&#xff1a;建立预测模型 2&#xff1a;问题模型 3&#xff1a;求解优化问题 4&#xff1a;得到的优化控制驱动系统 上述方法与qp解一样 硬约束 硬约束 四组约束条件 二次规划求解 matlab代码&#xff1a; 软约束 可以用指数函数 加入…

Python爬虫抓取微博数据及热度预测

首先我们需要安装 requests 和 BeautifulSoup 库&#xff0c;可以使用以下命令进行安装&#xff1a; pip install requests pip install beautifulsoup4然后&#xff0c;我们需要导入 requests 和 BeautifulSoup 库&#xff1a; import requests from bs4 import BeautifulSou…

csv文件导入mysql指定表中

csv文件导入mysql指定表中 mysql数据库准备指定表 准备导入的csv数据如下&#xff1a; sepaLengthsepalWidthpetalLengthpetalWidthlabel5.13.51.40.204.931.40.204.73.21.30.20…………… 准备导入的数据为151行5列的数据&#xff0c;其中第一行为标题行。 因此&#xff0…

什么是Node.js的调试器(debugger)工具?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

演示文稿制作软件 Deckset mac中文版介绍

Deckset mac是一款Mac上的演示文稿制作软件&#xff0c;它可以让你使用Markdown语言快速地创建演示文稿。与传统的演示文稿制作软件相比&#xff0c;Deckset采用了全新的设计理念&#xff0c;旨在让用户更加专注于内容的创作&#xff0c;而不是花费过多的时间在排版和设计上。 …

[100天算法】-颜色分类(day 69)

题目描述 给定一个包含红色、白色和蓝色&#xff0c;一共 n 个元素的数组&#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。此题中&#xff0c;我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。注意: 不能使…

LeetCode(4)删除有序数组中的重复项 II【数组/字符串】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 80. 删除有序数组中的重复项 II 1.题目 给你一个有序数组 nums &#xff0c;请你** 原地** 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数…

基恩士软件的基本操作(一)

今天就来学习基恩士软件的基础操作&#xff0c;欢迎大家的指正&#xff01;&#xff01;&#xff01; 基本操作 KV STUDIO 基恩士编程软件的名称就KV STUDIO。安装软件地址KV STUDIO的安装与实践 项目的创建 1&#xff0c;双击KV STUDIO. 2&#xff0c;新建项目 单元编辑器…

LeetCode(3)删除有序数组中的重复项【数组/字符串】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 26. 删除有序数组中的重复项 1.题目 给你一个 非严格递增排列 的数组 nums &#xff0c;请你** 原地** 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保…

【uniapp】仿微信通讯录列表实现

效果图 代码实现 <view class"main-container"><!-- 成员列表 --><scroll-viewclass"member-list":style"computedHeight":scroll-y"true":enable-back-to-top"true":scroll-with-animation"true"…

仙侠类型游戏开发2D3D仙侠古风游戏

仙侠类游戏是一种以仙侠文化为背景的角色扮演游戏&#xff0c;玩家在游戏中扮演修仙者或武侠&#xff0c;通过修炼技能、完成任务和与其他玩家互动&#xff0c;逐步提升角色的实力和境界。这类游戏通常融合了仙侠小说中的幻想元素、武侠的武技和修仙的奇遇&#xff0c;创造了一…

如何设计一个网盘系统的架构

1. 概述 现代生活中已经离不开网盘&#xff0c;比如百度网盘。在使用网盘的过程中&#xff0c;有没有想过它是如何工作的&#xff1f;在本文中&#xff0c;我们将讨论如何设计像百度网盘这样的系统的基础架构。 2. 系统需求 2.1. 功能性需求 用户能够上传照片/文件。用户能…

如何使用CORS和CSP保护前端应用程序安全

前端应用在提供无缝用户体验方面起着核心作用。在当今互联网的环境中&#xff0c;第三方集成和API的普及使得确保强大的安全性至关重要。安全漏洞可能导致数据盗窃、未经授权访问以及品牌声誉受损。本文将向您展示如何使用CORS和CSP为您的网页增加安全性。 嗨&#xff0c;大家好…

为什么审计平台不适合进行数据库变更管理?

关于视源电子 广州视源电子科技股份有限公司 (CVTE) 成立于 2005 年 12 月&#xff0c;旗下拥有多家业务子公司。 截至 2022 年底&#xff0c;公司总人数超 6000 人&#xff0c;约 60% 为技术人员。公司的主营业务为液晶显示主控板卡和交互智能平板等显控产品的设计、研发与销…

CSS3 分页、框大小、弹性盒子

一、CSS3分页&#xff1a; 网站有很多个页面&#xff0c;需要使用分页来为每个页面做导航。示例&#xff1a; <style> ul.pagination { display: inline-block; padding: 0; margin: 0; } ul.pagination li {display: inline;} ul.pagination li a { color: black; f…

网络安全-黑客技术-小白学习

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

虚幻5 删除C盘缓存及修改缓存路径

一.修改C盘缓存 C盘缓存路径为&#xff1a; C:\Users\xx(这里是你的用户名)\AppData\Local\UnrealEngine\Common\DerivedDataCache 注意&#xff0c;如果没有AppData文件夹&#xff0c;请依次点击查看-勾选显示隐藏的项目&#xff0c;即可 可删除里面的所有文件即可 二.修改…

华为eNSP实验-QinQ基本实验

1.拓扑图如下 PC1的设置如下&#xff1a; 在未配置VLAN之前&#xff0c;PC1可以ping通PC3&#xff0c;PC2可以ping通PC4&#xff08;因为同一网段&#xff09; 2.SW1和SW4配置VLAN <Huawei>system-view [Huawei]undo info-center enable //关闭提示信息 [Huawei]sysn…