Canvas实现缩放+涂鸦改进

news/2024/11/24 13:37:33/

几个月以前,有人问了我一个canvass怎么实现缩放和涂鸦的问题,我基于当时的想法写了一篇博客,但是后来发现当时做的不完善,所以实现上其实还是有一些其他问题的。但是因为前段时间太忙了,也就一直没有机会去改进它。现在总算是有时间了,正好抽空把这个问题解决一下。因为不太熟悉 JS,感觉代码写多了(200行以上),复杂性上来之后,我的能力就无法来维护这个代码了,所以这次换一个面向对象的写法,感觉是好了一点。

演示效果

左边是展示的 Canvas,右边是缓存 Canvas(这个通常是不显示的),这里进行显示是为了让你更好的理解我的思路。移动图片是很好理解的,主要是缩放和绘制,绘制时,需要计算当前点在图片上面的位置,然后计算对应的在缓存 Canvas图片上的位置,然后在对应的位置进行绘制。今天的状态不好,不想写那么详细了,可以去看上一篇博客了解怎么做的,这里主要是代码上面的改进,总体的思路是不变的:
canvas实现图片缩放+涂鸦

  • 拖拽图片进行移动
  • 在图片上面进行绘制
  • 拖拽绘制的图片
    在这里插入图片描述

