canvas 图片拖拽旋转之一——坐标转换translate
引言
对canvas中绘制的图片进行旋转操作,需要使用ctx.translate变换坐标系,将图片旋转的基点设为坐标系的原点,然后ctx.rotate旋转。
这个时候,因为canvas坐标系发生了旋转,而视觉感受上的坐标以及鼠标事件中的坐标都是旋转之前的屏幕坐标系。再根据鼠标的移动去控制canvas中的图片时,就会出现问题。
用A坐标系中的偏移来控制B坐标系中的图形,就需要进行一个坐标转换,即通过一种转换关系,将A坐标系中的点在B坐标系中表示出来,然后根据B坐标系中的偏移来控制B坐标系中的图形。
下面按照先易后难的顺序,把基本的坐标转换解释一下。
[备注]
这篇文章只是记录分享下解决问题的过程,找我要demo的,或者问我什么东西怎么做的,就不要加我了。你可以加一个canvas相关的交流群,或者如果需要用到KineticJS/FabricJS的话,可以加群251572039。
一、拖拽中的坐标转换
因为没有旋转,所以不需要考虑角度变化,屏幕坐标系的偏移=canvas坐标系的偏移。
实现思路:绘制图片之前,将canvas坐标系的原点移动到图片的中心点位置。移动的时候,根据鼠标move后在屏幕坐标系的偏移得出图片中心点需要的偏移量,算出新的图片中心点的坐标,再根据新的图片中心点在屏幕坐标系的坐标计算其在canvas坐标系的坐标值P,然后将canvas坐标系的原点ctx.translate到P。
demo中有详细的注释 链接http://youryida.duapp.com/demo_canvas/coor_convert_move.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>
<pre>
功能:拖拽
思路:始终保持图片中心点在canvas坐标系的原点处,图片的每一次重绘都基于canvas坐标系的原点来绘制,即drawImage(img,-imgW/2,-imgH/2)。
移动的时候绘制方法不变,变换的是canvas坐标系。
关键:理解屏幕坐标系和canvas坐标系的关系。将鼠标事件的屏幕坐标,转换为canvas坐标系中的坐标。
</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 isDown=false;
var imgH,imgW;
var moveAble=false;
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};
ctx.translate(PO.x,PO.y);
onDraw();
}
function onDraw(){
ctx.clearRect(-cvsW,-cvsH,2*cvsW,2*cvsH);
ctx.drawImage(img,-imgW/2,-imgH/2);
} cvs.addEventListener("mousedown", startMove, false);
cvs.addEventListener("mousemove", moving, false);
cvs.addEventListener("mouseup", endMove, false);
cvs.addEventListener("mouseout",endMove, false); function imgIsDown(x,y){
return (-imgW/2<=x && x<=imgW/2 && -imgH/2<y && y<=imgH/2);
} function startMove(){
event.preventDefault();
isDown=true;
var loc=getEvtLoc();//获取鼠标事件在屏幕坐标系的位置(原点在canvas左上角)
var x=loc.x,y=loc.y;
var cLoc=convertCoor(loc);
var Xc=cLoc.x,Yc=cLoc.y;
beginX=x,beginY=y;
moveAble=imgIsDown(Xc,Yc);
if (moveAble) cvs.style.cursor="move"; }
function moving(){
event.preventDefault();
if(isDown==false) return;
var loc=getEvtLoc(); if(moveAble){
var x=loc.x,y=loc.y;
var dx=x-beginX,dy=y-beginY;
var mPO={x:PO.x+dx,y:PO.y+dy};//因为鼠标移动dx dy,所以PO在屏幕坐标系的坐标也 移动dx dy
var cPO=convertCoor(mPO);//屏幕坐标系移动后的PO转换成canvas坐标系的坐标
ctx.translate(cPO.x,cPO.y);//canvas坐标系原点移动到新的图片中心点
onDraw(); PO.x=PO.x+dx;//记录下屏幕坐标系上PO的坐标变化
PO.y=PO.y+dy;
beginX=x,beginY=y; //记录移动后鼠标在屏幕坐标系的新位置
}
}
function endMove(){
event.preventDefault();
isDown=false;
moveAble=false;
cvs.style.cursor="auto";
}
function getEvtLoc(){//获取相对canvas标签左上角的鼠标事件坐标
return {x:event.offsetX,y:event.offsetY}
} function convertCoor(P) {//坐标变换 屏幕坐标系的点 转换为canvas新坐标系的点
var x=P.x-PO.x;//在屏幕坐标系中,鼠标位置和新坐标系原点PO的偏移
var y=P.y-PO.y;
return {x:x,y:y};
}
</script>
</body>
</html>
图片拖拽 坐标转换demo
二、拖拽+旋转 中的坐标转换
实现思路:还是上面的思路,要把屏幕坐标系的点都转换成canvas坐标系的点。关于旋转,图片中心点不动,即canvas坐标系原点不动,鼠标摁住旋钮(假设旋钮在图片中心上方)后,图片跟随鼠标进行旋转,需要计算鼠标点在canvas坐标系中的坐标值,并且计算出该点相对canvas坐标系y轴反方向的夹角θ,然后旋转canvas坐标系ctx.rotate(θ);
带有旋转的坐标转换详解:
如左图,鼠标事件中获取到的点(M) 坐标都是基于屏幕的坐标系,即XOY坐标系。
设canvas中经过一些旋转操作之后的canvas坐标系为X'O'Y'。
因为绘图代码是依据canvas中的坐标系进行绘制,所以就需要将屏幕坐标系中点的坐标值转换成canvas坐标系中点的坐标值。
该坐标转换抽象为一道高中几何题就是:
平面内一个直角坐标系XOY,经过平移、顺时针旋转θ角度后形成新的直角坐标系X'O'Y',已知O'在XOY坐标系中的坐标为(Xo,Yo),点M在XOY坐标系中的坐标为(Xm,Ym),求M在X'O'Y'坐标系中的坐标(x',y')。
解:
如左图,从M点对两坐标系的xy轴做垂线并连接O'M,
Δx=Xm-Xo;
Δy=Ym-Yo;
O'M = Math.sqrt(Δx*Δx+Δy*Δy);//勾股定理
Math.atan2(Δy,Δx)=α+β;//M点与X轴的夹角 三角函数对边/临边
β=Math.atan2(Δy,Δx)-θ;//因为θ=α
x'=O'M*Math.cos(β);
y'=O'M*Math.sin(β); //可得M在X'O'Y'坐标系中的坐标(x',y')
over;
demo中有详细的注释 链接http://youryida.duapp.com/demo_canvas/coor_convert_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>
<pre>
功能:拖拽+旋转
思路:始终保持图片中心点在canvas坐标系的原点处,图片的每一次重绘都基于canvas坐标系的原点来绘制,即drawImage(img,-imgW/2,-imgH/2)。
移动、旋转的时候绘制方法不变,变换的是canvas坐标系。
关键:理解屏幕坐标系和canvas坐标系的关系。将鼠标事件的屏幕坐标,转换为canvas坐标系中的坐标。
计算旋转时每一次mousemove,在旋转前的canvas坐标系中move的角度。
</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 Selected_Round_R=12;
var isDown=false;
var imgH,imgW;
var moveAble=false,rotateAble=false;
var img = new Image();
var rotate_radian=0;//canvas坐标系x轴与屏幕坐标系X轴夹角弧度
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};
ctx.translate(PO.x,PO.y);//载入时将canvas坐标系原点移到图片中心点上
onDraw(); }
function onDraw(){
ctx.clearRect(-cvsW,-cvsH,2*cvsW,2*cvsH);
ctx.drawImage(img,-imgW/2,-imgH/2);
//旋转控制旋钮
ctx.beginPath();
ctx.arc(0,-imgH/2-Selected_Round_R,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 imgIsDown(x,y){
return (-imgW/2<=x && x<=imgW/2 && -imgH/2<y && y<=imgH/2);
}
function RTIsDown(x,y){
var round_center={x:0,y:-imgH/2-Selected_Round_R};
var bool=getPointDistance({x:x,y:y},round_center)<=Selected_Round_R;
return bool;
}
function startMove(){
event.preventDefault();
isDown=true;
var loc=getEvtLoc();//获取鼠标事件在屏幕坐标系的位置(原点在canvas标签左上角)
var x=loc.x,y=loc.y;
beginX=x,beginY=y;
var cLoc=convertCoor(loc);
var Xc=cLoc.x,Yc=cLoc.y;
moveAble=imgIsDown(Xc,Yc);
rotateAble=RTIsDown(Xc,Yc);
if (moveAble) cvs.style.cursor="move";
if (rotateAble) cvs.style.cursor="crosshair";
}
function moving(){
event.preventDefault();
if(isDown==false) return;
var loc=getEvtLoc();
if(moveAble){
var x=loc.x,y=loc.y;
var dx=x-beginX,dy=y-beginY;
var mPO={x:PO.x+dx,y:PO.y+dy};//因为鼠标移动dx dy,所以PO在屏幕坐标系的坐标也 移动dx dy
var cPO=convertCoor(mPO);//屏幕坐标系移动后的PO转换成canvas坐标系的坐标
ctx.translate(cPO.x,cPO.y);//canvas坐标系原点移动到新的图片中心点
onDraw(); PO.x=PO.x+dx;//记录下屏幕坐标系上PO的坐标变化
PO.y=PO.y+dy;
beginX=x,beginY=y;//记录移动后鼠标在屏幕坐标系的新位置
}else if(rotateAble){
var cLoc=convertCoor(loc);
var Xc=cLoc.x,Yc=cLoc.y;
var newR = Math.atan2(Xc,-Yc);//在旋转前的canvas坐标系中 move的角度(因为旋钮在上方,所以跟,应该计算 在旋转前canvas坐标系中,鼠标位置和原点连线 与 y轴反方向的夹角)
ctx.rotate(newR);
rotate_radian+=newR;
onDraw();
}
}
function endMove(){
event.preventDefault();
isDown=false;
moveAble=rotateAble=false;
cvs.style.cursor="auto";
} function getEvtLoc(){//获取相对canvas标签左上角的鼠标事件坐标
return {x:event.offsetX,y:event.offsetY}
} function convertCoor(P) {//坐标变换 屏幕坐标系的点 转换为canvas坐标系的点
var x=P.x-PO.x;//在屏幕坐标系中,P点相对canvas坐标系原点PO的偏移
var y=P.y-PO.y; if(rotate_radian!=0){
var len = Math.sqrt(x*x + y*y);
var oldR=Math.atan2(y,x);//屏幕坐标系中 PO与P点连线 与屏幕坐标系X轴的夹角弧度
var newR =oldR-rotate_radian;//canvas坐标系中PO与P点连线 与canvas坐标系x轴的夹角弧度
x = len*Math.cos(newR);
y = len*Math.sin(newR);
} return {x:x,y:y};
}
//获取两点距离
function getPointDistance(a,b){
var x1=a.x,y1=a.y,x2=b.x,y2=b.y;
var dd= Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
return dd;
}
</script>
</body>
</html>
图片拖拽+旋转 坐标转换demo
三、总结
在canvas上绘制的元素比较多的时候,不适合用这种办法进行拖拽旋转,因为时刻变换的坐标系会影响到canvas上的其他元素,增加其他元素绘制的复杂性。
有时间再研究save和restore在以上需求中的应用。
canvas 图片拖拽旋转之一——坐标转换translate的更多相关文章
- canvas 图片拖拽旋转之二——canvas状态保存(save和restore)
引言 在上一篇日志“canvas 图片拖拽旋转之一”中,对坐标转换有了比较深入的了解,但是仅仅利用坐标转换实现的拖拽旋转,会改变canvas坐标系的状态,从而影响画布上其他元素的绘制.因此,这个时候需 ...
- 自制一个H5图片拖拽、裁剪插件(原生JS)
前言 如今的H5运营活动中,有很多都是让用户拍照或者上传图片,然后对照片加滤镜.加贴纸.评颜值之类的.尤其是一些拍照软件公司的运营活动几乎全部都是这样的. 博主也做过不少,为了省事就封装了一个简单的图 ...
- HTML5新特性之Canvas+drag(拖拽图像实现图像反转)
1.什么是canvas 在网页上使用canvas元素时,会创建一块矩形区域,默认矩形区域宽度300px,高度150px.. 页面中加入canvas元素后,可以通过javascript自由控制.可以在其 ...
- vue在移动端使用alloyfinger手势库操作图片拖拽、缩放
最近开发一个活动需要在手机上给上传的头像加上边框.装饰,需要拖拽.手势缩放边框下的头像图片,因为是vue项目,开始尝试了vue-drag-resize这个组件,对图片拖拽支持很完美,但是无法手势缩放, ...
- HTML5图片拖拽预览原理及实现
一.前言 这两天恰好有一位同事问我怎样做一个图片预览功能.作为现代人的我们首先想到的当然是HTML5啦,其实HTML5做图片预览已经是一个老生常谈的问题了.我在这里就简单说说其中相关的一些东西,当然会 ...
- HTML5多图片拖拽上传带进度条
前言 昨天利用css2的clip属性实现了网页进度条觉得还不错,但是很多情况下,我们在那些时候用进度条呢,一般网页加载的时候如果有需要可以用,那么问题就来了,怎么才算整个加载完毕呢,是页面主要模块加载 ...
- Android 仿微信朋友圈发表图片拖拽和删除功能
朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...
- Vue富文本编辑器(图片拖拽缩放)
富文本编辑器(图片拖拽缩放) 需求: 根据业务要求,需要能够上传图片,且上传的图片能在移动端中占满屏幕宽度,故需要能等比缩放上传的图片,还需要能拖拽.缩放.改变图片大小.尝试多个第三方富文本编辑器,很 ...
- CSS 奇思妙想 | 使用 resize 实现强大的图片拖拽切换预览功能
本文将介绍一个非常有意思的功能,使用纯 CSS 利用 resize 实现强大的图片切换预览功能.类似于这样: 思路 首先,要实现这样一个效果如果不要求可以拖拽,其实有非常多的办法. 将两张图片叠加在一 ...
随机推荐
- java并发编程(十七)内存操作总结
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17377197 主内存与工作内存 Java内存模型的主要目标是定义程序中各个变量的访问规则, ...
- 控制TextField的内容长度
参考如下代码(下例是控制设置交易密码,控制6位): - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [ ...
- Map Network Driver
K: \\10.11.80.5\deptfiling-pubgz$ O: \\10.11.80.5\deptfiling-itdgz$
- JQuery中的dialog使用介绍
初始化参数 对于 dialog 来说,首先需要进行初始化,在调用 dialog 函数的时候,如果没有传递参数,或者传递了一个对象,那么就表示在初始化一个对话框. 没有参数,表示按照默认的设置初始化对话 ...
- 安装SQL Server2016正式版
安装SQL Server2016正式版 今天终于有时间安装SQL Server2016正式版,下载那个安装包都用了一个星期 安装包可以从这里下载: http://www.itellyou.cn/ ht ...
- Kinect for Windows SDK开发学习相关资源
Kinect for Windows SDK(K4W)将Kinect的体感操作带到了平常的应用学习中,提供了一种不同于传统的鼠标,键盘及触摸的无接触的交互方式,在某种程度上实现了自然交互界面的理想,即 ...
- SQL Server 权限管理
标签:SQL SERVER/MSSQL SERVER/数据库/DBA/权限控制/管理/分配/登入名/数据库用户/角色 概述 对数据库系统而言,保证数据的安全性永远都是最重要的问题之一.一个好的数据库环 ...
- C# 6.0 功能预览 (一)
一.索引的成员和元素初始化 1.1 原始初始化集合 Dictionary 1.2 键值初始化集合 Dictionary 1.3 运算符 $ 初始化集合 Dictionary 二.自动属性的初始化 一不 ...
- C语言 · 特殊回文数
问题描述 123321是一个非常特殊的数,它从左边读和从右边读是一样的. 输入一个正整数n, 编程求所有这样的五位和六位十进制数,满足各位数字之和等于n . 输入格式 输入一行,包含一个正整数n. 输 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (36) ------ 第六章 继承与建模高级应用之TPC继承映射
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-12 TPC继承映射建模 问题 你有两张或多张架构和数据类似的表,你想使用TP ...