引言

在上一篇日志“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();
ctx.translate(PO.x,PO.y);//坐标原点移动到图片中心点
ctx.rotate(now_rotate_radian);//旋转画布 在屏幕坐标系基础上旋转 now_rotate_radian
ctx.drawImage(img,-imgW/2,-imgH/2);
drawRotateCtrl();
ctx.restore();//还原画布坐标系

二、实现思路

因为涉及到旋转,所以不管是进行拖拽还是旋转,每次绘制都是先将canvas坐标系原点translate到图片中心点,然后rotate旋转的角度。

根据坐标变换原则,在旋转时计算角度变化(鼠标相对于图片中心点与屏幕坐标系y轴负方向的夹角),拖拽时记录图片中心点偏移。

三、代码实现

  demo链接:http://youryida.duapp.com/demo_canvas/img_move_rotate.html

  

<!doctype html>
<html>
<head>
<title> </title>
<meta http-equiv="X-UA-Compatible" content="IE=9">
<meta charset="utf-8" />
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<style>
#canvas{border:1px solid #ccc;}
</style>
</head>
<body>
<canvas id="canvas" width="500" height="300"></canvas>
<br/>
<pre>
一、因为涉及到旋转,所以不管是进行拖拽还是旋转,每次绘制都是先将canvas坐标系原点移到图片中心点。旋转时记录角度变化,拖拽时记录图片中心点偏移。
二、为了不影响坐标系变换后其他图形的绘制,使用ctx.save()保存旋转前的坐标系,绘制完成后ctx.restore()恢复。
QQ:1140215489
</pre>
<script>
var cvs =document.getElementById("canvas");
var ctx =cvs.getContext("2d");
var cvsH=cvs.height;
var cvsW=cvs.width;
var beginX,beginY;
var LT={x:30,y:30};//图片初始载入左上角坐标
var PO;
var Selected_Round_R=12;
var now_rotate_radian=0;//记录图片的旋转角度 初始为0
var isDown=false;
var moveAble=false,rotateAble=false;
var imgH,imgW;
var img = new Image();
img.src ="img/niuniu.jpg";
img.onload=function (){
imgH=img.height;
imgW=img.width;
PO={x:LT.x+imgW/2,y:LT.y+imgH/2};
onDraw();
}
function imgIsDown(x,y){
var P={x:x,y:y};
var A={x:PO.x-imgW/2,y:PO.y-imgH/2};
var B={x:PO.x+imgW/2,y:PO.y-imgH/2};
var C={x:PO.x+imgW/2,y:PO.y+imgH/2};
var D={x:PO.x-imgW/2,y:PO.y+imgH/2};
A=getRotatedPoint(A,PO,now_rotate_radian);
B=getRotatedPoint(B,PO,now_rotate_radian);
C=getRotatedPoint(C,PO,now_rotate_radian);
D=getRotatedPoint(D,PO,now_rotate_radian);
var APB=getRadian(A,P,B);
var BPC=getRadian(B,P,C);
var CPD=getRadian(C,P,D);
var DPA=getRadian(D,P,A);
var sum=(APB+BPC+CPD+DPA).toFixed(5);//某点与矩形四顶点夹角之和=360那么判断点在矩形内
return (sum==(2*Math.PI).toFixed(5));
}
function RTIsDown(x,y){
var RT={x:PO.x,y:PO.y-imgH/2-Selected_Round_R};
var rotate_top=getRotatedPoint(RT,PO,now_rotate_radian);
var bool=(x-rotate_top.x)*(x-rotate_top.x)+(y-rotate_top.y)*(y-rotate_top.y)<=Selected_Round_R*Selected_Round_R;
return bool;
}
function onDraw(){
ctx.clearRect(0,0,cvsW,cvsH);
ctx.save();
ctx.translate(PO.x,PO.y);//坐标原点移动到图片中心点
ctx.rotate(now_rotate_radian);//旋转画布 在屏幕坐标系基础上旋转 now_rotate_radian
ctx.drawImage(img,-imgW/2,-imgH/2);
drawRotateCtrl();
ctx.restore();//还原画布坐标系
}
function drawRotateCtrl(){
var rotate_top={x:0,y:-imgH/2-Selected_Round_R};
ctx.beginPath();
ctx.arc(rotate_top.x,rotate_top.y,Selected_Round_R,0,Math.PI*2,false);
ctx.closePath();
ctx.lineWidth=2;
ctx.strokeStyle="#0000ff";
ctx.stroke();
}
cvs.addEventListener("mousedown", startMove, false);
cvs.addEventListener("mousemove", moving, false);
cvs.addEventListener("mouseup", endMove, false);
cvs.addEventListener("mouseout",endMove, false);
function startMove(){
preventDefault();
isDown=true;
var loc=getEvtLoc();
var x=loc.x,y=loc.y;
beginX=x,beginY=y;
moveAble=imgIsDown(x,y);
rotateAble=RTIsDown(x,y);
if (moveAble) cvs.style.cursor="move";
if (rotateAble) cvs.style.cursor="crosshair";
}
function moving(){
preventDefault();
if(isDown==false) return;
var loc=getEvtLoc();
var x=loc.x,y=loc.y;
if(moveAble){
var dx=x-beginX,dy=y-beginY;
PO.x=PO.x+dx;
PO.y=PO.y+dy;
onDraw();
}
if(rotateAble){
var A={x:beginX-PO.x,y:beginY-PO.y};//转换为以PO为原点的屏幕坐标系 计算鼠标move前后两点与y轴反方向的角度
var B={x:x-PO.x,y:y-PO.y};
var a=Math.atan2(A.x,-A.y);
var b=Math.atan2(B.x,-B.y);
var θ=b-a;
now_rotate_radian+=θ;
onDraw();
}
beginX=x,beginY=y;
}
function endMove(){
isDown=false;
moveAble=rotateAble=false;
cvs.style.cursor="auto";
}
function getEvtLoc(){
return {x:event.offsetX,y:event.offsetY}
}
//取消浏览器默认事件
function preventDefault(){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue = false;//注意加window
}
}
//获取三点角度
function getRadian(A,O,B) {
var Xo=O.x,Yo=O.y;
var Xa=A.x,Ya=A.y;
var Xb=B.x,Yb=B.y;
var oa = Math.sqrt((Xo - Xa) * (Xo - Xa) + (Yo - Ya)* (Yo - Ya));
var ob = Math.sqrt((Xo - Xb) * (Xo - Xb) + (Yo - Yb)* (Yo - Yb));
var ab = Math.sqrt((Xa - Xb) * (Xa - Xb) + (Ya - Yb)* (Ya - Yb));
var aob = Math.acos((oa * oa + ob * ob - ab * ab) / (2 * oa * ob)); // 弧度
return aob;//
}
//获取绕点旋转角度后的新点坐标
function getRotatedPoint(A,O,α){
var dx =A.x-O.x;
var dy =A.y-O.y;
var x=Math.cos(α)*dx-Math.sin(α)*dy+O.x;
var y=Math.cos(α)*dy+Math.sin(α)*dx+O.y;
return {x:x,y:y};
} </script>
</body>
</html>

