【Canvas】彩色弹跳小球动画的实现

弹跳小球

视频指导:【酷酷的Canvas动画-哔哩哔哩】 https://b23.tv/7eLF8hv

画一个小球

要实现弹跳小球,第一步当然是先使用canvas画出一个小球,用到的方法是canvas的arc方法来进行圆弧的绘制

image.png
比如我们想在canvas上绘制一个半径为100px的小球,可以使用以下代码:

// 绘制一个圆形
ctx.beginPath()
ctx.arc(200, 200, 100, 0, 2 * Math.PI, false)
// 绘制路径用stroke,填充路径用fill
ctx.fill() // 填充小球
ctx.closePath() // 闭合路径

通过上面的代码,我们画出了下面这样的一个小球

image.png
通过fillStyle属性和radius的值我们可以控制小球的颜色和大小。

画很多个小球

小球构造函数

上面我们使用4行代码画出了一个最简单的小球,那如果我们想要画很多个小球的话,不可能手动去一个小球一个小球的去写,因此,我们需要一个小球的构造函数,然后当我们需要一个新的小球的时候,直接new一个即可。

通过对小球的分析,我们可以知道,一个小球实例需要有以下的属性

  • 圆心坐标(x,y)
  • 半径r
  • 颜色color

因为我们要实现的是弹跳小球,因此我们还需要给小球一个x方向的速度dx和y方向的速度dy

  • x方向的速度dx
  • y方向的速度dy

我们要把小球绘制到canvas上,因此还需一个draw方法,调用它就会根据小球上面的属性将小球绘制出来

  • 绘制方法draw

小球弹跳的过程起始就是小球自身坐标xy的变化过程,因此我们还要有一个update方法来进行小球自身坐标的更新,这样小球才能动起来

  • 更新方法update

通过上面的分析,小球的构造函数可以如下:

// 小球构造函数
function Ball(x, y, dx, dy, r, color) {
    this.x = x
    this.y = y
    this.dx = dx
    this.dy = dy
    this.r = r
    this.color = color
    // 绘制方法
    this.draw = function () {
        ctx.beginPath()
        ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false)
        cyx.fillStyle = this.color
        ctx.fill() // 填充小球
        ctx.closePath() // 闭合路径
    }
    // 更新方法
    this.update = function () {
    }
}

创建小球实例

尝试创建一个小球实例对象,并调用它的draw方法,看是否能在canvas上绘制出一个小球

const ball = new Ball(100,100,10,10,50,'green') // 绘制一个绿色的小球
ball.draw()

image.png

可以看到,是可以画出的,接下来我们进一步来完善小球的构造函数,比如说小球半径的大小是随机的,圆心坐标也是随机的,颜色也是随机的。

完善构造函数

半径大小及圆心坐标随机

因为canvas是有边界的,就是我们给canvas元素设置的宽高,所以我们的半径和圆心坐标都应该在canvas允许的范围内进行随机,因此,这里我们要思考一下边界条件

边界条件

image.png

已知canvas的左上角是(0,0)原点,通过上图我们可以得知,

  • 假设小球的左边刚好碰到矩形左边边界的成立条件是 x=r
  • 假设小球的右边刚好碰到矩形右边边界的成立条件是 x+r=width
  • 假设小球的上边刚好碰到矩形上边边界的成立条件是 y=r
  • 假设小球的下边刚好碰到矩形下边边界的成立条件是 y+r=height

通过上面的分析,我们可以知道,在r确定的时候,圆心坐标的随机范围分别是

x坐标:[r,width - r]
y坐标:[r,height - r]

确定了范围之后,我们就可以实现获取指定范围的随机数方法了

// 获取指定范围的随机数
const giveMeRandom = (min, max) => {
    // Math.random() * (max - min) + min
    return Math.floor(Math.random() * (max - min + 1) + min)
}

小球颜色随机

小球颜色的随机方法就比较多了,选取较简单的一种,定义一个颜色数组,里面存放一定数量的颜色值,然后每次创建小球实例的时候,从中随机取出一个作为小球的颜色即可。实现代码如下:

// 颜色列表
const COLOR_LIST = ['#ff4858', '#1b7f79', '#00ccc0', '#72f2eb', '#747f7f']
// 随机获取颜色
const giveMeColor = () => {
    const index = giveMeRandom(0, COLOR_LIST.length)
    return COLOR_LIST[index]
}

