实现canvas的元素动画

181 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。 点击查看活动详情

写在前面

前面我们实现了一个简单的canvas库 100行代码写个canvas库 ,我们已经把添加到canvas上的每个元素都视为一个独立个体或者组合,除了能够和他们交互(点击,拖拽等),它们自身也应该有状态的变化,例如游戏中角色的运动,炸弹的爆炸等,现在我们给它加上元素动画的功能。

CSS的帧动画

CSS提供了一种帧动画,通过steps来切换动作,这里就不详细展开了,有兴趣的可以去了解一下,我们先来看看效果。

这是一张动作分解图,为了动画的时候看的更清楚,我给他们编了号

1660553243(1).jpg

通过添加CSS动画的效果

.person-run {
    position: absolute;
    width: 92px;
    height: 163px;
    background-image: url("./run.png");
    animation: run 1s steps(8) infinite;
    border: 1px red solid;
}
@keyframes run {
    from {
      background-position: 0 0;
    }
    to {
      background-position: -736px 0;
    }
}
<div class="person-run"></div>

run1.gif

这个animation: run 1s steps(8) infinitekeyframes run的意思就是把这张图片在1s内分八次从0px 0px移动到-736px 0px,也就是125ms切换一次,最终达到的帧动画效果。

可是CSS实现的动画都有一个问题,那就是不好控制,比如我们想随时暂停再开始,或者通过交互触发暂停开始,更何况我们现在要处理canvas上的元素。

但我们可以从这个动画的机制上得到一些启发,从而实现canvas上的元素帧动画,通过给元素类定时切换要绘制的内容,控制刷新的频率,也能达到同样的效果。

元素动画

我们通过不断计算当前应该绘制的内容来实现元素的动画

class Person {
    draw(ctx) {
        ctx.drawImage(
            this.image, 
            this.runIndex % 8 * this.w, 
            0, 
            this.w, 
            this.h, 
            this.x + this.offsetX, 
            this.y + this.offsetY, 
            this.w, 
            this.h);
    }
    play() {
        this.run = true
        this.timer = requestAnimationFrame(Person.prototype.play.bind(this))
        this.runIndex++
    }
}

这样我们不断的改变runIndex,从而拿到要绘制的内容,配合Stage的刷新功能,元素就动起来了

let p = new Person({
    x: 0,
    y: 0,
    w: 92,
    h: 163,
    src: "./run.png"
})
s2.add(p)

run2.gif

可是这个时候元素的动画频率是跟Stage一样的(16.67ms),实际上每个元素动画的频率肯定是不一样的,所以我们要根据每个元素的实际频率来进行定时更新 用requestAnimationFrame封装一个定时器 。 把元素更新的方式改一下

class Person {
    play() {
        this.run = true
        this.timer = new Timer(() => {
            this.runIndex++
        }, this.interval)
    }
}

把interval设为125ms,动画效果就跟上面CSS实现的一样了

run4.gif

更新位置

我们再给元素添加上xy的更新,就能实现一个完整的人物走动效果了

class Person {
    play() {
        this.run = true
        this.timer = new Timer(() => {
            // 除了更新帧动画,还更新xy
            this.runIndex++
            if(this.x < 700) {
                this.x ++
            }
        }, this.interval)
    }
    pause() {
        this.run = false
        cancelAnimationFrame(this.timer)
        this.timer = null
    }
}

run5.gif

这样就可以通过person.playperson.pause来控制人物的状态了。