html5 Canvas画图教程27:isPointInPath

本文属于《html5 Canvas画图系列教程

这篇文章写得有点晚了.原因?因为我写着写着就忘了有这一茬还没写….

canvas的isPointInPath是一个非常重要的函数,其作用是判断某一点是否是在某个路径内.其语法如下:

context.isPointInPath(x,y)

其中很明显,x和y就是一个点的坐标了.

canvas有个致命的缺点就是本身缺乏交互性,比如这个问题:

问题1:我画了一个黑圆,想让鼠标经过的时候颜色变红,鼠标移出后又变回来

–很简单的功能吧,用CSS都可以轻松做到,但在canvas中,就是个难事了.

基本用法

这时候isPointInPath就派上了用场.他的用法是,先画出一条路径,然后调用isPointInPath,此时isPointInPath就能判断你传入的这个点是否在这个路径之内.

isPointInPath虽然判断出了点在路径中,但是呢,我们要把原来的黑色变红,就得重绘一下canvas.

然后还有个问题,当鼠标进入形状区域又出来之后,我们又要把红色变成黑色,又得重绘一下canvas–看起来很麻烦是不?

所以代码如下:

HTML代码:

1
2
3
4
5
6
7
<style type="text/css">
  .canvas-box {position: relative;}
  canvas {box-shadow: 0 0 10px rgba(0,0,0,0.2)  }
  </style>
  <div class="canvas-box">    
    <canvas id="cvs" width="400" height="300">不支持canvas</canvas>
  </div>

大家注意我的css里给canvas外面的DIV加了个相对定位,额外提醒一下,这个非常重要,下面我们获取鼠标坐标的时候必须要依靠这个.

JS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var cvs = document.getElementById('cvs');
var ctx = cvs.getContext('2d');
function draw (color) {
  ctx.fillStyle = '#000';
  ctx.beginPath();
  ctx.moveTo(100,100);
  ctx.bezierCurveTo(110,110,199,278,300,379);
  ctx.lineTo(400,100)
  ctx.closePath();
  ctx.fillStyle = color;
  ctx.fill()  
}
draw();
cvs.onmousemove = function (e) {
  var x = e.offsetX, y = e.offsetY;
  if(ctx.isPointInPath(x,y)) {
    console.log('in');
    draw('#f00');
  } else {
    draw()
  }
}

这样一来我们的目的达到了.鼠标一进一出,再一进一出,再一进一出….也不会出错.

但要是细看一下代码,会发现实现这个效果我们付出了太多,首先是给canvas加上了onmousemove事件,这个事件在鼠标移动的时候会一直触发,很浪费资源;然后,鼠标每一次移动,都会执行draw函数,画图形,区别只有填充是黑色还是红色,也就是说我们重绘了太多次.我们投入这么多资源,只实现了如此简单的效果,实在是不甘心.

当然,函数是可以优化的,重绘次数可以减少到进来一次出去一次,但用canvas实现交互的流程就是这样

得到鼠标坐标–判断图形–清空区域–以相同位置不同颜色重绘图形

以后的文章我会讲一些canvas的高级技巧,比如怎么尽可能的不重绘.现在先看下面的问题.

进阶用法

问题2:如果canvas上有10条路径,那怎么判断坐标是在哪一条路径中呢?

答案就是那个最笨的答案:每结束一条路径时,都必须进行判断,此时得出的结果就是针对你当前的这个路径而不是其他路径.

isPointInPath只对当前路径有效!

也就是说你不能画了10条路径后突然跑去判断点是不是在第1条路径之中.

为什么要这样呢?因为canvas中路径是不能保存的,不能像一个变量那样保留起来供后面再用.这样一来你想要重现这个路径的话,惟一的办法就是:按照之前的值,之前的函数,重新画一遍.

所以我把上面的代码改一下,画出两个黑色的图形,然后都实现hover变色的效果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var cvs = document.getElementById('cvs');
var ctx = cvs.getContext('2d');
function draw () {
  ctx.fillStyle = '#000';
  ctx.beginPath();
  ctx.moveTo(100,100);
  ctx.bezierCurveTo(110,10,199,278,300,379);
  ctx.lineTo(400,100)
  ctx.closePath();
}
function circle () {
  ctx.fillStyle = '#000';
  ctx.beginPath();
  ctx.arc(100,200,50,0,Math.PI*2)
  ctx.closePath();
}
draw();
ctx.fill()
circle();
ctx.fill()
var fns = [draw,circle];
cvs.onmousemove = function (e) {
  var x = e.offsetX, y = e.offsetY;
  ctx.clearRect(0,0,400,300)
  for(var i = fns.length;i--;) {
    fns[i]();
    if(ctx.isPointInPath(x,y)) {
      ctx.fillStyle = "#f00"
    } else {
      ctx.fillStyle = "#000"
    }
    ctx.fill()
  }
}

虽然只增加了一个图形,但代码却大变样.先是把两个画图形的函数变成了只画路径,因为我们要判断下isPointInPath再决定用什么颜色填充;

然后用一个数组把函数都保留起来,以便循环调用;

然后像前面说的,每画出一条路径,就判断一下isPointInPath,看看当前是不是在图形中.

查看完整示例页

现在就可以很明显地看到每次mousemove都要清空canvas并重绘,这让我觉得很不爽,要是有100个图形,我相信判断起来效率好不到哪里去.

如上所述,10个图形的实现和2个的就差别不大了,大家自行实验吧.

效率问题以后再说

2 评论

发表评论

电子邮件地址不会被公开。