SVGアニメーション – Web Animations API を使う – アニメーションのPromiseについて

javascript

SVGアニメーション – Web Animations API を使う – アニメーションのPromiseについて

Animationオブジェクトには以下のプロパティが定義されています。
①Animation.ready
②Animation.finished

これはPromiseの仕組みで動作します。
PromiseはECMAScript 2015から導入された概念で、javascriptで利用できますが、言語として組み込まれたのは他の言語です。

Promiseは非同期処理を抽象化したオブジェクトと、オブジェクトを操作する仕組みです。
ここではPromiseの詳解はしないですが、このPromiseの概念はjavascriptを書き進めるにあたり避けて通れない概念です。

SVGアニメーションでPromiseを利用する例として、上記の「ready」と「finished」を軸に進めます。

Promiseのおもな動き

Promiseには非同期処理をおもに取り扱います。
非同期処理の結果が解決(resolve)された際の処理、コールバック関数をthenメソッドを使って設定することができます。
また、解決ではなく非同期処理が失敗(reject)された際にも処理(コールバック関数)が呼ばれます。

Animation.readyの動き

Animation.readyはアニメーションの再生準備が完了した際にthenのコールバック関数を実行します。
以下、簡単なサンプルです。
再生準備が完了したかどうかをconsole.logに出力しています。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style>
#bottom {
    margin-bottom    : 10px;
}
#test1_dom {
    color            : #5f5d5d;
    background-color : #eeeeee;
    font-size        : 1.1rem;
    width            : 150px;
}
</style>
</head>
<body>
<!-- ボタン -->
<div id="bottom">
    <input type="button" id="start_animate"   value="アニメーション開始"    >
    <input type="button" id="pause_animate"   value="アニメーション一時停止">
    <input type="button" id="reverse_animate" value="アニメーション逆再生"  >
    <input type="button" id="finish_animate"  value="アニメーション終了"    >
    <input type="button" id="cancel_animate"  value="アニメーション停止"    >
</div>
<!-- アニメーション用DIV -->
<div id="test1_dom">TEST1 (<span id="display"></span>)</div>
<!-- 再生秒数 -->
<div class="params" id="currentTime_value"></div>
<script>
// DOM要素取得
let start_animate_dom   = document.querySelector("#start_animate"  );
let pause_animate_dom   = document.querySelector("#pause_animate"  );
let reverse_animate_dom = document.querySelector("#reverse_animate");
let finish_animate_dom  = document.querySelector("#finish_animate" );
let cancel_animate_dom  = document.querySelector("#cancel_animate" );
let display_dom         = document.querySelector("#display"        );
let test1_dom           = document.querySelector("#test1_dom"      );
// アニメーション用オブジェクト
let animation1;
// keyframes定義
const keyframes1 = [
    {transform: 'translateX(0px)'  },
    {transform: 'translateX(200px)'},
];
// option定義
const options1 = {
    duration: 5000
};
animation1 = test1_dom.animate(keyframes1, options1);

animation1.cancel(); // 初期値は停止

// Promiseの確認
// 再生準備ができているかを確認する
animation1.ready.then(function() {
    console.log("再生準備完了");
});

