怎么获取 canvas 上鼠标的坐标

怎么获取 canvas 上鼠标的坐标,第1张

以下程序实现了在canvas上画红色的圆,圆心为鼠标所在位置,其中圆的位置随着鼠标位置的移动而移动,js代码中mousePos(e)方法用于获取鼠标在整个页面的坐标,getCanvasPos(canvas,e)方法用于获取鼠标在canvas上的坐标;canvas以其左上角为起点,并设为(0,0),因此当页面包含其他元素的时候,两者坐标不一致,不过在本例中两者坐标是一致的,因为页面只包含一个canvas元素:
html代码如下:
[html] view plain copy
<html>
<head></head>
<body>
<div onmousemove="draw(event)" id="testcanvas">
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #c3c3c3;" >
Your browser does not support the canvas element
</canvas>
<script type="text/javascript" src="abcjs">
</script>
</div>
</body>
</html>
其中abcjs文件中的代码如下:
[javascript] view plain copy
function mousePos(e)
{//获取鼠标所在位置的坐标,相对于整个页面
var x,y;
var e = e||windowevent;
return {
x:eclientX+documentbodyscrollLeft + documentdocumentElementscrollLeft,
y:eclientY+documentbodyscrollTop + documentdocumentElementscrollTop
};
}

function getCanvasPos(canvas,e)
{//获取鼠标在canvas上的坐标
var rect = canvasgetBoundingClientRect();
return {
x: eclientX - rectleft (canvaswidth / rectwidth),
y: eclientY - recttop (canvasheight / rectheight)
};
}
function draw(e)
{
var c=documentgetElementById("myCanvas");
var cxt=cgetContext("2d");
cxtclearRect(0,0,cwidth,cheight);
cxtfillStyle="#FF0000";
cxtbeginPath();
//cxtarc(mousePos(e)x,mousePos(e)y,15,0,MathPI2,true);
cxtarc(getCanvasPos(c,e)x,getCanvasPos(c,e)y,15,0,MathPI2,true);
cxtclosePath();
cxtfill();
}