canvas 图片拖拽旋转之二——canvas状态保存(save和restore)的更多相关文章

  1. canvas 图片拖拽旋转之一——坐标转换translate

    引言 对canvas中绘制的图片进行旋转操作,需要使用ctx.translate变换坐标系,将图片旋转的基点设为坐标系的原点,然后ctx.rotate旋转. 这个时候,因为canvas坐标系发生了旋转 ...

  2. HTML5新特性之Canvas+drag(拖拽图像实现图像反转)

    1.什么是canvas 在网页上使用canvas元素时,会创建一块矩形区域,默认矩形区域宽度300px,高度150px.. 页面中加入canvas元素后,可以通过javascript自由控制.可以在其 ...

  3. 自制一个H5图片拖拽、裁剪插件(原生JS)

    前言 如今的H5运营活动中,有很多都是让用户拍照或者上传图片,然后对照片加滤镜.加贴纸.评颜值之类的.尤其是一些拍照软件公司的运营活动几乎全部都是这样的. 博主也做过不少,为了省事就封装了一个简单的图 ...

  4. HTML5图片拖拽预览原理及实现

    一.前言 这两天恰好有一位同事问我怎样做一个图片预览功能.作为现代人的我们首先想到的当然是HTML5啦,其实HTML5做图片预览已经是一个老生常谈的问题了.我在这里就简单说说其中相关的一些东西,当然会 ...

  5. vue在移动端使用alloyfinger手势库操作图片拖拽、缩放

    最近开发一个活动需要在手机上给上传的头像加上边框.装饰,需要拖拽.手势缩放边框下的头像图片,因为是vue项目,开始尝试了vue-drag-resize这个组件,对图片拖拽支持很完美,但是无法手势缩放, ...

  6. HTML5多图片拖拽上传带进度条

    前言 昨天利用css2的clip属性实现了网页进度条觉得还不错,但是很多情况下,我们在那些时候用进度条呢,一般网页加载的时候如果有需要可以用,那么问题就来了,怎么才算整个加载完毕呢,是页面主要模块加载 ...

  7. Android 仿微信朋友圈发表图片拖拽和删除功能

    朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...

  8. Vue富文本编辑器(图片拖拽缩放)

    富文本编辑器(图片拖拽缩放) 需求: 根据业务要求,需要能够上传图片,且上传的图片能在移动端中占满屏幕宽度,故需要能等比缩放上传的图片,还需要能拖拽.缩放.改变图片大小.尝试多个第三方富文本编辑器,很 ...

  9. CSS 奇思妙想 | 使用 resize 实现强大的图片拖拽切换预览功能

    本文将介绍一个非常有意思的功能,使用纯 CSS 利用 resize 实现强大的图片切换预览功能.类似于这样: 思路 首先,要实现这样一个效果如果不要求可以拖拽,其实有非常多的办法. 将两张图片叠加在一 ...

