JavaScript+Canvas模拟实现支付宝画年兔游戏

 更新时间:2023年01月14日 11:19:05   作者:郑丫头  
接近过年了,支付宝的集福的活动又开始了,集美们的五福集齐了没有。每年的集福活动都有一些小游戏,今年也不例外,画年画就是其中之一,本篇用canvas来写一个画年兔的游戏

接近过年了,支付宝的集福的活动又开始了,集美们的五福集齐了没有。每年的集福活动都有一些小游戏,今年也不例外,画年画就是其中之一,本篇用canvas来写一个画年兔的游戏。

动手前的思路

画年画游戏规则是:跟着特定轮廓画出线条来。

思考1、如何让鼠标只能在特定区域内画画?

首先要获取到这个轮廓区域所在画布上的位置,判断鼠标绘画的位置是否在指定范围内。用canvas的getImageData函数能够获取到画布上有颜色的像素点,然后根据像素点分布计算出像素点所在位置。

getImageData函数的用法在我之前的文章canvas文字粒子特效中有详细介绍,不懂的可以去看看。

getImageData(canvas, ctx) {
    const data = ctx.getImageData(
        0,
        0,
        canvas.width,
        canvas.height,
    ).data;
    const gap = 4;
    const points = [];
    const length = data.length;
    for (let i = 0, wl = canvas.width * gap; i < length; i += gap) {
        if (data[i + gap - 1] == 255) {
            // 根据透明度判断
            const x = (i % wl) / gap;
            const y = Math.ceil(i / wl);
            points.push([x, y]);
        }
    }
    return points;
}

思考2、如何让绘制的图画动起来

通过定时旋转画布实现。我选择用帧动画requestAnimationFrame函数,比setInterval函数性能更好一点。

思考3、如何撤销上一步操作

将每一步绘制的点都记录到创建的栈中,每一次撤销都把上一步的绘制点删除。

思考4、如何判断线条绘制完毕

想了很久没有什么太好的办法,如果你有想法可以分享给我。当mouseup事件执行,会判断当前步骤下绘制的点数是否>=30,如果满足条件会延迟半秒执行下一步绘制,当mousedown在半秒内触发,延迟函数会取消,等待下一个mouseup事件。

// mouseup事件
const length = _this.execStack.reduce((prev, next) => {
    if (next.step == _this.curStep) {
        prev += next.points.length;
    }
    return prev;
}, 0)
if (length >= 30) {
    _this.timer = setTimeout(() => {
        if (_this.curStep == 2) {
            _this.curStep = 2.5
            _this.canEdit = false;
            _this.animate('ears_1', 3);
        } else if (_this.curStep == 4) {
            _this.curStep = 4.5
            _this.canEdit = false;
            _this.animate('shake_head', 5);
        } else if (_this.curStep == 6) {
            _this.curStep = 6.5
            _this.canEdit = false;
            _this.animate('shake_body', 7);
        } else if (_this.curStep <= 6) {
            _this.canEdit = true;
            _this.curStep += 1;
        }
        _this.execCanvas();
    }, 500);
}

关键步骤

1、创建一个RabbitPainting类,初始化时监听canvas的鼠标点击事件。要注意的是移动端和pc端监听的事件不同:

this.pcEvents = ['mousedown', 'mousemove', 'mouseup'];
this.mobileEvents = ['touchstart', 'touchmove', 'touchend'];

我的代码里对兼容的处理比较粗糙,只是将大致功能做出来了,所以大家多看看思路。

鼠标移开之后,需要解除事件监听,当鼠标重新按压时再绑定事件。mousedown事件监听流程如下:

鼠标移动时,会得到两个点,鼠标按压位置和鼠标移动位置,如果绘制的线都是从按压点到移动点的话,就会画出:

上图所示,红色的线是鼠标移动路径,黑色的线是canvas画出的线条,所以mousemove函数执行后要更新初始按压点,使前后两个点衔接在一起。

线条绘制函数如下:

drawLine(point) {
    const { ctx } = this
    ctx.beginPath()
    ctx.moveTo(point.startX, point.startY);
    ctx.lineTo(point.endX, point.endY);
    if (point.style) {
        for (let key in point.style) {
            ctx[key] = point.style[key]
        }
    }
    ctx.stroke();
    ctx.closePath()
}

2、兔子轮廓绘制,采用贝塞尔2阶函数绘制图形