HTML5 的标准已经出来好久了,但是似乎其中的 Canvas 现在并没有在太多的地方用到。一个很重要的原因是,Canvas 的标准还没有完全确定,不适合大规模用在生产环境。但是,Canvas 的优点也是很明显的,例如在绘制含有大量元素的图表的时候,SVG 往往因为性能问题而无法胜任,例如我见过的一次技术分享会的抽奖环节,虽然效果比较炫,但因为每个头像都是 DOM,利用 CSS3 控制的动画,导致了性能非常低下。此外,随着硬件性能的提高,视频截图、图像处理等功能也逐渐可以在网页上实现了,大多数网站用的是 Flash,但是 Flash 在 Mac 电脑上性能不高,还需要学一些额外的知识。Canvas 则是直接使用 JavaScript 来进行绘图,对 Mac 友好,所以不失为 Flash 的一个继承者。
使用 Canvas
说了这么多,Canvas 究竟是个啥?
英文中 Canvas 的意思是“画布”,不过这里说的 Canvas 是 HTML5 中新出的一个元素,开发者可以在上面绘制一系列图形。Canvas 在 HTML 文件中的写法很简单:
<canvas id="canvas" width="宽度" height="高度"></canvas>
其中 id 属性是所有 HTML 元素都可以用的,Canvas 自带的属性只有后面两个(分别控制宽度、高度),没有其它的了。至于兼容性,CanIUse 上面写了,基础的功能目前用户使用的 90% 的浏览器都支持,所以大部分情况下还是可以放心使用的。
注意,一定要使用 Canvas 自带的 width 和 height 属性,不要使用 CSS 来控制,因为 CSS 控制会导致 Canvas 变形。可以试着与 PhptpShop 对比一下,后者是改变“图像大小”,前者才是正确的改变“画布大小”。例如下图是三张的横向拼接:最左边的黑框中是大小为 50px 50px 的原图;中间是改变了图像大小为 100px 100px 的效果,图像变得模糊,但是对于图像本身来说坐标范围并没有变大;最右边才是正确的 100px 100px 的 Canvas。
Canvas 绝大部分的绘图方法都与 <canvas> 标签无关,需要使用 JavaScript 对其进行 *** 作,这就是所谓的 Canvas API。
我们首先获取到这个元素:
var canvas = documentgetElementById('canvas');
然后通过一个方法来获取可以调用一切 Canvas API 的入口:
var ctx = canvasgetContext('2d');
看到 2d 是不是很激动地联想到有没有 3d 呢?没有 3d 的写法,不过如果想要开启 3D 世界的大门,则可以写 canvasgetContext('webgl')。然而 WebGL 是基于 OpenGL ES 20 的一套标准,与本文是彻彻底底的两条路,因此这里就不讨论了。
Canvas 中的基本概念
坐标
与数学上常见的笛卡尔坐标系不太相同,Canvas 的坐标系是计算机中常见的坐标系,它长这样:
画布的最左上角是 (0,0),往右 x 增大,往下 y 增大,而且 x 和 y 都是整数(就算在计算过程中不是整数,在绘制的时候也会当作整数处理),单位是像素。
绘图
带大家怀旧一下。不知道有多少同学小时候玩过 logo 语言,在里面你可以控制一只小海龟在一块板子上行走、画画、提笔、落笔。Canvas 中也一样,你需要控制一只画笔的移动和绘制。然而 Canvas 更高级一些,你可以直接利用一些函数来画图,不用去控制那只画笔的位置。
Canvas 中的基本图形
通过上文定义的 ctx 变量可以干许多有意思的事情,我们先看看如何绘制一些基本图形。
线条
我们指定画笔移动到某一点,然后告诉画笔需要从当前这一点画到另一点。我们可以让画笔多次移动、绘制,最后统一输出到屏幕上。例子如下:
ctxmoveTo(10, 10);
ctxlineTo(150, 50);
ctxlineTo(10, 50);
ctxmoveTo(10, 20);
ctxlineTo(40, 70);
ctxstroke();
上面的代码中,lineTo 是产生线条用的函数,执行完之后画笔就移到了线条的终点。需要注意的是,线条此时并没有显示在屏幕上,必须调用 stroke 才会显示。这样设计是有道理的,因为向屏幕上输出内容需要耗费大量的资源,我们完全可以先攒够一波 lineTo,最后用 stroke 放一个大的。
路径
绘制路径非常简单,只需要先告诉 ctx 一声“我要开始画路径了”,然后通过各种方法(例如 lineTo)绘制路径。如果需要画一个封闭路径,那就最后告诉 ctx一声:“我画完了,你把它封闭起来吧。”当然,不要忘记利用 stroke 输出到屏幕上。
一个简单的例子:
ctxbeginPath();
ctxmoveTo(10, 10);
ctxlineTo(150, 50);
ctxlineTo(10, 50);
ctxclosePath();
ctxstroke();
如果我不想只描绘路径线条,而是想填充整个路径呢?可以将最后一行的 stroke 改成 fill,这样就跟使用了画图中的油漆桶一样,封闭路径里面的内容就都被填充上颜色了:
ctxfill();
弧 / 圆形
绘制弧的函数参数比较多:
ctxarc(圆心 x 坐标, 圆心 y 坐标, 半径, 起始角度, 终止角度, 是否为逆时针);
注意,在 Canvas 的坐标系中,角的一边是以圆心为中心的水平向右的直线。角度单位均为弧度。例如下图,确定了圆心、起始角度(图中标明的锐角)和终止角度(图中标明的钝角),方向为逆时针,于是就有了这么一个弧。如果方向为顺时针,那么就会是一个跟它互补的、非常非常大的弧……
所以如果转了 2π 圈之后,弧就成了圆形,因此也可以使用绘制弧的方式来绘制圆形:
ctxbeginPath();
ctxarc(圆心 x 坐标, 圆心 y 坐标, 半径, 0, MathPI 2, true);
ctxclosePath();
最后一个参数随便填(当然也可以不填),因为不管是顺时针还是逆时针,转了 2π 圈之后都是一个圆。
矩形
如果只是想绘制一个横平竖直的矩形,可以使用下面的两个方法:
// 只描边
ctxstrokeRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);
// 只填充
ctxfillRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);
线条样式 / 填充样式
之前绘制的所有图形都是黑色的,但是 Canvas 肯定不止这么一种颜色(不然标准的制定者会被喷的很惨)。事实上,Canvas 可以单独设置线条样式和填充样式,分别使用的是 strokeStyle 和 fillStyle。可能的值有三种:纯色、渐变、图像。既然线条样式与填充样式的使用方法相同,那么下面统一以填充样式为例。如果想设置线条样式,直接将所有的 fillStyle改成 strokeStyle 即可,里面的参数都不变。
/ 纯色填充 /
// 普通的颜色
ctxfillStyle = '#0000ff';
// 带有透明度的颜色
ctxfillStyle = 'rgba(64, 0, 127, 05)';
/ 渐变填充 /
// 设置渐变的尺寸(参数分别为起始点的 x 和 y、终止点的 x 和 y)
var gradient = ctxcreateLinearGradient(0, 0, 170, 0);
// 设置过渡色,第一个参数是渐变的位置,第二个参数是颜色
gradientaddColorStop(0, 'magenta');
gradientaddColorStop(05, 'blue');
gradientaddColorStop(10, 'red');
// 设置填充样式
ctxfillStyle = gradient;
/ 填充 /
// 创建
var image = new Image;
imagesrc = '/path/to/imagepng';
// 创建笔触,可以指定的平铺方式,这里是横向平铺
var pattern = ctxcreatePattern(image, 'repeat-x');
// 设置笔触填充
ctxfillStyle = pattern;
关于渐变,除了代码中提到的线性渐变以外,还有 createRadialGradient,也就是径向渐变。
设置完填充样式之后,就可以使用 fill 来填充啦!如果设置的是线条样式,那么就可以使用 stroke 来描边。
当然,对于线条样式,还有个额外的方法叫 lineWidth 可以用来控制线条的宽度。
文字
要想在画布上画文字,首先需要知道所使用的字体和字号:
ctxfont = '30px Verdana';
然后就可以通过 strokeText 或者 fillText 来对字体描边或者填充字体。
ctxstrokeText("Hello Coding!", 23, 33);
ctxfillText("Hello Coding!", 23, 66);

