使用canvas实现图纸标记及回显

news2024/9/24 15:28:25

图纸
在这里插入图片描述
图纸标记后的效果图
在这里插入图片描述
最近做的一个qms项目里面,需要前端图纸上实现标记标记后的内容还要能够回显,然后后端通过标记的点,去读取标记图纸的内容,如一些公式数据之类的,目前实现的功能有 在图纸上面进行矩形标记已保存的标记回显单个标记点清除一键清除所有标记保存标记图片
一开始听领导说,小段啊,咱们做的这个项目模块,有个功能图纸标记的功能,需要前端做下,大概就是在图纸上面框住或者圈住一部分,然后传给后端,后端根据你传过去的数据,去读取标记的内容,实现一些其他的业务逻辑。。我一开始是懵逼的,这是啥呀,又搞骚操作??唉,打工人一切向钱看齐,想办法吧,正好前端同时之前做过类似的demo,可以借鉴,同时加上自己的一些琢磨,功能是基本实现,目前还没和后端对接。
说一下思路:我研究了下,目前在图片上面做一些操作的,大多是通过canvas实现的,canvas的默认背景是黑色的,然后下载后的图片背景就是乌黑黑的一片加一些自己画的线,然后就想着把图纸作为canvas的背景图片,这样下载后的背景图片就是图纸,挺好的,哈哈,剩下的具体代码实现的逻辑,都在贴在下面的代码示例里面了!!

以下是操作视频

图纸标记组件视频

代码如下

<template>
  <div>
    <div>
      <a-button @click="clearBtn" style="margin-right:15px;">清除当前标记</a-button>
      <a-button @click="clearAllBtn" style="margin-right:15px;">清除所有标记</a-button>
      <a-button @click="saveBtn" type="primary" style="margin-right:15px;">保存标记</a-button>
      <a-button @click="saveImage" type="primary">下载标记图片</a-button>
    </div>
    <div style="margin:10px 0">
      <a-form :model="formInfo" ref="formRef" name="basic" :label-col="{ span: 6 }"
        :wrapper-col="{ span: 18 }">
        <a-row :gutter="20">
          <a-col :span="8">
            <a-form-item label="标记名称" name="rectName">
              <a-input v-model:value="rectName" placeholder="请输入标记名称" allowClear></a-input>
            </a-form-item>
          </a-col>
        </a-row>
      </a-form>
    </div>
    <div id="canvasDiv">
      <canvas ref="canvas"  @contextmenu.prevent="canvasRight" @mousedown="startMark" @click="canvasClick" @mousemove="draw" @mouseup="endMark"></canvas>
    </div>
  </div>
</template>
 
<script>
import cardPng from '/@/assets/images/card.png';

