本文已参与「新人创作礼」活动,一起开启掘金创作之路。 点击查看活动详情
写在前面
前面我们实现了一个简单的canvas库 100行代码写个canvas库 ,我们已经把添加到canvas上的每个元素都视为一个独立个体或者组合,除了能够和他们交互(点击,拖拽等),它们自身也应该有状态的变化,例如游戏中角色的运动,炸弹的爆炸等,现在我们给它加上元素动画的功能。
CSS的帧动画
CSS提供了一种帧动画,通过steps
来切换动作,这里就不详细展开了,有兴趣的可以去了解一下,我们先来看看效果。
这是一张动作分解图,为了动画的时候看的更清楚,我给他们编了号
通过添加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>
这个animation: run 1s steps(8) infinite
和keyframes 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)
可是这个时候元素的动画频率是跟Stage一样的(16.67ms
),实际上每个元素动画的频率肯定是不一样的,所以我们要根据每个元素的实际频率来进行定时更新 用requestAnimationFrame封装一个定时器 。
把元素更新的方式改一下
class Person {
play() {
this.run = true
this.timer = new Timer(() => {
this.runIndex++
}, this.interval)
}
}
把interval设为125ms
,动画效果就跟上面CSS实现的一样了
更新位置
我们再给元素添加上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
}
}
这样就可以通过person.play
和person.pause
来控制人物的状态了。