在 Canvas 中绘制有三种方法:
// 指定绘制位置
ctxdrawImage(image, x, y);
// 指定绘制位置和图像宽高
ctxdrawImage(image, x, y, width, height);
// 指定剪裁区域、绘制位置和图像宽高
ctxdrawImage(image, sx, sy, swidth, sheight, x, y, width, height);
参数的含义依次如下:
image: 要使用的 Image、Canvas 或 Video
sx: 可选,开始剪切的 x 坐标
sy: 可选,开始剪切的 y 坐标
swidth: 可选,被剪切图像的宽度
sheight: 可选,被剪切图像的高度
x: 在画布上放置图像的 x 坐标
y: 在画布上放置图像的 y 坐标
width: 可选,要使用的图像的宽度
height: 可选,要使用的图像的高度
画布设置
细心的同学可能会发现,刚才有些属性是直接对 ctx 变量做设置,例如 ctxlineWidth,只要设置了它,那么后续画出来的线条全都是这么个宽度。
其实,Canvas 的设置项还有许多,例如我们可以直接移动画布、旋转画布、设置全局的绘制透明度等等。这些设置还可以随时保存和恢复。
要注意的一点是,所有已经画在画布上的东西,是已经定死了的,不管之后再次进行任何设置都不会再改变。这个很像 Windows 下的画图程序。
废话不多说,直接上代码:
// 移动画布,其实就是移动坐标系
ctxtranslate(往右移动的量, 往下移动的量);
// 旋转画布,旋转中心为坐标系原点
ctxrotate(顺时针旋转的角度);
// 以坐标系原点为中心缩放画布
ctxscale(横向放大倍数, 纵向放大倍数);
// 设置绘制透明度,如果 fillStyle 等属性设置了透明度则会叠加
ctxglobalAlpha(零到一的小数);
// 设置全局组合 *** 作
ctxglobalCompositeOperation = 'lighter';
// 保存当前设置
ctxsave();
// 恢复上次保存的设置
ctxrestore();
移动、旋转、缩放其实就是在控制绘图的坐标系,如果你在调用这三个方法的时候,脑子里时刻有一个带刻度的坐标系,效果会非常好。
事实上,Canvas 的坐标变换遵循计算机图形学的知识:变换矩阵。简单来说,一个坐标可以看成是一个矩阵,坐标所对应的矩阵乘上变换矩阵就可以实现对坐标的变换。为了提升计算的效率,可以先计算出几种变换复合之后的变换矩阵,然后直接通过 transform 函数对当前坐标系进行变换,或者通过 setTransform 函数将坐标系重置为初始状态后再进行变换。至于变换矩阵的内容,对于本文来说就有些超纲了。
全局组合 *** 作有点像 PhotoShop 里面的“混合选项”,具体的实现方式还没有完全确定,目前常见浏览器都统一了的实现方式有:source-over、source-atop、destination-over、destination-out、lighter、xor。具体的行为可以看 Mozilla 官方文档,但是由于标准还未完全确定,因此其它浏览器不保证所有的行为都跟 Mozilla 的标准一致。一般来说,比较常见的是 source-over 和 lighter 两种,这两种的标准在浏览器界也算是无可争议的。
至于保存和恢复设置就有点好玩了,首先需要了解一个叫“栈”的东西。
栈是一个一维数组,规定只能从一个方向 *** 作。栈一开始是空的,我们可以从这个方向往数组 push 元素,也只能从这个方向把最后一个元素(栈顶元素)pop 出来,除此以外没有任何多余的 *** 作。当然,pop 的次数不能多于 push 的次数,因为 pop 到栈底的时候栈里就已经没有元素了,此时再 pop 是没有意义的。栈的用处有很多,例如括号匹配、表达式求值、深度优先搜索,甚至绝大部分语言的函数调用都要用到栈。
每次我们调用 save 函数,实际上是将当前的全局设置 push 到了一个专门栈上,每次调用 restore 函数的时候将最后一次保存的内容 pop 出来并用它覆盖当前的全局设置,这样栈顶就是最近一次保存的内容了。保存和恢复在某些情况下很好用,例如我需要画一个歪着的图形,然后继续画正着的图形,这样就可以先调用 save,然后调用 rotate,画完图形之后再 restore 回来,继续画其它的图形。
其实 Canvas 还有许多方法,例如 toDataURL 直接将当前画布上的内容转换为十六进制的 data-url,getImageData直接将图像转换为 RGBA 数组以供图像处理算法使用,putImageData 将 RGBA 数组转换为显示在画布上等等。如果配上 JavaScript 的定时更新(最好用 requestAnimationFrame 而不是 setInterval),则可以产生动画效果。网上还有许多 Canvas 的库,可以让程序员更简便地基于 Canvas 编写属于自己的特效或功能。在这儿我想说一句话:大家的脑洞有多大,Canvas 的能力就有多强~