export default {
  data() {
    return {
      isMarking: false,//是否开始在标记的标识
      startX:0,//点击时初始位置x轴
      startY:0,//点击时初始位置y轴
      markedRectangles: [
        {x:162, y:253, width:46, height:76,name:'过渡板'},
        {x:70, y:253, width:47, height:70,name:'电磁阀'},
        {x:447, y:928, width:114, height:39,name:'软管'},
      ], // 存储已标记矩形的数组
      rectObj:'',//记录最后一次的矩形
      rectName:'',//标记名称
    };
  },
  mounted() {
    //设置canvas背景
    this.loadCanvas();
    window.addEventListener('resize', this.loadCanvas);
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.loadCanvas);
  },
  methods: {
    canvasRight(e){
      e.preventDefault();// 阻止默认的右键菜单
    },
    //下载标记并且保存的canvas图片
    saveImage(){
      this.drawBackground();//重绘canvas及回显保存的标记,若是标记未保存,则不会在下载的图片里面显示
      const img = this.canvas.toDataURL('image/png');
      const link = document.createElement('a');
      link.href = img;
      link.download = 'canvas.png';
      console.log(img,'img')
      console.log(link,'link')
      link.click();
    },
    canvasClick(event){
      // cavans点击事件监听
      console.log(event,'event')
      const canvas = this.$refs.canvas;
      const ctx = canvas.getContext('2d');
      const rect = canvas.getBoundingClientRect();
      const x = event.clientX - rect.left;
      const y = event.clientY - rect.top;
      // 遍历已标记的矩形,检查点击位置,若是在标记数组的矩形范围内,则从数组中进行比对,进行标记名称回显
      for (let i = this.markedRectangles.length - 1; i >= 0; i--) {
        const markedRect = this.markedRectangles[i];
        if (x >= markedRect.x &&x <= markedRect.x + markedRect.width && y >= markedRect.y && y <= markedRect.y + markedRect.height){
          this.rectObj=markedRect;
          this.rectName=markedRect.name;
        }
      }
    },
    
    //绘制canvas、添加背景图片、把标记数组里面的点回显
    drawBackground(){
      this.canvas.width = this.image.width;
      this.canvas.height = this.image.height;
      this.ctx.drawImage(this.image, 0, 0);
      this.ctx.strokeStyle = '#99eb1e';//改变画线的颜色和宽度
      this.ctx.lineWidth = 2;
      this.markedRectangles.forEach((item,index)=>{
        item.resetid=new Date().getTime()+index;
      })
      console.log(this.markedRectangles,'markedRectangles')
      for(let i=0;i<this.markedRectangles.length;i++){
        this.ctx.strokeRect(this.markedRectangles[i].x, this.markedRectangles[i].y, this.markedRectangles[i].width, this.markedRectangles[i].height)//起点/终点/宽度/高度
      }
    },
    //赋值初始值x轴的点、y轴的点
    startMark(event) {
      this.isMarking = true;//开始标记,记录此时开始标记的初始点
      const rect = this.canvas.getBoundingClientRect();
      this.startX = event.offsetX;
      this.startY = event.offsetY;
    },
    //标记中
    draw(event) {
      if (this.isMarking) {
        //获取最后标记的位置,拿最后标记的位置的x轴值、y轴值与初始位置的x轴值,与y轴值进行计算,获取标记矩形的宽高
        const rect = this.canvas.getBoundingClientRect();
        const x = event.offsetX - this.startX;
        const y = event.offsetY - this.startY;
        //canvas绘制时,会把背景图清掉,需要再次调用下添加背景图的方法
        this.drawBackground();
        this.ctx.strokeRect(this.startX, this.startY, x, y);
        this.rectObj={x:this.startX, y:this.startY,width: x,height:y};//把最后的标记矩形的位置及宽高记录,若是保存此时的标记,会用到
      }
    },
    //标记结束
    endMark() {
      this.isMarking = false;//重置标记标识为false
    },
    // 初始化canvas
    loadCanvas(){
      this.canvas = this.$refs.canvas;
      this.ctx = this.canvas.getContext('2d');
      this.image = new Image();
      this.image.src = cardPng; // 替换为你的图片路径
      let canvasDiv=document.getElementById("canvasDiv");
      this.image.onload = () => {
        this.drawBackground();
      };
    },
    saveBtn(){
      // 保存时,若是此时的矩形存在,则添加到标记数组里面
      console.log(this.rectObj,'rectObj')
      if(this.rectObj!=''){
        let that=this;
        //防止同一个标记被保存多次
        for(let i=0;i<this.markedRectangles.length;i++){
          if(this.markedRectangles[i].x!=that.rectObj.x&&this.markedRectangles[i].y!=that.rectObj.y&&this.markedRectangles[i].width!=that.rectObj.width&&this.markedRectangles[i].height!=that.rectObj.height){
            if(this.rectName){
              that.rectObj.name=this.rectName;
              that.markedRectangles.push(that.rectObj)
              that.$message.success('该标记已保存')
              that.rectObj='';
              return false
            }else{
              this.$message.warning('请输入标记名称')
            }
          }
        }
      }else{
        this.$message.warning('暂无标记可以保存')
        return false
      }
      console.log(this.markedRectangles,'markedRectangles')
    },
    //清除标记
    clearBtn(){
      //重绘canvas
      // 若是有resetid,则是已保存过的,若是没有resetid,则是后来新增的
      if(this.rectObj!=''){
        if(this.rectObj.resetid){
          // this.rectObj='';
          this.$confirm('确定删除?', '提示', {
            okText: '确定',
            cancelText: '取消',
            type: 'warning'
          }).then(() => {
            for(let i=0;i<this.markedRectangles.length;i++){
              if(this.markedRectangles[i].x==this.rectObj.x&&this.markedRectangles[i].y==this.rectObj.y&&this.markedRectangles[i].width==this.rectObj.width&&this.markedRectangles[i].height==this.rectObj.height){
                this.rectName='';
                this.rectObj='';
                this.markedRectangles.splice(i, 1); // 从数组中移除
                this.$message.success('标记已清除')
                this.drawBackground();
                return false
              }
            }
          }).catch(() => {
            this.$message({
              type: 'warning',
              message: '已取消删除'
            });          
          });
          
        }else{
          this.drawBackground();
          this.rectObj='';
          this.$message.warning('标记已清除')
          return false
        }
      }else{
        this.$message.warning('暂无标记可以清除')
      }

    },
    //一键清除所有标记
    clearAllBtn(){
      //把标记数组清空,同时重绘canvas
      if(this.markedRectangles.length>0){
        this.markedRectangles=[];
        this.drawBackground();
      }else{
        this.$message.warning('暂无标记可以清除')
        return false
      }
    },
  }
};
</script>
<style scoped>
#canvasDiv{
  width:800px;
  height: auto;
}
</style>

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

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