随机推荐

  1. sqlserver 连接mysql

    配置与电脑相对应的odbc   http://dev.mysql.com/downloads/connector/odbc/

  2. Sage CRM 平衡区域树结构

    Sage CRM 的区域是把整数区间-214748364 ~02147483647划分为一个个相等的区间,使用数字的范围来表示区域的概念. 默认情况下,crm把区域划分为10

  3. 用jdbc访问二进制类型的数据

    package it.cast.jdbc; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; impor ...

  4. 禁用nested loop join里的spool

    禁用nested loop join里的spool 转载自: https://blogs.msdn.microsoft.com/psssql/2015/12/15/spool-operator-and ...

  5. Raft 为什么是更易理解的分布式一致性算法

    一致性问题可以算是分布式领域的一个圣殿级问题了,关于它的研究可以回溯到几十年前. 拜占庭将军问题 Leslie Lamport 在三十多年前发表的论文<拜占庭将军问题>(参考[1]). 拜 ...

  6. HDFS 异常处理与恢复

    在前面的文章 <HDFS DataNode 设计实现解析>中我们对文件操作进行了描述,但并未展开讲述其中涉及的异常错误处理与恢复机制.本文将深入探讨 HDFS 文件操作涉及的错误处理与恢复 ...

  7. CYQ.Data 快速开发EasyUI

    EasyUI: 前端UI框架之一, 相对ExtJs来说,算是小了,这两天,抽空看了下EasyUI的相关知识,基本上可以和大伙分享一下: 官网: http://www.jeasyui.com/ 学习的话 ...

  8. 拥抱.NET Core,学习.NET Core的基础知识补遗

    前言 .NET Core的新特性之一就是跨平台,但由于对之前框架的兼容导致编写一个.NET Core类库变得相当复杂,主要体现为相当多的框架目标和支持平台,今天我们就对.NET Core的跨平台特性进 ...

  9. Redis性能问题排查解决手册(七)

     阅读目录: 性能相关的数据指标 内存使用率used_memory 命令处理总数total_commands_processed 延迟时间 内存碎片率 回收key 总结 性能相关的数据指标 通过Red ...

  10. 策划编写一个新的Helper类

    https://code.csdn.net/jy02305022/blqw-data 有朋友看见的话给点意见呗