完整代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Image-Editor</title><style type="text/css">body, div {margin: 0;padding: 0;}#cs {float: left;}canvas {border: red 1px solid;}</style>
</head>
<body><div class="cas"><canvas id="cs" width="800" height="600"></canvas></div><div class="cas"><canvas id="cache_cs" width="800" height="600"></canvas></div><button id="draw_move" onclick="editor.moveOrDraw()">移动</button><script type="text/javascript">let ZOOM = [0.5, 0.6, 0.7, 1.0, 1.1, 1.2, 1.5, 2.0] // 缩放数组let editor = {isUserMove: true,   // 用户是否在移动,否则就是绘制isMouseDown: false, // 用户鼠标是否按下,按下才处理移动和绘制isInImage: false,zoomIndex: 3,  // 缩放下标lineWidth: 10, // 默认线宽unit: 400,     // 宽高的最大值width: 0,      // 图像的宽height: 0,     // 图像的高canvas: null,ctx: null,cacheCanvas: null,cacheCtx: null,vertexPos: {x:0, y:0},  // 左上顶点的位置mousePos: {x:0, y:0},   // 鼠标当前位置init: function() {this.canvas = document.getElementById("cs")this.ctx = this.canvas.getContext("2d")this.ctx.lineWidth = this.lineWidth * ZOOM[this.zoomIndex]this.ctx.strokeStyle = "red"this.cacheCanvas = document.getElementById("cache_cs")this.cacheCtx = this.cacheCanvas.getContext("2d")this.cacheCtx.lineWidth = this.lineWidth * ZOOM[this.zoomIndex]this.cacheCtx.strokeStyle = "black"},loadImage: function() {let img = new Image()img.src = "./husky.png"img.onload = () => {// 缩放图片if (img.width > img.height) {this.width = 400this.height = this.width * img.height / img.width} else {this.height = 400this.width = this.height * img.width / img.height}// 计算左上顶点的位置this.vertexPos = {x: (this.canvas.width-this.width)/2, y: (this.canvas.height-this.height)/2}let zoom = ZOOM[this.zoomIndex]this.ctx.drawImage(img, this.vertexPos.x, this.vertexPos.y, this.width*zoom, this.height*zoom)this.cacheCtx.drawImage(img, 0, 0, this.width, this.height)}},addMouseEvent: function() {// 鼠标按下this.canvas.onmousedown = e => {let x = e.clientX - this.canvas.offsetLeftlet y = e.clientY - this.canvas.offsetTopthis.isMouseDown = true// 每次按下鼠标时更新顶点的位置let zoom = ZOOM[this.zoomIndex]console.log("vertex: ", this.vertexPos)this.mousePos = {x: x, y: y}console.log("On (%d, %d)", x, y)// 判断是否点击在图像上, 否则不做处理if (this.isMouseInImage(x, y)) {console.log("In image")this.isInImage = true// 这里加一个选中提示框if (this.isUserMove) {this.drawChooseRect()} else {// 把画笔移动到鼠标点击处this.ctx.beginPath()this.ctx.moveTo(x, y)console.log("move: ", x, y)// 计算相对位置let cachePos = this.computeRelevantPos(x, y)this.cacheCtx.beginPath()this.cacheCtx.moveTo(cachePos.x, cachePos.y)}} else {console.log("Out image")}}// 鼠标移动this.canvas.onmousemove = e => {// 鼠标按下才处理if (!this.isMouseDown) {return}let x = e.clientX - this.canvas.offsetLeftlet y = e.clientY - this.canvas.offsetTop// 鼠标在图像外部不处理if (!this.isMouseInImage(x, y)) {return}let dx = x-this.mousePos.xlet dy = y-this.mousePos.y// 更新鼠标的位置this.mousePos.x = xthis.mousePos.y = yif (this.isUserMove) {// 移动操作// 更新顶点位置this.vertexPos.x = this.vertexPos.x + dxthis.vertexPos.y = this.vertexPos.y + dy// 重新绘制this.redraw()this.drawChooseRect()} else {// 绘制操作this.draw(x, y)}}// 鼠标滚轮this.canvas.onmousewheel = e => {// 禁止移动和缩放一起操作if (this.isMouseDown) {return}let x = e.clientX - this.canvas.offsetLeft;let y = e.clientY - this.canvas.offsetTop;              delta = e.wheelDelta;if (delta > 0) {if (this.zoomIndex + 1 < ZOOM.length) {this.zoomIndex += 1;} else {this.zoomIndex = ZOOM.length - 1;}} else {if (this.zoomIndex - 1 >= 0) {this.zoomIndex -= 1;} else {this.zoomIndex = 0;}}// 图像缩放this.redraw()}let mouseUpAndOut =e => {this.isMouseDown = falsethis.isInImage = false// 如果是在移动操作中,则清空canvas,重新绘制if (this.isUserMove) {this.redraw()}}// 鼠标松开和鼠标离开this.canvas.onmouseup = mouseUpAndOutthis.canvas.onmouseout = mouseUpAndOut},moveOrDraw: function() {this.isUserMove = !this.isUserMoveif (this.isUserMove) {document.getElementById("draw_move").innerText = "移动";} else {document.getElementById("draw_move").innerText = "绘制";}},redraw: function() {let zoom = ZOOM[this.zoomIndex]this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)// 如果从顶点处进行缩放,我感觉不是很好,所以考虑从图像的中心处开始缩放,// 所以这里的顶点的计算要多思考一下this.ctx.save()this.ctx.drawImage(this.cacheCanvas, 0, 0, this.width, this.height, this.vertexPos.x + (1-zoom)*this.width/2,this.vertexPos.y + (1-zoom)*this.height/2,this.width*zoom, this.height*zoom)this.ctx.restore()console.log("zoom vertex: ", this.vertexPos)},drawChooseRect: function() {let zoom = ZOOM[this.zoomIndex]this.ctx.save()this.ctx.lineWidth = 1this.ctx.strokeStyle = "red"this.ctx.strokeRect(this.vertexPos.x + (1-zoom)*this.width/2, this.vertexPos.y + (1-zoom)*this.height/2, this.width*zoom,this.height*zoom)this.ctx.restore()},draw: function(x, y) {// 在显示canvas中绘制图像,在缓存canvas中绘制let zoom = ZOOM[this.zoomIndex]this.ctx.save()this.ctx.lineWidth = this.ctx.lineWidth * zoomthis.ctx.lineTo(x, y)this.ctx.stroke()this.ctx.restore()// 计算在缓存canvas中的相对位置,并进行绘制let cachePos = this.computeRelevantPos(x, y)this.cacheCtx.save()// 缩小对应放大的尺寸this.cacheCtx.lineWidth = this.ctx.lineWidth / zoomthis.cacheCtx.lineTo(cachePos.x, cachePos.y)this.cacheCtx.stroke()this.cacheCtx.restore()},isMouseInImage: function(x, y) {let zoom = ZOOM[this.zoomIndex]let vx = this.vertexPos.x+(1-zoom)*this.width/2let vy = this.vertexPos.y+(1-zoom)*this.height/2let xInImage = vx <= x && x <= vx+this.width*zoomlet yInImage = vy <= y && y <= vy+this.height*zoomif (xInImage && yInImage) {return true}return false},computeRelevantPos: function(x, y) {// 对应的缓存画布坐标需要做一个转换// 计算相对位置,这里是这个程序最复杂的一部分了// 这里需要考虑到显示canvas中图像的顶点位置,缩放尺寸,// 然后来计算在对应的缓存canvas上的相对位置let zoom = ZOOM[this.zoomIndex]let vx = this.vertexPos.x+(1-zoom)*this.width/2let vy = this.vertexPos.y+(1-zoom)*this.height/2return {x: (x-vx) / zoom,y: (y-vy) / zoom}},run: function() {this.init()this.loadImage()this.addMouseEvent()}}editor.run()</script>
</body>
</html>