相关文章

整型变量的原子操作

什么是原子操作 原子操作&#xff08;Atomic Operation&#xff09;是指不可中断的操作&#xff0c;即在多线程环境下&#xff0c;当一个线程在执行原子操作时&#xff0c;不会被其他线程的调度和中断所影响。这种操作在多线程编程中尤为重要&#xff0c;因为它能保证操作的原…

根据服务器系统选择对应的MySQL版本

1. 根据服务器系统选择对应的MySQL版本 MySQL有多个版本&#xff0c;选择对应的版本&#xff0c;重点信息是Linux的GLIBC版本号&#xff0c;Linux的版本、系统位数。 1.1 查看Linux的GLIBC版本号 通常libc.so会支持多个版本&#xff0c;即向前兼容&#xff0c;查看该文件中…

管理类联考--复试--英文面试--经典问题

文章目录 考研复试英语口试日常问题及连续表达问题一、考研复试英语口试的相关事项二、考研英语复试口语日常问题列举考研原因 (reasons for my choice)研究生期间你的计划 (plans in the postgraduate study)介绍你的家乡 (hometown)你的家庭 (family)你的大学 (university)研…

【AI论文阅读笔记】ResNet残差网络

论文地址&#xff1a;https://arxiv.org/abs/1512.03385 摘要 重新定义了网络的学习方式 让网络直接学习输入信息与输出信息的差异(即残差) 比赛第一名1 介绍 不同级别的特征可以通过网络堆叠的方式来进行丰富 梯度爆炸、梯度消失解决办法&#xff1a;1.网络参数的初始标准化…

固态硬盘有缓存和没缓存有什么区别

固态硬盘&#xff08;SSD&#xff09;已经成为现代计算机的重要组成部分&#xff0c;它们提供了比传统机械硬盘更快的读写速度&#xff0c;从而显著提升了操作系统的运行速度和应用程序的加载效率。 其中&#xff0c;缓存&#xff08;Cache&#xff09;是固态硬盘中一个重要的…

Ansible inventory文件详解

我们知道inventory主要用来管理managed nodes(host),定义playbook要在哪些远程机器上执行。如果不熟悉inventory的角色的,可以参考ansible playbook基本概念。 在具体讲解如何构建inventory文件之前,我们必须知道在ansible的配置文件中,有一个参数inventory用于指定默认的…

PyQt4应用程序的PDF查看器

最近因为项目需要创建一个基于PyQt4的PDF查看器应用程序&#xff0c;正常来说&#xff0c;我们可以使用PyQt4的QtWebKit模块来显示PDF文件。那么具体怎么实现呢 &#xff1f;以下就是我写的一个简单的示例代码&#xff0c;演示如何创建一个PyQt4应用程序的PDF查看器&#xff1a…

几何相互作用GNN预测3D-PLA

预测PLA是药物发现中的核心问题。最近的进展显示了将ML应用于PLA预测的巨大潜力。然而,它们大多忽略了复合物的3D结构和蛋白质与配体之间的物理相互作用,而这对于理解结合机制至关重要。作者提出了一种结合3D结构和物理相互作用的几何相互作用图神经网络GIGN,用于预测蛋白质…

每周一算法:迭代加深搜索

题目链接 加成序列 题目描述 满足如下条件的序列 X X X&#xff08;序列中元素被标号为 1 、 2 、 3 … m 1、2、3…m 1、2、3…m&#xff09;被称为加成序列&#xff1a; X [ 1 ] 1 X[1]1 X[1]1 X [ m ] n X[m]n X[m]n X [ 1 ] < X [ 2 ] < … < X [ m − 1 ]…

