カスタムイージング関数を利用したWAAPIアニメーションです。鳥が羽ばたくアニメーションはCSS Animationsで作成し、左右と上下に移動するアニメーションをWAAPIで作成しています。アニメーションの切り替えには、Animation.effect.setKeyframes()メソッドを使っています。
左右と上下に移動するアニメーションに激しいカスタムイージング関数を適用しているので、鳥はまったく”おだやかでない”動きをしています。
HTML
<div class="bird-monster bird-monster-fly"></div>
CSS
/* 鳥のフレームサイズ*/
.bird-monster {
width: 134px;
height: 105px;
margin: 10% auto;
}
/* 羽ばたいているフレームアニメーション */
.bird-monster-fly {
/* https://opengameart.org/content/2d-monster-bat-enemy */
background-image: url("images/monster_fly.png");
animation: fly-anim 800ms infinite steps(8);
}
@keyframes fly-anim {
0% {
background-position: 0 0;
}
100% {
background-position: -1078px 0;
}
}
/* 攻撃するフレームアニメーション */
.bird-monster-attack {
/* https://opengameart.org/content/2d-monster-bat-enemy */
background-image: url("images/monster_attack.png");
animation: attack-anim 700ms infinite steps(8);
}
@keyframes attack-anim {
0% {
background-position: 0 0;
}
100% {
background-position: -1078px 0;
}
}
JavaScript
// アニメーションタイマー
const timings = {
duration: 5000
};
const effect = new KeyframeEffect(null, null, timings);
const animationTimer = new Animation(effect, document.timeline);
// ターゲットの鳥。
const birdMonster = document.querySelector('.bird-monster');
// 左右に移動するアニメーション、時間は1
const keyframesFly = {
transform: ['translateX(0)', 'translateX(-100px)', 'translateX(100px)', 'translateX(0)']
};
const timingsBird = {
duration: 1,
fill: 'forwards'
};
const effectFly = new KeyframeEffect(birdMonster, keyframesFly, timingsBird);
const flyAnim = new Animation(effectFly, document.timeline);
// 上下に移動する攻撃時のキーフレームパラメータ
// アニメーションを切り替えるとき、setKeyframes()メソッドで使用する
const keyframesAttack = {
transform: ['translateY(0)', 'translateY(500px)', 'translateY(0)']
};
// カスタムイージング関数
// ばね運動の公式を転用
const customEasingFly = (p) => {
return (1 - Math.cos(p * 5 * Math.PI) * (1 - p) + p) - 1;
}
// イーズインアウトの公式の転用
const customEasingAttack = (p) => {
return p - Math.sin(p * 2 * Math.PI) / (2 * Math.PI);
// return 1-(1-p)*(1-p)*(1-p)*(1-p);
}
// 状態に応じて切り替えるキーフレームやイージング関数、CSSクラスを
// 定義したオブジェクト
// flyState[state].keyframesでアクセスできる
const flyState = {
'fly': {
keyframes: keyframesFly,
customEasing: customEasingFly,
classList: 'bird-monster-fly'
},
'attack': {
keyframes: keyframesAttack,
customEasing: customEasingAttack,
classList: 'bird-monster-attack'
}
};
// 初期状態は'fly'
let state = 'fly';
// 毎フレーム呼び出される
const update = () => {
// アニメーションタイマーの進捗値を取得
const progress = animationTimer.effect.getComputedTiming().progress;
if (progress) {
// 進捗値を、状態に応じたイージング関数に渡して計算結果を得る
const r = flyState[state].customEasing(progress)
// console.log(r);
// 鳥のアニメーションの現在位置に設定する
flyAnim.currentTime = r;
}
}
const render = () => {
update();
window.requestAnimationFrame(render);
}
// アニメーションタイマーが終わったら、
animationTimer.addEventListener('finish', () => {
// 鳥のクラスを調べて、それによって状態を切り替える
if (birdMonster.classList.contains('bird-monster-fly')) {
state = 'attack';
animationTimer.playbackRate *= 10;
}
else {
state = 'fly'
animationTimer.playbackRate = 1;
}
// 鳥のクラスを一度全部削除する
const classList = birdMonster.classList;
while (classList.length > 0) {
classList.remove(classList.item(0));
}
// 鳥の基本クラスとフレームアニメーションのクラスを追加
birdMonster.classList.add('bird-monster');
birdMonster.classList.add(flyState[state].classList);
// setKeyframes()でキーフレームを切り替え
flyAnim.effect.setKeyframes(flyState[state].keyframes);
// アニメーションタイマーを再生。
// これにより、鳥の別のアニメーションの再生が始まる
animationTimer.play();
}, false);
// アニメーションタイマーをスタートし、JavaScriptアニメーションを開始する
flyAnim.ready.then(() => {
animationTimer.play();
render();
});