绘制100个小球

接下里通过上面的分析,尝试绘制100个小球到canvas上,通过下面的代码

// 创建100个小球实例
const BALL_POOL = [] // 小球池-存放小球实例
for (let i = 0; i < 100; i++) {
    const r = giveMeRandom(1, 15) // 确定小球半径
    const x = giveMeRandom(r, canvas.width - r) // 确定x坐标
    const dx = 10 // x方向速度
    const y = giveMeRandom(r, canvas.height - r) // 确定y坐标
    const dy = 10 // y方向速度
    const color = giveMeColor() // 确定小球颜色
    // 创建实例
    BALL_POOL.push(new Ball(x, y, dx, dy, r, color))
}
// 绘制小球
for (let ball of BALL_POOL) {
    ball.draw()
}

执行上述代码,得到的结果如下图

image.png

让小球动起来

上面构造函数那里我们也说过了,小球动起来其实就是小球自身坐标的运动,而我们眼中小球动起来,其实是画布不断刷新同时小球坐标在每次刷新时都不一样,又因为小球的坐标的变化是连续的,因此当刷新的频率达到一定的速度的时候,小球在我们眼中就像是动起来了一样,总结整个过程其实就是:

绘制 – 坐标更新 – 清空画布 – 绘制 – 坐标更新 – 清空画布 – 绘制 – ….

也就是说,我们要每隔一定的时间,将画布清空,然后重新将坐标改变后的小球实例绘制出来,这里要用到的就是专门用来绘制平滑动画的一个API了,它就是 requestAnimationFrame(),这个API接收一个函数A作为参数,函数是要在重绘屏幕前调用的函数,我们对小球的坐标更新的操作就放在这个函数A中执行,又因为这个API只会调用一次传入的函数,因此我们要在每次调用完之后,再调用一次,以达到动画能连续的效果,代码如下:

// 动画函数
function animate() {
    requestAnimationFrame(animate) // 调用自身,进而得到连续的动画
    // ... 这里进行小球自身圆心坐标的更新操作
}
​
// 调用函数
animate()

到这里,我们就要回到小球的构造函数了,我们一开始在里面留了个update方法,用来进行小球自身坐标的更新,现在让我们来完善它。

update

这里就用到两个参数,分别是dx和dy,小球每次坐标变化多少就是由这两个参数决定的,所以,我们先尝试每次调用update的时候,给小球的xy坐标各自加上dx和dy试试看有什么效果。

// 更新方法
this.update = function () {
    this.x += this.dx
    this.y += this.dy
    this.draw() // 改变完坐标要重新绘制
}

改变后的效果如下:

20221120_181052.gif

可见小球都往一个方向改变,而且沿途都留下了上一次绘制的痕迹,因此,我们要改变一下animate方法,同时在update中进行坐标更新的时候,要加上边界检测。

绘制前清空画布

// 动画函数
function animate() {
    requestAnimationFrame(animate) // 调用自身,进而得到连续的动画
    ctx.clearRect(0, 0, canvas.width, canvas.height) // 清空画布
    // 绘制小球
    for (let ball of BALL_POOL) {
        ball.update()
    }
}

更新坐标时进行边界检测

// 更新方法
this.update = function () {
    // 左右边界检测
    if (this.x < this.r || this.x > canvas.width - this.r) {
        this.dx = -this.dx
    }
    // 上下边界检测
    if (this.y < this.r || this.y > canvas.height - this.r) {
        this.dy = -this.dy
    }
    this.x += this.dx
    this.y += this.dy
    this.draw()
}

进行边界检测之后,小球就具有初步的弹跳效果了,如下图

20221120_182539.gif

但是很明显这里面的小球并不是无规则乱跳的,可以很明显的看出它们的跳动方向,这是因为,我们在初始化的时候,小球的运动方向并没有进行随机处理,而是采用了统一值,并且方向也是统一的(都是正值),因此我们可以修改一下创建小球实例的代码,使得小球的跳动更加自然。

const dx = (Math.random() - 0.5) * 20 // 0.5~-0.5 有正有负
const dy = (Math.random() - 0.5) * 20

最后得到的效果如图

20221120_183057.gif

贴上完整代码

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容