泽众云真机-机型支持ADB调试功能即将上线

最近云真机平台在线客服&#xff0c;收到很多咨询关于ADB调试功能&#xff0c;什么时候能更新&#xff1f;据小编所知&#xff0c;正在升级之中&#xff0c;有一块专门为了解决ADB调试功能提前准备&#xff0c;升级网络硬件设备&#xff0c;目前平台的功能已开发完成&#xff0…

windows 资源管理器(explorer)占用高的问题

找到配置 win r 输入 gpedit.msc 打开本地组策略管理器 扎到本地计算机策略-》 用户配置-》管理模板-》 windows组件-》 搜索 将后续的设置项均设置为已禁用

SpringTask实现的任务调度与XXL-job实现的分布式任务调度【XXL-Job工作原理】

目录 任务调度 分布式任务调度 分布式任务调度存在的问题以及解决方案 使用SpringTask实现单体服务的任务调度 XXL-job分布式任务调度系统工作原理 XXL-job系统组成 XXL-job工作原理 使用XXL-job实现分布式任务调度 配置调度中心XXL-job 登录调度中心创建执行器和任务 …

CVPR2023 | 3D Data Augmentation for Driving Scenes on Camera

3D Data Augmentation for Driving Scenes on Camera 摄像机驾驶场景的 3D 数据增强 摘要翻译 驾驶场景极其多样和复杂&#xff0c;仅靠人力不可能收集到所有情况。虽然数据扩增是丰富训练数据的有效技术&#xff0c;但自动驾驶应用中现有的摄像头数据扩增方法仅限于二维图像…

MySQL order by 语句执行流程

全字段排序 假设这个表的部分定义是这样的&#xff1a; CREATE TABLE t (id int(11) NOT NULL,city varchar(16) NOT NULL,name varchar(16) NOT NULL,age int(11) NOT NULL,addr varchar(128) DEFAULT NULL,PRIMARY KEY (id),KEY city (city) ) ENGINEInnoDB; 有如下 SQL 语…

自动控制原理--matlab/simulink建模与仿真

第一讲 自动控制引论 第二讲 线性系统的数学模型 第三讲 控制系统的复域数学模型(传递函数) 第四讲 控制系统的方框图 /video/BV1L7411a7uL/?p35&spm_id_frompageDriver pandas, csv数据处理 numpy&#xff0c;多维数组的处理 Tensor&#xff0c;PyTorch张量 工作原理图…

留学生课设|R语言|研究方法课设

目录 INSTRUCTIONS Question 1. Understanding Quantitative Research Question 2. Inputting data into Jamovi and creating variables (using the dataset) Question 3. Outliers Question 4. Tests for mean difference Question 5. Correlation Analysis INSTRUCTIO…

Tomcat的使用

1. Tomcat 1.1 Tomcat 是什么 Tomcat 就是基于 Java 实现的一个开源免费, 也是被广泛使用的 HTTP 服务器 1.2 下载安装 Tomcat官网选择其中的 zip 压缩包, 下载后解压缩即可&#xff0c;解压缩的目录最好不要带 “中文” 或者 特殊符号 进入 webapps 目录,每个文件夹都对应…

graylog API 弱密码

graylog web 页面密码设置 输入密码&#xff1a;获取sha256加密后密码 echo -n "Enter Password: " && head -1 </dev/stdin | tr -d \n | sha256sum | cut -d" " -f1vi /etc/graylog/server/server.conf #修改以下配置 root_usernameroot ro…

Monorepo 解决方案 — 基于 Bazel 的 Xcode 性能优化实践

背景介绍 书接上回《Monorepo 解决方案 — Bazel 在头条 iOS 的实践》&#xff0c;在头条工程切换至 Bazel 构建系统后&#xff0c;为了支持用户使用 Xcode 开发的习惯&#xff0c;我们使用了开源项目 Tulsi 作为生成工具&#xff0c;用于将 Bazel 工程转换为 Xcode 工程。但是…

【爬虫开发】爬虫从0到1全知识md笔记第1篇:爬虫概述【附代码文档】

爬虫开发从0到1全知识教程完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;爬虫概述。selenium的其它使用方法。Selenium课程概要。常见的反爬手段和解决思路。验证码处理。chrome浏览器使用方法介绍。JS的解析。Mongodb的介绍和安装,小结。mongodb的简单使…