「10-10 アニメーションタイマーとしての利用」で述べた”3 セットした時間を100%として、今がその何%に当たる瞬間かを教えてくれるタイマー”の例です。ここでは、Animation.effect.getComputedTiming()が返すprogressを利用します。下記リンクがそのサンプルです。
ページを開くと、赤い太線で2つの円が同時に描かれます。見た目は単純ですが、アニメーションタイマーの進捗値を使って、左の円はSVG + WAAPIで、右の円は<canvas>要素 + JavaScriptで描画しています。
その仕組みは次の通りです。
- アニメーションタイマーを、duration:3000で作成します。
- 毎フレーム呼び出される関数で、アニメーションタイマーから
getComputedTiming().progressを呼び出して、その時点での進捗値を取得し、SVGで円を描くWAAPIアニメーションに渡します。
- SVGで円を描くWAAPIアニメーションは、duration:1で作成しておきます。すると、進捗値(0から1)をそのまま、アニメーションのcurrentFrameに割り当てることができます。
- さらに進捗値を、<canvas>で円を描く関数に渡し、円を描画します。
HTML
<svg width="200" height="200" id="svg-circle">
<circle class="circle" cx="100" cy="100" r="50" stroke="red" fill="white" stroke-width="15" stroke-linejoin="round"></circle>
</svg>
<div class="progress-circle">
<canvas id="canvas" width="120" height="120"></canvas>
</div>
<div class="box a">
<p>SVGで円を描画</p>
</div>
<div class="box b">
<p>CANVASで円を描画</p>
</div>
JavaScript
// requestAnimationFrame()を止めるためのID値用変数
let lastId = 0;
// 3秒を刻むアニメーションタイマー
// タイミングパラメータには時間だけを設定
const timings = {
duration: 3000
};
const effect = new KeyframeEffect(null, null, timings);
const animationTimer = new Animation(effect, document.timeline);
// SVG
// SVGの円の半径を調べて、円周の長さを求める
const circle = document.querySelector('.circle');
const r = circle.getAttribute('r');
const circleLength = r * 2 * Math.PI;
// SVGの円のstroke-dashoffsetとstroke-dasharray CSSプロパティに、
// 円周の長さを設定する。
circle.setAttribute('stroke-dashoffset', circleLength);
circle.setAttribute('stroke-dasharray', circleLength);
// 設定できたかをgetComputedStyle()で確認
//const dashoffset = window.getComputedStyle(circle).strokeDashoffset;
//const dashoarray = window.getComputedStyle(circle).strokeDasharray;
// stroke-dashoffsetがcircleLengthから0まで変化するアニメーション
// ただし時間は1
const keyframesCircle = {
strokeDashoffset: [circleLength, "0"]
};
const timingsCircle = {
duration: 1,
fill: 'forwards'
};
const effectCircle = new KeyframeEffect(circle, keyframesCircle, timingsCircle);
const circleAnim = new Animation(effectCircle, document.timeline);
// circleAnimはupdateイベントの発生を待ち受けている
circleAnim.addEventListener('update', (e) => {
// イベントオブジェクトから進捗値を取り出し、
// 自分のcurrentTime値に設定する。
const value = e.detail.value;
circleAnim.currentTime = value;
// 進捗値に応じてキャンバスに円を描く
drawProgressCircle(value);
}, false);
circleAnim.addEventListener('stop', (e) => {
animationTimer.cancel();
circleAnim.cancel();
}, false);
// CANVAS
const context = document.getElementById('canvas').getContext('2d');
// 初めに表示する白い円を描く
context.fillStyle = 'white'
context.arc(60, 60, 50, 0, 2 * Math.PI, false);
context.fill();
// アニメーションで使用する、途中で変更しない線やテキストの設定
context.lineWidth = 15;
context.strokeStyle = 'red';
context.textAlign = "center";
// 開始角度は270度(円の真上)
const startAngle = 270;
// 度数をラジアン数に変換
const startRadian = startAngle * (Math.PI / 180);
// 白い円を描き、進捗値に応じて円のストロークを描く
const drawProgressCircle = (progress) => {
// 現在のキャンバスをクリア
context.clearRect(0, 0, canvas.width, canvas.height);
// 塗り用のパスを開始
context.beginPath();
// 円を白で塗る
context.fillStyle = 'white'
context.arc(60, 60, 50, 0, 2 * Math.PI, false);
context.fill();
// 進捗値を描く
context.fillStyle = 'black';
const p = progress.toFixed(2);
context.fillText(p, canvas.width / 2, canvas.height / 2);
context.save();
// 進捗度に全体(360度)を掛けて、残りの割合を算出し、それに開始角度を足す
// const endRadian = (progress * Math.PI*2) + startRadian;
// 進捗値に360度を掛けて、描画する角度を求める。
const deffAngle = progress * 360;
// 度数をラジアン数に変換
const deffRadian = deffAngle * (Math.PI / 180);
// それを開始ラジアンに足して、終了ラジアンにする
const endRadian = startRadian + deffRadian;
// ストローク用のパスを開始
context.beginPath();
// 270度(円の真上)からendRadianまで円のストロークのパスを作成
context.arc(60, 60, 50, startRadian, endRadian, false);
// そのストロークを描く
context.stroke();
context.restore();
}
const update = () => {
if (animationTimer.playState === 'running') {
// 毎フレーム、timerAnimの進捗値を取得する
const progress = animationTimer.effect.getComputedTiming().progress;
// console.log(progress);
if (progress !== null) {
// circleAnimにupdateイベントとともに、進捗値を入れたオブジェクトを届ける
circleAnim.dispatchEvent(new CustomEvent('update', {
detail: {
value: progress
}
}));
}
else {
circleAnim.dispatchEvent(new CustomEvent('stop'));
}
}
}
const render = () => {
update();
lastId = window.requestAnimationFrame(render);
}
animationTimer.addEventListener('finish', (e) => {
console.log('finish');
// アニメーション終了後、1秒後にrequestAnimationFrame()への呼び出しを止める
window.setTimeout(() => {
window.cancelAnimationFrame(lastId);
}, 1000);
}, false);
animationTimer.ready.then(() => {
animationTimer.play();
render();
});