// アニメーション開始
start_animate_dom.addEventListener('click', (e) => {
    animation1.play();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション一時停止
pause_animate_dom.addEventListener('click', (e) => {
    animation1.pause();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション逆再生
reverse_animate_dom.addEventListener('click', (e) => {
    animation1.reverse();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション終了
finish_animate_dom.addEventListener('click', (e) => {
    animation1.finish();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション停止
cancel_animate_dom.addEventListener('click', (e) => {
    animation1.cancel();
    display_dom.innerHTML = animation1.playState;
}, false);


// 再生秒数を定期的に更新する
setInterval(() => {
    // アニメーションが再生中の場合に秒数を更新
    if (animation1.playState === 'running') {
        // currentTimeはミリ秒で返されるので、秒に変換して表示
        let disp_sec = (animation1.currentTime / 1000).toFixed(2) + '秒';
        document.getElementById('currentTime_value').innerHTML = disp_sec;
    }
}, 10); // 0.01秒ごとにチェック
</script>
</body>
</html>

サーバ上のHTMLはこちら(test1.html)

Animation.finishedの動き

上記と同様に「再生完了しているか」を確認するサンプルです。
再生完了をPromiseを使って「animation1.finished.then」という形で確認しています。
非同期的に動作するので、再生秒数にかか関わらず動作します。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style>
#bottom {
    margin-bottom    : 10px;
}
#test1_dom {
    color            : #5f5d5d;
    background-color : #eeeeee;
    font-size        : 1.1rem;
    width            : 150px;
}
</style>
</head>
<body>
<!-- ボタン -->
<div id="bottom">
    <input type="button" id="start_animate"   value="アニメーション開始"    >
    <input type="button" id="pause_animate"   value="アニメーション一時停止">
    <input type="button" id="reverse_animate" value="アニメーション逆再生"  >
    <input type="button" id="finish_animate"  value="アニメーション終了"    >
    <input type="button" id="cancel_animate"  value="アニメーション停止"    >
</div>
<!-- アニメーション用DIV -->
<div id="test1_dom">TEST1 (<span id="display"></span>)</div>
<!-- 再生秒数 -->
<div class="params" id="currentTime_value"></div>
<script>
// DOM要素取得
let start_animate_dom   = document.querySelector("#start_animate"  );
let pause_animate_dom   = document.querySelector("#pause_animate"  );
let reverse_animate_dom = document.querySelector("#reverse_animate");
let finish_animate_dom  = document.querySelector("#finish_animate" );
let cancel_animate_dom  = document.querySelector("#cancel_animate" );
let display_dom         = document.querySelector("#display"        );
let test1_dom           = document.querySelector("#test1_dom"      );
// アニメーション用オブジェクト
let animation1;
// keyframes定義
const keyframes1 = [
    {transform: 'translateX(0px)'  },
    {transform: 'translateX(200px)'},
];
// option定義
const options1 = {
    duration: 5000
};
animation1 = test1_dom.animate(keyframes1, options1);

animation1.cancel(); // 初期値は停止

// Promiseの確認
// 再生準備ができているかを確認する
animation1.ready.then(function() {
    console.log("再生準備完了");
});

// 再生完了しているかを確認する
animation1.finished.then(function() {
    console.log("再生完了");
});

// アニメーション開始
start_animate_dom.addEventListener('click', (e) => {
    animation1.play();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション一時停止
pause_animate_dom.addEventListener('click', (e) => {
    animation1.pause();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション逆再生
reverse_animate_dom.addEventListener('click', (e) => {
    animation1.reverse();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション終了
finish_animate_dom.addEventListener('click', (e) => {
    animation1.finish();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション停止
cancel_animate_dom.addEventListener('click', (e) => {
    animation1.cancel();
    display_dom.innerHTML = animation1.playState;
}, false);


// 再生秒数を定期的に更新する
setInterval(() => {
    // アニメーションが再生中の場合に秒数を更新
    if (animation1.playState === 'running') {
        // currentTimeはミリ秒で返されるので、秒に変換して表示
        let disp_sec = (animation1.currentTime / 1000).toFixed(2) + '秒';
        document.getElementById('currentTime_value').innerHTML = disp_sec;
    }
}, 10); // 0.01秒ごとにチェック
</script>
</body>
</html>

サーバ上のHTMLはこちら(test2.html)

2つのAnimationオブジェクトに対し、Animation.finishedを参照してPromise.allで終了タイミングを取得する

アニメーション1とアニメーション2を用意し、1と2をそれぞれ再生して「どちらも終了したタイミング」を
Promise.allを用いて取得(確認)します。

便宜上、このサンプルの場合のアニメーション1とアニメーション2は途中で異常終了しなものとしています。

Promise.allを使う場合は、引数の中にアニメーション1とアニメーション2のfinishedを格納したPromiseを使用しています。

アニメーション1の「anifini1」を取得

// 再生完了しているかを確認する
let anifini1 = animation1.finished.then(function() {
    console.log("アニーメション1 再生完了");
});

アニメーション2の「anifini2」を取得

// 再生完了しているかを確認する
let anifini2 = animation2.finished.then(function() {
    console.log("アニーメション2 再生完了");
});

取得した「anifini1」と「anifini2」をPromise.allの引数として使用し、
アニメーション1とアニメーション2の両方が完了したタイミングで、アラート(とconsoleログ)を出力

// Promise allの確認
Promise.all([anifini1, anifini2]).then(() => {
	console.log('Promise.all then');
	alert('Promise.all then');
});

サンプルの全体は以下のように書きました。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style>
#bottom {
    margin-bottom    : 10px;
}
#test1_dom, #test2_dom {
    color            : #5f5d5d;
    background-color : #eeeeee;
    font-size        : 1.1rem;
    width            : 150px;
}
</style>
</head>
<body>
<!-- ボタン -->
<div id="bottom">
    <input type="button" id="start_animate"   value="アニメーション開始"    >
    <input type="button" id="pause_animate"   value="アニメーション一時停止">
    <input type="button" id="reverse_animate" value="アニメーション逆再生"  >
    <input type="button" id="finish_animate"  value="アニメーション終了"    >
    <input type="button" id="cancel_animate"  value="アニメーション停止"    >
</div>
<!-- アニメーション用DIV -->
<div id="test1_dom">TEST1</div>
<div id="test2_dom">TEST2</div>
<!-- 再生秒数 -->
<div class="params" id="currentTime_value1"></div>
<div class="params" id="currentTime_value2"></div>
<script>
//-----------------------------
// DOM要素取得
let start_animate_dom   = document.querySelector("#start_animate"  );
let pause_animate_dom   = document.querySelector("#pause_animate"  );
let reverse_animate_dom = document.querySelector("#reverse_animate");
let finish_animate_dom  = document.querySelector("#finish_animate" );
let cancel_animate_dom  = document.querySelector("#cancel_animate" );
let test1_dom           = document.querySelector("#test1_dom"      );
let test2_dom           = document.querySelector("#test2_dom"      );

//-----------------------------
// アニメーション1用オブジェクト
let animation1;
// keyframes定義
const keyframes1 = [
    {transform: 'translateX(0px)'  },
    {transform: 'translateX(200px)'},
];
// option定義
const options1 = {
    duration: 5000
};
animation1 = test1_dom.animate(keyframes1, options1);

//-----------------------------
// アニメーション2用オブジェクト
//-----------------------------
let animation2;
// keyframes定義
const keyframes2 = [
    {transform: 'translateX(0px)'  },
    {transform: 'translateX(200px)'},
];
// option定義
const options2 = {
    duration: 10000
};
animation2 = test2_dom.animate(keyframes2, options2);

//-----------------------------
animation1.cancel(); // 初期値は停止
animation2.cancel(); // 初期値は停止

//-----------------------------
// Promiseの確認
//-----------------------------
// 再生準備ができているかを確認する
animation1.ready.then(function() {
    console.log("アニーメション1 再生準備完了");
});

// 再生完了しているかを確認する
let anifini1 = animation1.finished.then(function() {
    console.log("アニーメション1 再生完了");
});

//-----------------------------
// Promiseの確認
//-----------------------------
// 再生準備ができているかを確認する
animation2.ready.then(function() {
    console.log("アニーメション2 再生準備完了");
});

// 再生完了しているかを確認する
let anifini2 = animation2.finished.then(function() {
    console.log("アニーメション2 再生完了");
});

// Promise allの確認
Promise.all([anifini1, anifini2]).then(() => {
	console.log('Promise.all then');
	alert('Promise.all then');
});

//-----------------------------
// アニメーション操作
//-----------------------------
start_animate_dom.addEventListener('click', (e) => {
    animation1.play();
    animation2.play();
}, false);
// アニメーション一時停止
pause_animate_dom.addEventListener('click', (e) => {
    animation1.pause();
    animation2.pause();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション逆再生
reverse_animate_dom.addEventListener('click', (e) => {
    animation1.reverse();
    animation2.reverse();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション終了
finish_animate_dom.addEventListener('click', (e) => {
    animation1.finish();
    animation2.finish();
    display_dom.innerHTML = animation1.playState;
}, false);
// アニメーション停止
cancel_animate_dom.addEventListener('click', (e) => {
    animation1.cancel();
    animation2.cancel();
    display_dom.innerHTML = animation1.playState;
}, false);


// 再生秒数を定期的に更新する
setInterval(() => {
    // アニメーション1が再生中の場合に秒数を更新
    if (animation1.playState === 'running') {
        // currentTimeはミリ秒で返されるので、秒に変換して表示
        let disp_sec = (animation1.currentTime / 1000).toFixed(2) + '秒';
        document.getElementById('currentTime_value1').innerHTML = disp_sec;
    }

    // アニメーション2が再生中の場合に秒数を更新
    if (animation2.playState === 'running') {
        // currentTimeはミリ秒で返されるので、秒に変換して表示
        let disp_sec2 = (animation2.currentTime / 1000).toFixed(2) + '秒';
        document.getElementById('currentTime_value2').innerHTML = disp_sec2;
    }
}, 10); // 0.01秒ごとにチェック
</script>
</body>
</html>

サーバ上のHTMLはこちら(test3.html)

画面アクセスして「アニメーション開始」ボタンを押下すると、
アニメーション1とアニメーション2がそれぞれ実行されます。

アニメーション1とアニメーション2は再生秒数が異なるので、
アニメーションが終了するタイミングは別々になります。

どちらのアニメーションも終了したタイミングでPromise.allが動き、アラート表示されることが確認できます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です