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>
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>
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>
画面アクセスして「アニメーション開始」ボタンを押下すると、
アニメーション1とアニメーション2がそれぞれ実行されます。
アニメーション1とアニメーション2は再生秒数が異なるので、
アニメーションが終了するタイミングは別々になります。
どちらのアニメーションも終了したタイミングでPromise.allが動き、アラート表示されることが確認できます。