http://www.ppmy.cn/news/332341.html

相关文章

C/C++/Java/Go/Rust,Python喊你来打擂:3秒钟内统计出小于1亿的素数个数

前几天&#xff0c;有个非计算机专业的同学问我&#xff0c;如何快速找出1亿之内的孪生素数——所谓孪生素数&#xff0c;就是差值为2的两个素数。原本以为这是一个很简单的问题&#xff0c;随便用python写了一个方法&#xff0c;没想到却要跑17分钟左右。改用C试试&#xff0c…

看到「财富自由」就想吐

这些年&#xff0c;网上关于财富自由的人生故事&#xff0c;可以说是数不胜数。 不过&#xff0c;其中 70% 像是真假参半的凡尔赛炫耀帖&#xff0c;剩下的那 30% 则更像是兜售某些产品的广告。 比如前几天很火的腾讯 35 岁员工准备退休的帖子&#xff0c;就挺能说明问题。 这种…

我在翻译《Thinking in Java》(三)

1.8 单根的继承结构 自从C诞生以来&#xff0c;面向对象程序设计就出现了这样一个问题&#xff1a;是否所有的类最终都应该继承于一个共同的基类&#xff1f;在Java中&#xff08;实际上除C以外所有的OOP语言都是这样&#xff09;给出了肯定的回答&#xff0c;并把最终基类简单…

感知春运(1)车票

现在才说春运会不会有点晚。元宵已过&#xff0c;回家过年返回上班的人大部分都已回来。公交车&#xff0c;超市&#xff0c;马路上又重现往常的人潮涌动&#xff0c;擦肩而过。 今天周末&#xff0c;天气很冷&#xff0c;窗外还刮有缕缕冷风。想想还是算了&#xff0c;窝在家抱…

苹果6s照相快门声音设置_你不知道的8种手机快门启动方式,各有妙用!

如果问各位摄友&#xff0c;你是如何启动手机快门的&#xff1f;99%的摄友可能都会说通过按下界面下方的“大白点儿”。 如果再接着问&#xff0c;你知道还有哪些启动快门的方式吗&#xff1f;很多摄友可能就不太清楚了。 事实上&#xff0c;为了满足在不同情况下都可以随时按下…

SEO服务价格的影响因素(转)

可能是这个行业还比较新的原因吧&#xff0c;大家对SEO价格的把握还是没准儿&#xff0c;所以这里再根据自己最近跟几个客户交流过程中遇到的相关问题&#xff0c;再结合网上一些朋友分享的经验&#xff0c;再写一篇关于网站搜索引擎优化SEO如何报价的问题&#xff0c;并且探索…

2023年的深度学习入门指南(5) - 动手写第一个语言模型

2023年的深度学习入门指南(5) - 动手写第一个语言模型 上一篇我们介绍了openai的API&#xff0c;其实也就是给openai的API写前端。在其它各家的大模型跟gpt4还有代差的情况下&#xff0c;prompt工程是目前使用大模型的最好方式。 不过&#xff0c;很多编程出身的同学还是对于…

程序员参加5月软考高项考试的体会分享,是机会还是坑?

大家好我是陈哈哈&#xff0c;参加了今年5月27日的软考信息项目管理师考试&#xff0c;我深知高项的难度以及需要付出的时间和精力成本&#xff0c;我在3月份工作最忙的阶段毅然决然选择了加入。至于为什么&#xff0c;至少在一个月前我还没搞明白报名的勇气从何而来。作为一名…