最近在制作公司的一个项目时有运用到 canvas 与屏幕坐标互相转换的一个知识点,如果没有进行 canvas 缩放 、 旋转 、 平移 一系列 *** 作时,这个需求还是比较简单的,但是如果 *** 作复杂起来后,要再进行转换,在不知道一些特定api就显得比较困难了,在查阅了了一些资料后解决了这个需求这里分享给大家。

解决这个需求时引用了两个API,分别是 CanvasRenderingContext2DgetTransform() 、
DOMMatrixinvertSelf()
CanvasRenderingContext2DgetTransform() 可以获取当前 canvas 上下文的被应用的转换矩阵,返回 DOMMatrix 对象。

DOMMatrixinvertSelf() 可以获取当前矩阵逆转后的结果,注意这个api是会修改自身矩阵的。

我们通过这两个API可以获取当前canvas的 DOMMatrix ,然后将其逆转,通过逆转后的转换矩阵就可以获取屏幕坐标转对应的canvas坐标了。

CanvasRenderingContext2DgetTransform()
DOMMatrixinvertSelf()

可以看到兼容性还是比较良好的,如果没有要求兼容IE就放心大胆的用吧。

原因是UGUI做的d幕功能,d幕从一侧飞入,从另一侧飞出,飞入伴随着淡入效果,飞出伴随淡出效果。
1、Canvas中的各元素的网格顶点在Canvas合批绘制时被统一变换到Canvas的局部坐标系中。
2、淡入淡出是通过计算某个显示位置与渐变区间的关系,计算透明度。从第一条可知渐变区间也定义在Canvas局部坐标系。
3、最好在像素着色器中处理。UI元素的网格顶点较少而且非均匀分布,如果在顶点着色器中计算透明度渐变,当元素尺寸比渐变区间大一些时,像素着色器中插值得到的透明度渐变将不符合渐变区间。
4、如果对TextMeshPro的文本也做渐变,TextMeshPro的共享材质可以通过将Inspector面板设为Debug模式看到。

