canvas实践——绘制刮刮卡

canvas实践——绘制刮刮卡

刮刮卡是一种常见的效果,如下图所示:

这里写图片描述

上述效果可以通过canvas中图像合成来实现,具体思路和代码如下:

1、利用css将刮刮卡的结果与canvas灰色涂层重叠;

  • html
<div class="card-body">
    <!-- 灰色涂层 -->
    <canvas class="canvas">sorry, 浏览器不支持canvas</canvas>
    <!-- 刮卡结果 -->
    <div class="gift">谢谢参与</div>
</div>
  • css
.card-body{
    position: relative;
    width: 340px;
    height: 160px;
    margin: 0 auto;
}
.canvas{
    /* 利用绝对定位让灰色涂层与刮卡结果重叠 */
    position: absolute; 
    top: 0;
    left: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
}
.gift{
    /* 利用绝对定位让灰色涂层与刮卡结果重叠 */
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    font-size: 32px;
    text-align: center;
    line-height: 160px;
    background-color: #fcfcfc;
}

2、在canvas画布上绘制刮刮卡的灰色涂层;

// 获取canvas并设置其宽高
let canvas = document.querySelector('.canvas'),
    card = document.querySelector('.card-body');
    canvas.width = card.offsetWidth;
    canvas.height = card.offsetHeight;

let ctx = canvas.getContext('2d');

// 绘制涂层
function drawCanvas() {
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = '#e5e5e5';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'red';
    ctx.font = '24px "微软雅黑"';
    ctx.fillText('请开始刮奖', canvas.width/2 - 64, canvas.height/2 + 4);
    ctx.closePath();
    ctx.restore();
}

drawCanvas()

3、在canvas画布区域监听鼠标事件或触摸事件;

// 监听鼠标和touch事件
canvas.addEventListener('mousedown', moveStart, false);
canvas.addEventListener('touchstart', moveStart, false);
canvas.addEventListener('mousemove', move, false);
canvas.addEventListener('touchmove', move, false);
canvas.addEventListener('mouseup', moveEnd, false);
canvas.addEventListener('touchend', moveEnd, false);

4、在鼠标移动过程中,实时计算鼠标在画板中的位置以及移动路径,并在鼠标移动的位置处绘制用于挂卡的“刷子”图案,利用canvasglobalCompositeOperation方法中的destination-out效果将刮过的地方设置为透明从而实现刮卡的效果;

// 获取鼠标或者手指当前点的位置
function getPointPosition(e, canvas) {

    let mx,
        my,
        offsetX = 0,
        offsetY = 0;
    if(canvas.offsetParent !== null){
        // 由于canvas刚开始是绝对定位,其offsetLeft和offsetTop为0,需要不断向上寻找其父级,
        // 计算父级的offsetLeft和offsetTop,直到向上寻找到body元素,因为body的offsetParent为null
        do{
            offsetX += canvas.offsetLeft;
            offsetY += canvas.offsetTop;
        }while(canvas = canvas.offsetParent)
    }

    // 鼠标在页面中的坐标减去canvas在页面中的坐标就是鼠标在canvas中的坐标
    mx = e.pageX - offsetX;
    my = e.pageY - offsetY;

    return {
        x: mx,
        y: my
    }
}

// 计算从鼠标移动开始到结束时点之间的距离
function distance(point1, point2) {
    return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2))
}

// 计算从鼠标移动开始到结束时两点之间与x轴的夹角、
function angle(point1, point2) {
    return Math.atan2(point2.x - point1.x, point2.y - point1.y)
}

// 设置是否擦除状态和刷子
let isErasering = false,
    lastPoint,
    brush = new Image();
brush.src='擦除时刷子图片地址';

// 鼠标按下或手指触摸开始时事件
function moveStart(e) {
    isErasering = true;
    lastPoint = getPointPosition(e, canvas);
}

// 鼠标移动或手指滑动时事件
function move(e) {
    if(!isErasering){
        return
    }

    var currentPoint = getPointPosition(e, canvas);
    // console.log(currentPoint)
    var dist = distance(lastPoint, currentPoint);
    var ang = angle(lastPoint, currentPoint);
    var x;
    var y;

    // 计算并绘制鼠标或手指移动时的实时路径
    for (var i = 0; i < dist; i++) {
        x = lastPoint.x + Math.sin(ang) * i - 25;
        y = lastPoint.y + Math.cos(ang) * i - 25;

        // destination-out效果是将源图像(刷子图案)绘制到目标图像(灰色涂层)上,源图像是透明的
        ctx.globalCompositeOperation = 'destination-out';

        // 在鼠标或手指移动过的地方绘制刷子图案
        ctx.drawImage(brush, x, y);
    }
    lastPoint = currentPoint;
    handleFilledPercentage(getFilledPercentage());
}

// 鼠标或手指松开时事件
function moveEnd(e) {
    isErasering = false;
}

5、设置当灰色涂层被刮到一定百分比时去除整个涂层;利用canvasgetImageData可以获取指定范围区域的每个像素点的rgba的信息,其中a指的是透明度,当a为0时,该像素点完全透明,当a为255时,表示该像素点完全不透明;通过遍历指定区域的像素点信息,得到透明像素点的个数,用透明像素点的个数和总像素点的个数可以计算出已经刮开区域所占的百分比;通过设置阈值,使得当所刮区域占比满足一定值时,去除整个灰色涂层;

// 计算已经刮过的区域占整个区域的百分比
function getFilledPercentage() {
    let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    // imgData.data是个数组,存储着指定区域每个像素点的信息,数组中4个元素表示一个像素点的rgba值
    let pixels = imgData.data;
    let transPixels = [];
    for(let i = 0; i < pixels.length; i += 4){
        // 严格上来说,判断像素点是否透明需要判断该像素点的a值是否等于0,
        // 为了提高计算效率,这儿设置当a值小于128,也就是半透明状态时就可以了
        if(pixels[i+3] < 128){
            transPixels.push(pixels[i+3]);
        }
    }
    return (transPixels.length / (pixels.length / 4) * 100).toFixed(2) + '%'
}

// 设置阈值,去除灰色涂层
function handleFilledPercentage(percentage) {
    percentage = percentage || 0;
    if(parseInt(percentage) > 50){
        // 去除画布方法一:直接将canvas涂层删掉
        // canvas.parentNode.removeChild(canvas);
        // 去除画布方法二:将canvas涂层设置为透明
        ctx.fillStyle = 'rgba(255, 255, 255)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
}

查看完整代码

参考文献:

[1] Canvas学习:图像合成
[2] HTML5 Canvas实战之刮奖效果

猜你喜欢

转载自blog.csdn.net/XuM222222/article/details/82353729