基于Canvas与JavaScript实现支付宝画年兔游戏的技术解析

作者:交互手记

时间:2026-10-26

浏览:575

本文详解如何使用JavaScript和Canvas技术实现支付宝画年兔小游戏。核心内容包括利用getImageData限制鼠标绘制区域、通过requestAnimationFrame实现画布旋转动画、使用栈结构处理撤销操作,以及结合贝塞尔曲线绘制兔子轮廓。文章提供了关键代码逻辑与实现思路,帮助开发者理解前端图形交互与动画控制的实际应用。

文章目录

春节临近,支付宝的集五福活动如期而至,大家是否已经集齐了五福?每年的集福活动都会推出互动小游戏,今年的“画年画”便是其中之一。本文将介绍如何利用Canvas技术开发一款画年兔的互动游戏。

开发前的思路规划

开发前的思路规划

画年画游戏的核心规则是:用户需沿着特定的轮廓线条进行描画。

关键问题一:如何限制鼠标仅在特定区域内绘制?

首先需要获取轮廓区域在Canvas画布上的具体坐标位置,并判断鼠标绘制点是否落在指定范围内。利用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;
}

关键问题二:如何让绘制的图案产生动态效果?

可以通过定时旋转画布来实现动态效果。这里推荐使用requestAnimationFrame函数进行帧动画处理,相比setInterval函数,其性能表现更优。

关键问题三:如何实现上一步操作的撤销?

将每一步绘制的关键点记录到创建的栈结构中,每次执行撤销操作时,从栈中移除上一步的绘制点数据即可。

关键问题四:如何判断线条绘制是否完成?

经过多次尝试,目前采用以下逻辑:当触发mouseup事件时,判断当前步骤下绘制的点数是否大于等于30。若满足条件,则延迟0.5秒执行下一步绘制;若在0.5秒内再次触发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. 兔子轮廓的绘制,采用二阶贝塞尔曲线来构建图形。

开发前的思路规划

// 外轮廓样式
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模拟实现支付宝画年兔游戏的详细内容,更多相关技术资料请关注本站其他文章。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章