// 外轮廓样式
const wrapperStyle = {
    lineWidth: "30",
    strokeStyle: this.tipPathColor[0]
}
// 内虚线样式
const innerStyle = {
    lineWidth: "3",
    strokeStyle: this.tipPathColor[1],
    lineDash: [15, 12]
}
drawCurve({ list, wrapperStyle, innerStyle }) {
    const { tempCtx: ctx } = this
    list.forEach(point => {
        const { x, y, list } = point;
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.bezierCurveTo(...list);
        for (let key in wrapperStyle) {
            ctx[key] = wrapperStyle[key]
        }
        ctx.stroke();
        ctx.save()
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.bezierCurveTo(...list);
        for (let key in innerStyle) {
            if (key == 'lineDash') {
                ctx.setLineDash(innerStyle[key]);
            }
            ctx[key] = innerStyle[key]
        }
        ctx.stroke();
        ctx.restore();
    })
}

贝塞尔曲线的关键在于设置p1和p2两个控制点,大家自行把握。

我的兔子轮廓总体是这样的:

3、旋转画布功能

使用canvas的rotate函数,画布的默认中心点是(0,0),所以旋转时需要用translate(x,y)函数将中心点移动到特定位置。要注意旋转后将画布的中心点还原到(0,0)。

const rotateCanvas = (centerPoints, item) => {
    ctx.save()
    ctx.translate(...centerPoints)
    ctx.rotate(Math.PI / 180 * item.curDeg)
    ctx.translate(-centerPoints[0], -centerPoints[1])
}

注意ctx.save(),用来记录画布旋转之前的状态,绘制结束后需要用ctx.restore()将画布状态还原,否则定时函数执行角度旋转时角度会累加。

4、眨眼睛动画

眨眼睛是用一张精灵图,因为图片是我自己画的,只有六帧,所以动画看起来不是很好,将就看着吧。

以上就是JavaScript+Canvas模拟实现支付宝画年兔游戏的详细内容,更多关于JavaScript Canvas画年兔游戏的资料请关注脚本之家其它相关文章!

相关文章

  • JavaScript中的函数式编程详解

    JavaScript中的函数式编程详解

    这篇文章主要介绍了JavaScript中的函数式编程详解,需要的朋友可以参考下
    2020-08-08
  • uniapp跨页面传值uni.$emit和uni.$on的使用及踩坑实战

    uniapp跨页面传值uni.$emit和uni.$on的使用及踩坑实战

    使用uni.$emit触发事件后,对应的uni.$on就会监听到事件触发,在回调中去执行相关的逻辑,下面这篇文章主要给大家介绍了关于uniapp跨页面传值uni.$emit和uni.$on的使用及踩坑实战的相关资料,需要的朋友可以参考下
    2023-04-04
  • JavaScript中如何判断一个值是否为Null

    JavaScript中如何判断一个值是否为Null

    我们在开发的时候经常会判断一个null值,那么我们该如何去判断呢?下面这篇文章主要给大家介绍了关于JavaScript中如何判断一个值是否为Null的相关资料,需要的朋友可以参考下
    2023-12-12
  • Layui选项卡制作历史浏览记录的方法

    Layui选项卡制作历史浏览记录的方法

    今天小编大家分享一篇Layui选项卡制作历史浏览记录的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-09-09
  • JavaScript生成GUID的多种算法小结

    JavaScript生成GUID的多种算法小结

    这篇文章介绍了GUID的几种生成算法,有需要的朋友可以参考一下
    2013-08-08
  • 如何去除js中的json存在的转义字符\问题

    如何去除js中的json存在的转义字符\问题

    这篇文章主要介绍了如何去除js中的json存在的转义字符\问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • js变量以及其作用域详解

    js变量以及其作用域详解

    Javascript和Java、C这些语言不同,它是一种无类型、弱检测的语言。它对变量的定义并不需要声明变量类型,我们只要通过赋值的形式,可以将各种类型的数据赋值给同一个变量
    2012-01-01
  • 详解JavaScript中的Object.is()与

    详解JavaScript中的Object.is()与"==="运算符总结

    这篇文章主要介绍了详解JavaScript中的Object.is()与"==="运算符总结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 轻松学习Javascript闭包

    轻松学习Javascript闭包

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。这篇文章主要介绍了Javascript闭包,需要的朋友可以参考下
    2017-03-03
  • js实现百度地图定位于地址逆解析,显示自己当前的地理位置

    js实现百度地图定位于地址逆解析,显示自己当前的地理位置

    本文分享了基于javascript实现的百度地图定位于地址逆解析,显示自己当前的地理位置的实例代码,有兴趣的朋友可以看下
    2016-12-12

最新评论