canvas画图,画矩形可拖拽移动,可拖拽更改尺寸大小

提示:canvas画图,画矩形,圆形,直线,曲线可拖拽移动

文章目录

  • 前言
  • 一、画矩形,圆形,直线,曲线可拖拽移动
  • 总结


前言

一、画矩形,圆形,直线,曲线可拖拽移动

test.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>canvas跟随鼠标移动画透明线</title><style>div,canvas,img{user-select: none;}.my_canvas,.bg_img{position: absolute;top: 50%;left: 50%;transform: translate(-50%,-50%);}.cf{content: '';display: block;overflow: hidden;clear: both;}.fl{float: left;}.fr{float: right;}.bg_img{width: 674px;height: 495px;background: #ddd;}.img_tools{position: absolute;top: 20px;left: 50%;transform: translateX(-50%);border: 1px solid #eee;border-radius: 64px;height: 64px;line-height: 64px;box-sizing: border-box;padding: 15px 20px 0;}.img_tool{height: 32px;line-height: 32px;color: #000;font-size: 14px;text-align: center;width: 80px;border: 1px solid #ddd;border-radius: 32px;margin-right: 10px;cursor: pointer;position: relative;}.img_tool_active{color: #409EFF;border: 1px solid #409EFF;}.show_history{position: absolute;bottom:0;left: 50%;transform: translateX(-50%);}.show_history>img{width: 120px;margin-right: 10px;border: 1px solid #eee;border-radius: 4px;}.canvas_text{width: 120px;height: 32px;line-height: 32px;position: absolute;top: 0;left: 0;border: 1px solid #c0c0c0;border-radius: 4px;font-size: 16px;outline: none;background: none;display: none;font-family: Arial, Helvetica, sans-serif;padding-left: 0;letter-spacing: 0;}</style>
</head>
<body><div class="bg_img"></div><canvas id="myCanvasBot" class="my_canvas" width="674" height="495"></canvas><canvas id="myCanvasTop" class="my_canvas" width="674" height="495"></canvas><div class="img_tools cf"><div class="img_tool fl" onclick="changeType('curve',this)">涂鸦</div><div class="img_tool fl" onclick="changeType('line',this)">直线</div><div class="img_tool fl img_tool_active" onclick="changeType('rect',this)">矩形</div><div class="img_tool fl" onclick="changeType('ellipse',this)">圆形</div><!-- <div class="img_tool fl" onclick="changeType('eraser',this)">橡皮擦</div> --><!-- <div class="img_tool fl" onclick="changeType('text',this)">文字</div> --><!-- <div class="img_tool fl" onclick="changeType('revoke',this)">撤销</div> --><!-- <div class="img_tool fl" onclick="changeType('restore',this)">恢复</div> --></div><input id="canvasText" autofocus class="canvas_text" type="text"><div id="showHistory" class="show_history"></div><script>const canvasWidth = 674;const canvasHeight = 495;//底层canvasconst botCan = document.getElementById('myCanvasBot');//顶层canvasconst topCan = document.getElementById('myCanvasTop');//底层画布const botCtx = botCan.getContext('2d');//顶层画布const topCtx = topCan.getContext('2d');//鼠标是否按下  是否移动let isDown = false,isMove = false;//鼠标是否在canvas上抬起let isCanUp = false;//需要画图的轨迹let drawPoints = [];//起始点x,ylet startPoint = {x:0,y:0};//图片历史let historyList = [];//空历史historyList.push(new Image())//当前绘画历史indexlet historyIndex = -1;//icon历史// let partHistory = [];//操作类型let drawType = 'rect';//画线宽度const lineWidth = 10;//文字大小const fontSize = 16;//画线颜色let strokeStyle = 'rgba(255,0,0,0.6)';//path2D图形列表let pathList = [];//path2D单个图形let pathObj = null;//path2D的唯一标识let pathId = 0;//当前被激活的path2Dlet activePath = null;//是否为拖拽行为let isDrag = false;//拖拽是否移动let isDragMove = false;//是否为改变尺寸行为isResize = false;//改变尺寸点list let pointsList = [];//拖拽修改尺寸的点let activePoint = null;//文字输入框initconst canvasText = document.getElementById('canvasText');canvasText.style.display = 'none';canvasText.style.lineHeight = '32px';canvasText.style.height = '32px';canvasText.style.display = 'none';canvasText.style.color = 'none';canvasText.addEventListener('blur',()=>{topCtx.font = fontSize + 'px Arial, Helvetica, sans-serif';let h = parseFloat(canvasText.style.height);topCtx.fillText(canvasText.value, startPoint.x+1, startPoint.y+h/2+fontSize/2-1);canvasText.style.display = 'none';canvasText.value = '';topToBot();})//起始点x,ylet textPoint = {x:0,y:0};//鼠标按下const mousedown = (e)=>{isDown = true;let x = (e||window.event).offsetX;let y = (e||window.event).offsetY;if(canvasText.style.display == 'none')startPoint = {x,y};//检测是否点击到图形activePath = isPointInPath(x,y);if(activePath){isDrag = true;topCtx.strokeStyle = topCtx.fillStyle = botCtx.strokeStyle = botCtx.fillStyle = activePath.strokeStyle||strokeStyle;topCtx.lineWidth = botCtx.lineWidth = activePath.lineWidth||lineWidth;switch (activePath.type){case 'rect':makePathActive();break;case 'ellipse':makePathActive();break;case 'line':makePathActive();break;case 'curve':makePathActive();break;}return;}if(drawType == 'text'){textPoint = {x:x+topCan.offsetLeft-canvasWidth/2,y:y+topCan.offsetTop-canvasHeight/2};// canvasText.style.height = 32 + 'px';canvasText.style.top = textPoint.y+'px';canvasText.style.left = textPoint.x+'px';canvasText.style.display = 'block';canvasText.style.fontSize = fontSize + 'px';canvasText.style.color = strokeStyle;setTimeout(()=>{canvasText.focus();},100)}if(drawType == 'curve'){drawPoints = [];drawPoints.push({x,y});}topCtx.strokeStyle = topCtx.fillStyle = botCtx.strokeStyle = botCtx.fillStyle = strokeStyle;topCtx.lineWidth = botCtx.lineWidth = lineWidth;topCtx.lineCap = topCtx.lineJoin = botCtx.lineCap = botCtx.lineJoin = 'round';}//鼠标移动const mousemove = (e)=>{let x = (e||window.event).offsetX;let y = (e||window.event).offsetY;let distanceX = 0;let distanceY = 0;if(isDown){isMove = true;if(isDrag){isDragMove = true;switch(activePath.type){case 'curve':distanceX = x - startPoint.x;distanceY = y - startPoint.y;let newPoints = [];for(let i=0;i<activePath.drawPoints.length;i++){let drawPoint = activePath.drawPoints[i];newPoints.push({x:drawPoint.x + distanceX,y:drawPoint.y + distanceY});}drawCurve(newPoints);break;case 'line':distanceX = x - startPoint.x;distanceY = y - startPoint.y;drawLine(activePath.startX + distanceX,activePath.startY + distanceY,activePath.x + distanceX,activePath.y + distanceY,);break;case 'eraser':// drawEraser(x,y);break;case 'rect':// xy 为当前point的坐标// startPoint为点击的矩形上点   查看当前point.x点距离startPoint.x移动了多少  point.y点距离startPoint.y移动了多少drawRect(activePath.x + (x - startPoint.x),activePath.y + (y - startPoint.y),activePath.width,activePath.height);break;case 'ellipse':// drawEllipse(x,y);drawEllipse(activePath.x + (x - startPoint.x),activePath.y + (y - startPoint.y),activePath.radiusX,activePath.radiusY);break;}return;}switch(drawType){case 'curve':drawPoints.push({x,y});drawCurve(drawPoints);break;case 'line':drawLine(startPoint.x,startPoint.y,x,y);break;case 'eraser':drawEraser(x,y);break;case 'rect':// drawRect(x,y);drawRect(startPoint.x, startPoint.y, x-startPoint.x, y - startPoint.y);break;case 'ellipse':drawEllipse((x+startPoint.x)/2, (y+startPoint.y)/2, Math.abs((x-startPoint.x)/2), Math.abs((y-startPoint.y)/2),0,0, Math.PI*2,true);break;}}}//鼠标抬起const mouseup = (e)=>{isCanUp = true;if(isDown){isDown = false// topCan内容画到botCan上if(isDrag){isDrag = false;activePath = null;if(isDragMove){isDragMove = false;pathList.pop();}else{pathObj = pathList.pop();}topToBot();return}if(drawType!='text')topToBot();}}//topCan内容画到botCan上const topToBot = ()=>{if(pathObj){pathObj.id = pathId++;pathList.push(pathObj);topCtx.clearRect(0,0,canvasWidth,canvasHeight);if(isCanUp)isCanUp=false;botCtx[pathObj.shape](pathObj.path);pathObj = null;}drawPoints = [];isDown = false;isMove = false;}//判断是否点击到图形const isPointInPath = (x,y)=>{let PointInPath = null;for(let i=0;i<pathList.length;i++){let path = pathList[i];if(botCtx.isPointInStroke(path.path,x,y)){PointInPath = path;break;}}return PointInPath;}//激活rect图形轮廓const makePathActive = ()=>{botCtx.clearRect(0,0,canvasWidth,canvasHeight);let arr = [];for(let i=0;i<pathList.length;i++){let path = pathList[i] if(activePath.id != path.id){botCtx[path.shape](path.path);arr.push(path);}else{topCtx[path.shape](path.path);}   }arr.push(activePath);pathList = arr;}//画椭圆形const drawEllipse = (x,y,radiusX,radiusY)=>{//清除topCtx画布topCtx.clearRect(0,0,canvasWidth,canvasHeight);topCtx.beginPath();let path = new Path2D();// 椭圆path.ellipse(x,y,radiusX,radiusY,0,0, Math.PI*2,true);topCtx.stroke(path);pathObj = {type:'ellipse',shape:'stroke',path,x, y, radiusX, radiusY,lineWidth:topCtx.lineWidth||lineWidth,strokeStyle:topCtx.strokeStyle||strokeStyle};}//画矩形const drawRect = (x,y,width,height)=>{//清除topCtx画布topCtx.clearRect(0,0,canvasWidth,canvasHeight);topCtx.beginPath();let path = new Path2D();// 矩形path.rect(x,y,width,height);topCtx.stroke(path);pathObj = {type:'rect',shape:'stroke',path,x, y, width, height,lineWidth:topCtx.lineWidth||lineWidth,strokeStyle:topCtx.strokeStyle||strokeStyle};}//橡皮擦const drawEraser = (x,y)=>{//橡皮擦圆形半径const radius = lineWidth/2;botCtx.beginPath(); for(let i=0;i<radius*2;i++){//勾股定理高hlet h = Math.abs( radius - i); //i>radius h = i-radius; i<radius  h = radius - i//勾股定理llet l = Math.sqrt(radius*radius -h*h); //矩形高度let rectHeight = 1;//矩形宽度let rectWidth = 2*l;//矩形Xlet rectX = x-l;//矩形Ylet rectY = y-radius + i;botCtx.clearRect(rectX, rectY, rectWidth, rectHeight);}}//画透明度直线const drawLine = (startX,startY,x,y)=>{if(!isDown)return;//清空当前画布内容topCtx.clearRect(0,0,canvasWidth,canvasHeight);//必须每次都beginPath  不然会卡topCtx.beginPath();let path = new Path2D();path.moveTo(startX,startY);path.lineTo(x,y);topCtx.stroke(path);pathObj = {type:'line',shape:'stroke',path,x, y, startX, startY,lineWidth:topCtx.lineWidth||lineWidth,strokeStyle:topCtx.strokeStyle||strokeStyle};}//画带透明度涂鸦const drawCurve = (drawPointsParams)=>{// drawPoints.push({x,y});if(!drawPointsParams||drawPointsParams.length<1)return//清空当前画布内容topCtx.clearRect(0,0,canvasWidth,canvasHeight);//必须每次都beginPath  不然会卡topCtx.beginPath();let path = new Path2D();path.moveTo(drawPointsParams[0].x,drawPointsParams[0].y);for(let i=1;i<drawPointsParams.length;i++){path.lineTo(drawPointsParams[i].x,drawPointsParams[i].y);}topCtx.stroke(path);pathObj = {type:'curve',shape:'stroke',path,drawPoints:drawPointsParams,lineWidth:topCtx.lineWidth||lineWidth,strokeStyle:topCtx.strokeStyle||strokeStyle};}//切换操作const changeType = (type,that)=>{// if(drawType == type) return;let tools = document.getElementsByClassName('img_tool');for(let i=0;i<tools.length;i++){let ele = tools[i];if(ele.classList.contains('img_tool_active'))ele.classList.remove('img_tool_active');}that.classList.add('img_tool_active');drawType = type;//撤销if(drawType == 'revoke'){if(historyIndex>0){historyIndex--;drawImage(historyList[historyIndex]);}//恢复}else if(drawType == 'restore'){if(historyIndex<historyList.length - 1){historyIndex++;drawImage(historyList[historyIndex]);}}}const drawImage = (img)=>{botCtx.clearRect(0,0,canvasWidth,canvasHeight);botCtx.drawImage(img,0,0);}//canvas添加鼠标事件topCan.addEventListener('mousedown',mousedown);topCan.addEventListener('mousemove',mousemove);topCan.addEventListener('mouseup',mouseup);//全局添加鼠标抬起事件document.addEventListener('mouseup',(e)=>{let x = (e||window.event).offsetX;let y = (e||window.event).offsetY;let classList = (e.target || {}).classList || [];if(classList.contains('img_tool'))return;if(!isCanUp){isDown = false;// topCan内容画到botCan上if(isDrag){isDrag = false;activePath = null;if(isDragMove){isDragMove = false;pathList.pop();}else{pathObj = pathList.pop();}topToBot();return}if(drawType == 'line'&&!isDrag){let clientX = topCan.getBoundingClientRect().x;let clientY = topCan.getBoundingClientRect().y;drawLine(startPoint.x,startPoint.y,x-clientX,y-clientY);}// topCan内容画到botCan上topToBot();}});//全局添加鼠标移动事件document.addEventListener('mousemove',(e)=>{if(isMove)return isMove = false;let x = (e||window.event).offsetX;let y = (e||window.event).offsetY;if(drawType == 'line'&&!isDrag){let clientX = topCan.getBoundingClientRect().x;let clientY = topCan.getBoundingClientRect().y;drawLine(startPoint.x,startPoint.y,x-clientX,y-clientY);}});</script>
</body>
</html>

请添加图片描述

总结

踩坑路漫漫长@~@

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

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

相关文章

两张图片相似度匹配算法学习路线

大纲&#xff1a;​​​​​​目标跟踪基础&#xff1a;两张图片相似度算法-腾讯云开发者社区-腾讯云 (tencent.com) 目标跟踪基础&#xff1a;两张图片相似度算法 (qq.com) 一、传统方法 1.欧式距离&#xff08;用于判断是否完全相同&#xff09; [三维重建] [机器学习] 图…

DC电源模块的设计与调试技巧

BOSHIDA DC电源模块的设计与调试技巧 DC电源模块的设计与调试是电子工程师在实际项目中常常需要面对的任务。一个稳定可靠的DC电源模块对于电路的正常运行起到至关重要的作用。以下是一些设计与调试的技巧&#xff0c;帮助工程师们更好地完成任务。 第一&#xff0c;正确选择…

如何简化多个 if 的判断结构

多少算太多&#xff1f; 有些人认为数字就是一&#xff0c;你应该总是用至少一个三元运算符来代替任何单个 if 语句。我并不这样认为&#xff0c;但我想强调一些摆脱常见的 if/else 意大利面条代码的方法。 我相信很多开发人员很容易陷入 if/else 陷阱&#xff0c;不是因为其…

git的使用日常习惯规范与一些特殊操作

git的使用日常习惯规范与一些特殊操作 操作习惯规范创建本地新分支&#xff0c;推送新分支到云端仓库1.创建一个本地的login分支2.创建新分支后切换到新分支3.推送新分支到云端 git的特殊操作撤回commit&#xff08;取消提交到本地版本库的动作&#xff0c;本地工作区写的代码不…

鸿蒙开发(七)-UIAbility启动模式

鸿蒙开发(七)-启动模式 根据代码中定义,UIAbility的启动模式有以下几种&#xff1a; "launchType": {"description": "Indicates the boot mode of ability.","type": "string","enum": ["standard",…

springboot点餐平台网站

目 录 摘 要 1 前 言 2 第1章 概述 2 1.1 研究背景 3 1.2 研究目的 3 1.3 研究内容 4 第二章 开发技术介绍 5 2.1相关技术 5 2.2 Java技术 6 2.3 MySQL数据库 6 2.4 Tomcat介绍 7 2.5 Spring Boot框架 8 第三章 系统分析 9 3.1 可行性分析 9 3.1.1 技术可行性 9 3.1.2 经济可行…

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记12:DAC数模转换

系列文章目录 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记01&#xff1a;赛事介绍与硬件平台 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记02&#xff1a;开发环境安装 嵌入式|蓝桥杯STM32G431&#xff08;…

Django(二)-搭建第一个应用(1)

一、项目环境和结构 1、项目环境 2、项目结构 二、编写项目 1、创建模型 代码示例: import datetimefrom django.db import models from django.utils import timezone# Create your models here.class Question(models.Model):question_text models.CharField(max_length2…

golang grpc和protobuf的版本降级问题(version4 -> version3)

最后更新于2024年3月28日 10:57:52 简中没查到类似的文章。一点小事闹麻了&#xff0c;搞了一天&#xff0c;特意发出来造福大家。 所谓的版本就是下面这个东西proto.ProtoPackageIsVersion4或者proto.ProtoPackageIsVersion3&#xff1a; 目的 为了适配旧代码&#xff0c…

【Monero】Wallet RPC | Wallet CLI | 门罗币命令行查询余额、种子、地址等命令方法教程

ubuntu22.04 首先在运行daemon&#xff0c;详细安装运行教程可参考&#xff1a;The Monero daemon (monerod) ./monerodWallet CLI run ./monero-wallet-cli如果还没有钱包就根据提示创建钱包即可 输入密码 查询余额 balance查询种子 seed其他可执行命令操作&#xff1…

跳槽多次未成功,问题源自何处?

众所周知&#xff0c;2023年市场很难&#xff01;看着企业们纷纷裁员&#xff0c;甚至连内推这个后门都走不通&#xff01;哪怕有面试&#xff0c;都是屡屡碰壁&#xff0c;你想清楚问题出在哪了吗&#xff1f;&#x1f62d;“求职不得&#xff0c;夜不能寐&#xff1b;三更半夜…

GEE土地分类——基于遥感影像数据的不同作物的分类

简介 这里我们首先要更改原始代码的中的影像和研究区矢量的问题,这个为了防止我们计算的过程超限,建议先将我们的研究区影像和样本点先存在自己的assets中,然后导入到新的脚本中。然周本文就是对其进行影像进行归一化处理,然后进行样本点值提取至点,然后训练样本点,进行…

学习刷题-14

3.29 贪心算法 跳跃游戏 II 给定一个非负整数数组&#xff0c;你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 贪心的思路&#xff0c;局部最优&#xff1a;当前可移动距离尽可能多…

【4】单链表(有虚拟头节点)

【4】单链表&#xff08;有虚拟头节点&#xff09; 1、虚拟头节点2、构造方法3、node(int index) 返回索引位置的节点4、添加5、删除6、ArrayList 复杂度分析(1) 复杂度分析(2) 数组的随机访问(3) 动态数组 add(E element) 复杂度分析(4) 动态数组的缩容(5) 复杂度震荡 7、单链…

3.两数相加 - 链表

文章目录 题目简介题目解答代码&#xff1a; 题目链接 大家好&#xff0c;我是晓星航。今天为大家带来的是 两数相加 相关的讲解&#xff01;&#x1f600; 题目简介 题目解答 通过题目给的第一个示例来解析 图解如下&#xff1a; l1的2和l2的5首先相加变为7 这里相加结果为7…

Ubuntu18.04安装wireshark

安装wireshark 环境Ubuntu18.04 1.使用root用户进行安装 2.将 wireshark-dev/stable PPA 添加到系统的软件源列表中。系统就可以从该PPA获取Wireshark软件包及其更新了。 apt-add-repository ppa:wireshark-dev/stable3.确保你系统上的软件包信息是最新的&#xff0c;这样在…

Yarn - macOS 上安装使用

文章目录 关于 YarnYarn 工作流程安装检查安装、查看版本 常用命令 关于 Yarn Yarn 是一款成熟的开源软件包管理器&#xff0c;用于管理 JavaScript 项目中的依赖关系。 官网&#xff1a;https://yarnpkg.comgithub : https://github.com/yarnpkg官方文档&#xff1a;https:/…

高炉项目中DeviceNET到Ethernet的转换奥秘

在工业自动化的世界中&#xff0c;高炉项目中的数据通信至关重要。其中DeviceNET和Ethernet作为两种主流的网络协议&#xff0c;扮演着不可或缺的角色。它们之间的转换不仅仅是技术上的桥梁&#xff0c;更是实现信息高效传递的关键。今天&#xff0c;我们就来揭开从DeviceNET到…

数据结构——lesson13排序之计数排序

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

Java 中的单例模式

引言&#xff1a; 在 Java 编程中&#xff0c;单例模式是一种常见的设计模式&#xff0c;它保证一个类只能创建一个实例&#xff0c;并提供一个全局访问点。单例模式在很多场景下都非常有用&#xff0c;比如线程池、日志系统、数据库连接池等。本文将详细介绍 Java 中单例模式的…