最近在看《HTML5...案例》,看到一个刮奖,便参照其写了一下, 并做了一些改变
图片使用的是书中的的图片素材

一、需求

实现刮奖的效果,在PC和移动端都能刮奖(PC使用鼠标操作刮奖)。

书中的代码主要是为了讲解移动端的touch操作,所以我在想是否能够在PC端使用鼠标进行刮奖,实现同样的效果。

1.png

二、关键点

1、使用一个 canvas 实现刮奖的效果

2、使用一张刮奖结果图(如上面的恭喜您获奖的图片)做 canvas 的背景

    <div class="container">
        <h1>刮奖</h1>
        <div id="canvas">
            <canvas id="mask" width="240" height="65"></canvas>
        </div>
        <h3 id="result"></h3>
    </div>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            width: 100%;
            height: 100%;
            background: #dfdfdf;
            text-align: center;
        }

        .container h1 {
            padding-top: 50px;
        }
        .container #result {
            padding-top: 50px;
            color:red;
        }
        #canvas {
            width: 240px;
            height: 65px;
            background: url('result.png');
            background-size: 100% 100%;
            margin: 0 auto;
            margin-top: 50px;
        }

3、将 canvas 画满一个遮罩层

2.png

    var canvas = document.getElementById('mask');
    var context = canvas.getContext("2d");
    context.fillStyle = "#d1d1d1";
    context.fillRect(0, 0, 240, 65);

4、启用 globalCompositeOperation

说实话,我之前都没见过和使用过这个属性。

它可以进行遮盖,遮盖的方式有12中,具体地址:

context.globalCompositeOperation = 'destination-out';

使用 destination-out 能够把之前画过的这部分内容变成透明:

3.png

5、画圆时透明遮罩层

启用了globalCompositeOperation= = 'destination-out' 之后,在 canvs 上绘制的新的图形,就会使得之前的 mask 变成透明的,从而显示出背景图片来。

为了比较好看的效果,会使用画圆的方式来进行。

而画圆需要有一个圆心,至于半径可以定死就可以,比如下面的画圆方式:

需要拿到触发事件的点的横纵坐标(pageX和pageY),然后根据 canvas 的offsetLeft和 canvas 的offsetTop计算需要画的圆的点.
需要减去 canvas 的 offsetLeft 和 offsetTop 是因为画圆的(X,Y是 canvas 的坐标,pageXpageY是页面上鼠标的坐标

    // 根据某个点在canvas上画圆
    // x 坐标和 y 坐标 两个坐标是触摸点的坐标而不是画圆的圆心
    // 圆心通过计算得出
    function drawArcByPoint(x, y) {
        context.beginPath();
        context.arc(x - canvas.offsetLeft, y - canvas.offsetTop, 20, 0, Math.PI * 2);
        context.closePath();
        context.fillStyle = '#dddddd';
        context.fill();
        checkComplete();
    }

这样每画一个圆,画布上就出现一个圆形的透明区域,显示背景图片。

6、 判断是否完成刮奖

刮奖过程中需要进行判断是否已经完成刮奖,从而实现某些业务逻辑。

下面的方法也是刘欢书中的代码。不过我使用了 80% 的达成条件。

基本的思想是:

上面提到了,启用globalCompositeOperation= = 'destination-out'之后,画的圆使得这个圆是透明的,根据这个条件可以进行判断有多少像素是透明的,就代表多少像素已经被刮了,当像素比例达到80%的时候,就认为刮奖结束。

canvas 可以通过getImageData转 base64 的图片,同时通过图片的data能够拿到像素的字节数据。

比较重要的点是:

拿到的像素字节数据是每四个长度表示一个像素,分别表示rgba。也就是说,下面代码中pxData[0]表示第一个像素的r,pxData[1]表示第一个像素的g,pxData[2]表示第一个像素的b,pxData[3]表示第一个像素的a,

因此下面代码中的循环是使用了 i += 4作为每次的step,同时获取的是 pxData[i+3],拿到的是alpha的数值

拿到 alpha 之后就能够通过 < 10 来判断是否是透明的

最后计算有多少透明的,如果这个比例高于 80% 则说明刮奖结束

    // 判断是否完成刮奖 点数大于80%
    function checkComplete() {
        var imgData = context.getImageData(0, 0, 240, 65);
        var pxData = imgData.data;// 获取字节数据
        var len = pxData.length; // 获取字节长度
        var count = 0; // 记录透明点的个数
        // 主要的思想是 一个像素由四个数据组成,每个数据分别是 rgba() 所以第四个数据 a 表示alpha透明度
        for (var i = 0; i < len; i += 4) {
            var alpha = pxData[i + 3]; // 获取每个像素的透明度
            if (alpha < 10) {
                // 透明度小于10 
                count++;
            }
        }
        var percent = count / (len / 4);// 计算百分比
        // 如果百分比大于0.8 则表示成功
        if (percent >= 0.8) { 
            showResult();
        }
    }

三、事件监听

基本的布局做好了,然后就是进行事件的监听.

移动端比较简单,书中主要是移动端使用了 touchmove,为了扩展到PC端网站,使用mousemove

PC上需要关注的问题是:

  1. mousedown的时候使用 addEventListener 给canvas增加一个 mousemove事件
  2. mouseup的时候使用removeEventListener移除mousemove

因此我将 handle 抽出来做了一个function,这样子能够方便的使用removeEventListener

    // 鼠标按下 增加mousemove的事件监听
    canvas.addEventListener('mousedown', drawArcMouseHandle);
    canvas.addEventListener('mouseup', function (event) {
        // 鼠标抬起之后,把mousemove的事件监听撤销掉
        this.removeEventListener('mousemove', mousemoveHandle);
    });
    // 根据鼠标的move画圆
    function drawArcMouseHandle(event) {
        event.preventDefault();
        event.target.addEventListener("mousemove", mousemoveHandle);
    }
    // 为了能够移除movesemove的事件需要单独处理一下
    function mousemoveHandle(event) {
        event.preventDefault();
        drawArcByPoint(event.pageX, event.pageY);
    }
    // 监听 touchmove
    canvas.addEventListener('touchmove', drawArcTouchHandle);
    // 根据触摸点画圆 
    function drawArcTouchHandle(event) {
        event.preventDefault();
        var touch = event.touches[0];
        drawArcByPoint(touch.pageX, touch.pageY);
    }

四、效果