canvas 图片拖拽旋转之二——canvas状态保存(save和restore)
引言
在上一篇日志“canvas 图片拖拽旋转之一”中,对坐标转换有了比较深入的了解,但是仅仅利用坐标转换实现的拖拽旋转,会改变canvas坐标系的状态,从而影响画布上其他元素的绘制。因此,这个时候需要用到一对canvas方法,在变换坐标系前保存canvas状态,在变换并绘制完成之后,恢复canvas状态,即save()和restore()。
[备注]
这篇文章只是记录分享下解决问题的过程,找我要demo的,或者问我什么东西怎么做的,就不要加我了。你可以加一个canvas相关的交流群,或者如果需要用到KineticJS/FabricJS的话,可以加群251572039。
一、理解save和restore的操作对象
对于save和restore方法,有一个误解就是,认为每一步都save之后restore就等同于ctrl+z。其实save保存的只是CanvasRenderingContext2D 对象的状态以及对象的所有属性,并不包括这个对象上绘制的图形。引用一段w3school上的解释:
save() 和 restore() 方法允许你保存和恢复一个 CanvasRenderingContext2D 对象的状态。save() 把当前状态推入到栈中,而 restore() 从栈的顶端弹出最近保存的状态,并且根据这些存储的值来设置当前绘图状态。
CanvasRenderingContext2D 对象的所有属性(除了画布的属性是一个常量)都是保存的状态的一部分。变换矩阵和剪切区域也是这个状态的一部分,但是当前路径和当前点并不是。
也就是说,save()可以保存canvas的状态(比如坐标系的状态)以及fillStyle、strokeStyle、lineWidth 等等属性。
基于这一点,我们就可以在变换坐标系之前save(),变换并绘制完成后restor(),这样就可以保证图形发生了旋转偏移而canvas坐标系仍然是屏幕坐标系的状态(类似于拿了一把标尺画完图之后又把标尺放回了原位)。关键代码如下:
ctx.save();<br/> ctx.translate(PO.x,PO.y);//坐标原点移动到图片中心点<br/> ctx.rotate(now_rotate_radian);//旋转画布 在屏幕坐标系基础上旋转 now_rotate_radian<br/> ctx.drawImage(img,-imgW/2,-imgH/2);<br/> drawRotateCtrl();<br/> ctx.restore();//还原画布坐标系
二、实现思路
因为涉及到旋转,所以不管是进行拖拽还是旋转,每次绘制都是先将canvas坐标系原点translate到图片中心点,然后rotate旋转的角度。
根据坐标变换原则,在旋转时计算角度变化(鼠标相对于图片中心点与屏幕坐标系y轴负方向的夹角),拖拽时记录图片中心点偏移。
三、代码实现
demo链接:http://youryida.duapp.com/demo_canvas/img_move_rotate.html
<!doctype html><br/> <html><br/> <head><br/> <title> </title><br/> <meta http-equiv="X-UA-Compatible" content="IE=9"><br/> <meta charset="utf-8" /><br/> <meta http-equiv="pragma" content="no-cache"><br/> <meta http-equiv="cache-control" content="no-cache"><br/> <meta http-equiv="expires" content="0"><br/> <style><br/> #canvas{border:1px solid #ccc;}<br/> </style><br/> </head><br/> <body><br/> <canvas id="canvas" width="500" height="300"></canvas><br/> <br/><br/> <pre><br/> 一、因为涉及到旋转,所以不管是进行拖拽还是旋转,每次绘制都是先将canvas坐标系原点移到图片中心点。旋转时记录角度变化,拖拽时记录图片中心点偏移。<br/> 二、为了不影响坐标系变换后其他图形的绘制,使用ctx.save()保存旋转前的坐标系,绘制完成后ctx.restore()恢复。<br/> QQ:1140215489<br/> </pre><br/> <script><br/> var cvs =document.getElementById("canvas");<br/> var ctx =cvs.getContext("2d");<br/> var cvsH=cvs.height;<br/> var cvsW=cvs.width;<br/> var beginX,beginY;<br/> var LT={x:30,y:30};//图片初始载入左上角坐标<br/> var PO;<br/> var Selected_Round_R=12;<br/> var now_rotate_radian=0;//记录图片的旋转角度 初始为0<br/> var isDown=false;<br/> var moveAble=false,rotateAble=false;<br/> var imgH,imgW;<br/> var img = new Image();<br/> img.src ="img/niuniu.jpg";<br/> img.onload=function (){<br/> imgH=img.height;<br/> imgW=img.width;<br/> PO={x:LT.x+imgW/2,y:LT.y+imgH/2};<br/> onDraw();<br/> }<br/> function imgIsDown(x,y){<br/> var P={x:x,y:y};<br/> var A={x:PO.x-imgW/2,y:PO.y-imgH/2};<br/> var B={x:PO.x+imgW/2,y:PO.y-imgH/2};<br/> var C={x:PO.x+imgW/2,y:PO.y+imgH/2};<br/> var D={x:PO.x-imgW/2,y:PO.y+imgH/2};<br/> A=getRotatedPoint(A,PO,now_rotate_radian);<br/> B=getRotatedPoint(B,PO,now_rotate_radian);<br/> C=getRotatedPoint(C,PO,now_rotate_radian);<br/> D=getRotatedPoint(D,PO,now_rotate_radian);<br/> var APB=getRadian(A,P,B);<br/> var BPC=getRadian(B,P,C);<br/> var CPD=getRadian(C,P,D);<br/> var DPA=getRadian(D,P,A);<br/> var sum=(APB+BPC+CPD+DPA).toFixed(5);//某点与矩形四顶点夹角之和=360那么判断点在矩形内<br/> return (sum==(2*Math.PI).toFixed(5));<br/> }<br/> function RTIsDown(x,y){<br/> var RT={x:PO.x,y:PO.y-imgH/2-Selected_Round_R};<br/> var rotate_top=getRotatedPoint(RT,PO,now_rotate_radian);<br/> var bool=(x-rotate_top.x)*(x-rotate_top.x)+(y-rotate_top.y)*(y-rotate_top.y)<=Selected_Round_R*Selected_Round_R;<br/> return bool;<br/> }<br/> function onDraw(){<br/> ctx.clearRect(0,0,cvsW,cvsH);<br/> ctx.save();<br/> ctx.translate(PO.x,PO.y);//坐标原点移动到图片中心点<br/> ctx.rotate(now_rotate_radian);//旋转画布 在屏幕坐标系基础上旋转 now_rotate_radian<br/> ctx.drawImage(img,-imgW/2,-imgH/2);<br/> drawRotateCtrl();<br/> ctx.restore();//还原画布坐标系<br/> }<br/> function drawRotateCtrl(){<br/> var rotate_top={x:0,y:-imgH/2-Selected_Round_R};<br/> ctx.beginPath();<br/> ctx.arc(rotate_top.x,rotate_top.y,Selected_Round_R,0,Math.PI*2,false);<br/> ctx.closePath();<br/> ctx.lineWidth=2;<br/> ctx.strokeStyle="#0000ff";<br/> ctx.stroke();<br/> }<br/> cvs.addEventListener("mousedown", startMove, false);<br/> cvs.addEventListener("mousemove", moving, false);<br/> cvs.addEventListener("mouseup", endMove, false);<br/> cvs.addEventListener("mouseout",endMove, false);<br/> function startMove(){<br/> preventDefault();<br/> isDown=true;<br/> var loc=getEvtLoc();<br/> var x=loc.x,y=loc.y;<br/> beginX=x,beginY=y;<br/> moveAble=imgIsDown(x,y);<br/> rotateAble=RTIsDown(x,y);<br/> if (moveAble) cvs.style.cursor="move";<br/> if (rotateAble) cvs.style.cursor="crosshair";<br/> }<br/> function moving(){<br/> preventDefault();<br/> if(isDown==false) return;<br/> var loc=getEvtLoc();<br/> var x=loc.x,y=loc.y;<br/> if(moveAble){<br/> var dx=x-beginX,dy=y-beginY;<br/> PO.x=PO.x+dx;<br/> PO.y=PO.y+dy;<br/> onDraw();<br/> }<br/> if(rotateAble){<br/> var A={x:beginX-PO.x,y:beginY-PO.y};//转换为以PO为原点的屏幕坐标系 计算鼠标move前后两点与y轴反方向的角度<br/> var B={x:x-PO.x,y:y-PO.y};<br/> var a=Math.atan2(A.x,-A.y);<br/> var b=Math.atan2(B.x,-B.y);<br/> var θ=b-a;<br/> now_rotate_radian+=θ;<br/> onDraw();<br/> }<br/> beginX=x,beginY=y;<br/> }<br/> function endMove(){<br/> isDown=false;<br/> moveAble=rotateAble=false;<br/> cvs.style.cursor="auto";<br/> }<br/> function getEvtLoc(){<br/> return {x:event.offsetX,y:event.offsetY}<br/> }<br/> //取消浏览器默认事件<br/> function preventDefault(){<br/> if(event.preventDefault){<br/> event.preventDefault();<br/> }else{<br/> event.returnValue = false;//注意加window<br/> }<br/> }<br/> //获取三点角度<br/> function getRadian(A,O,B) {<br/> var Xo=O.x,Yo=O.y;<br/> var Xa=A.x,Ya=A.y;<br/> var Xb=B.x,Yb=B.y;<br/> var oa = Math.sqrt((Xo - Xa) * (Xo - Xa) + (Yo - Ya)* (Yo - Ya));<br/> var ob = Math.sqrt((Xo - Xb) * (Xo - Xb) + (Yo - Yb)* (Yo - Yb));<br/> var ab = Math.sqrt((Xa - Xb) * (Xa - Xb) + (Ya - Yb)* (Ya - Yb));<br/> var aob = Math.acos((oa * oa + ob * ob - ab * ab) / (2 * oa * ob)); // 弧度<br/> return aob;//<br/> }<br/> //获取绕点旋转角度后的新点坐标<br/> function getRotatedPoint(A,O,α){<br/> var dx =A.x-O.x;<br/> var dy =A.y-O.y;<br/> var x=Math.cos(α)*dx-Math.sin(α)*dy+O.x;<br/> var y=Math.cos(α)*dy+Math.sin(α)*dx+O.y;<br/> return {x:x,y:y};<br/> } </script><br/> </body><br/> </html>
转发申明:
本文转自互联网,由小站整理并发布,在于分享相关技术和知识。版权归原作者所有,如有侵权,请联系本站,将在24小时内删除。谢谢