接 PC端&移动端:canvas三种方式实现刮刮乐(上)

JS部分

因为我个人想多多练习使用对象,所有这块结构可能并不是最简便的写法。

1
2
3
4
5
6
7
8
9
10
11
12
function scratcher() {
return this.init();
}

scratcher.prototype = {
init: function() {
this.canvas = document.getElementById('canvas');
this.ctx = this.canvas.getContext('2d');

return this;
},
}

创建了各scratcher对象封装所有变量和方法,最后只需new一下实例就可以展示效果。init作为入口,首先把canvas和画布对象存储下来供后面的方法调用。

涂层绘制

接下来绘制刮刮乐的中奖图片上面的涂层,我使用的是最常见的灰色#ccc,绘制一个长方形,宽高和canvas相同这样正好全部盖中奖图。如下:

1
2
3
4
draw: function() {
this.ctx.fillStyle = "#ccc";
this.ctx.fillRect(0, 0, canvas.width, canvas.height);
},

别忘了把这个方法放到init中执行。

事件绑定

然后写涂抹功能scratchHandle,这部分最后再说,先假设已经有一个scratchHandle。那么接下来我们将功能事件绑定到鼠标/触摸动作上去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bind: function() {
var self = this;

this.canvas.onmousedown = function(){
self.canvas.onmousemove = self.scratchHandle.bind(self);
}

this.canvas.onmouseup = function() {
self.canvas.onmousemove = null;
self.flag = true;
}

this.canvas.addEventListener('touchmove', self.scratchHandle.bind(self), false);

this.canvas.addEventListener('touchend', function(){
self.canvas.removeEventListener('touchmove', self.scratchHandle.bind(self), false);
self.flag = true;
}, false);
}

上面两段绑定的鼠标动作,下面两段绑定的手指触摸动作,记得对应绑定也要解绑,不然就会一直涂抹下去。
bind事件也要加到init中。

关于flag接下来会提到。

涂抹功能

现在就只剩最核心的涂抹功能了,基本原理就是获得鼠标/触摸点的位置(相对canvas),然后设置图形大小,将涂层抹没。

这里有三种方式,先看代码:

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
35
scratchHandle: function(e) {
var self = this,
evt = e || window.event,
canvas_location = this.canvas.getBoundingClientRect(),
client_x = evt.clientX || evt.touches[0].clientX,
client_y = evt.clientY || evt.touches[0].clientY,
x = client_x - canvas_location.left,
y = client_y - canvas_location.top;

//方式一
// ctx.clearRect(x-25, y-25, 50, 50);

//方式二
// ctx.globalCompositeOperation = 'destination-out';
// ctx.beginPath();
// ctx.arc(x, y, 25, 0, Math.PI*2, false);
// ctx.fillStyle = 'rgba(0, 0, 0, 1)';
// ctx.fill();

//方式三
this.ctx.globalCompositeOperation = 'destination-out';
this.ctx.lineWidth = 40;
this.ctx.lineCap = "round";
this.ctx.strokeStyle = 'rgba(0, 0, 0, 1)';

if(this.flag) {
self.ctx.beginPath();
self.ctx.moveTo(x, y);

self.flag = false;
}

this.ctx.lineTo(x, y);
this.ctx.stroke();
},

和常用的方法一样,用鼠标/触摸点所在位置的坐标减去canvas左上角左边即为鼠标/触摸点相对canvas的坐标。

获取canvas坐标
不同的是,canvas并不能像div之类的通过offset取坐标值,而需要通过getBoundingClientRect()去取

获取鼠标/触摸点坐标
evt.touches[0]是对触摸点坐标值的获取

三种涂抹方式
第一种用的是clearRect方法,可以像橡皮擦一样擦除一定区域的图像,参数分别是橡皮擦的右上角坐标和宽高。缺点就是需要手动模拟中心点,而且形状只能是方形的,还原度不好。

第二种是在涂层上继续绘画,开始想的是设置颜色为透明色,显然只是这样是无法擦出的,后来查到globalCompositeOperation,这个属性作用是设置重叠区域的效果,有很多属性值可以选择,使用destination-out就可以将重叠区域“消除”。然后绘制圆形、填充即可。这种方式的明显缺点就是当鼠标移动快的时候会断开,不连续,看起来就是一堆小圆点点。

第三种是比较好的一种,同样是继续绘画,但画的是线。把线设置粗一些,边缘设置成圆角便可获得比较好的效果。为了然线条正常的连接/分开,需要一个flag标识当前是不是线的起始点,什么时候用moveTo什么时候用lineTo都需要在此区分。这也是上面出现flag的原因,另外需要flag有一个默认值,跟canvas一起声明即可。

所以最后的init长这样

1
2
3
4
5
6
7
8
9
10
init: function() {
this.canvas = document.getElementById('canvas');
this.ctx = this.canvas.getContext('2d');
this.flag = true;

this.draw();
this.bind();

return this;
},

全部代码可以到文章开始提到的连接去看,再放一下把连接在此么么哒