DOM是Web前端领域非常重要的组成部分,不仅在处理HTML元素时会用到DOM,图形编程也同样会用到。比如SVG绘图,各种图形都是以DOM节点的形式插入到页面中,这就意味着可以使用DOM方法对图形进行 *** 作。比如有一个 元素,可以直接用jquery增加click事件$('#p1')click(function(){…})"。然而这种DOM处理方法在HTML5的Canvas里不再适用,Canvas使用的是另外一套机制,无论在Canvas上绘制多少图形,Canvas都是一个整体,图形本身实际都是Canvas的一部分,不可单独获取,所以也就无法直接给某个图形增加JavaScript事件。

Canvas的限制

在Canvas里,所有图形都绘制在帧上,绘制方法不会将绘制好的图形元素作为一个返回值输出,js也无法获取到已经绘制好的图形元素。比如:

代码如下:

cvs = documentgetElementById('mycanvas');

ctx = canvasgetContext('2d');

theRect = ctxrect(10, 10, 100, 100);

ctxstroke();

consolelog(theRect); //undefined

这段代码在canvas标签里绘制了一个矩形,首先可以看到绘制图形的rect方法没有返回值。如果打开浏览器的开发者工具,还可以看到canvas标签内部没有增加任何内容,而在js里获取到的canvas元素以及当前的上下文,也都没有任何表示新增图形的内容。

所以,前端常用的dom方法在canvas里是不适用的。比如点击上面Canvas里的矩形,实际点击的是整个Canvas元素。

给Canvas元素绑定事件

由于事件只能达到Canvas元素这一层,所以,如果想进一步深入,识别点击发生在Canvas内部的`哪一个图形上,就需要增加代码来进行处理。基本思路是:给Canvas元素绑定事件,当事件发生时,检查事件对象的位置,然后检查哪些图形覆盖了该位置。比如上面的例子里画过一个矩形,该矩形覆盖x轴10-110、y轴10-110的范围。只要鼠标点击在这个范围里,就可以视为点击了该矩形,也就可以手动触发矩形需要处理的点击事件。思路其实比较简单,但是实现起来还是稍微有点复杂。不仅要考虑这个判断过程的效率,有些地方还需要重新判断事件类型,设置要重新定义一个Canvas内部的捕获和冒泡机制。

首先要做的,是给Canvas元素绑定事件,比如Canvas内部某个图形要绑定点击事件,就需要通过Canvas元素代理该事件:

代码如下:

cvs = documentgetElementById('mycanvas');

cvsaddEventListener('click', function(e){

//

}, false);

接下来需要判断事件对象发生的位置,事件对象e的layerX和layerY属性表示Canvas内部坐标系中的坐标。但是这个属性Opera不支持,Safari也打算移除,所以要做一些兼容写法:

代码如下:

function getEventPosition(ev){

var x, y;

if (evlayerX || evlayerX == 0) {

x = evlayerX;

y = evlayerY;

} else if (evoffsetX || evoffsetX == 0) { // Opera

x = evoffsetX;

y = evoffsetY;

}

return {x: x, y: y};

}

//注:使用上面这个函数,需要给Canvas元素的position设为absolute。

现在有了事件对象的坐标位置,下面就要判断Canvas里的图形,有哪些覆盖了这个坐标。

isPointInPath方法

Canvas的isPointInPath方法可以判断当前上下文的图形是否覆盖了某个坐标,比如:

代码如下:

cvs = documentgetElementById('mycanvas');

ctx = canvasgetContext('2d');

ctxrect(10, 10, 100, 100);

ctxstroke();

ctxisPointInPath(50, 50); //true

ctxisPointInPath(5, 5); //false

接下来增加一个事件判断,就可以判断一个点击事件是否发生在矩形上:

代码如下:

cvsaddEventListener('click', function(e){

p = getEventPosition(e);

if(ctxisPointInPath(px, py)){

//点击了矩形

}

}, false);

以上就是处理Canvas事件的基本方法,但是上面的代码还有局限,由于isPointInPath方法仅判断当前上下文环境中的路径,所以当Canvas里已经绘制了多个图形时,仅能以最后一个图形的上下文环境来判断事件,比如:

代码如下:


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/10408226.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-08
下一篇2023-05-08

发表评论

登录后才能评论

评论列表(0条)

    保存