🚀

anime.jsでシークバーに連動する数値アニメーションを実装した

2025/03/09に公開

前書き

作りたいものの説明

私は今、生産シミュレーションアプリを作成している。生産設備を配置しそれぞれを線で結ぶことで、生産ラインを形成する。ラインの中を人が行き来して、製品をひとつずつ作り上げる様をシミュレーションする。

解決したかったこと

シミュレーションの機能として再生や停止機能を設けた。シークバーを設けて任意の位置から再生できるようにした。このほかにも現在の生産数を表示させるようにしたい。シークバーに連動して、その値が動くようにする。

そのやり方がわかったので、ここで説明する。

anime.jsとは何か

軽量なJSアニメーションライブラリ。CSSやDOM、JSオブジェクトを対象にしてアニメーションを作成することができる。実装も簡単で、ぬるぬる動かせる。良い。

https://animejs.com/

これを用いて数値をアニメーションにしたい。

シークバーの実装

animeインスタンスの説明

animeインスタンスを作成し、その戻り値を使用することで、アニメーションをつくることができる。

let animation = anime({
  targets: '#ob1',
  translateX: 300,
  easing: "linear",
  duration: 4000
}

こうすると対象となる要素が右に300px移動する。変化の仕方や変化時間なども指定できる。

タイムラインの概要

複数のanimeインスタンスをひとつのタイムラインに載せることで、各種アニメーションをひとつの時間軸上で表現できる。動画を繋ぎ合わせる作業に似ている。

シークバーで連動するアニメーション

シークバーはinputタグのtypeオプションをrangeに変更することで表現できる。そして、その値が変わった時のイベントを設定する。

<input class="progress" step=".001" min="0" max="100" value="0" type="range"></input>
let controlsProgress = document.querySelector("input.progress");

let tl = anime.timeline({
      easing: "linear",
      autoplay: false,
      update: function () {
        controlsProgress.value = tl.progress;
      },
    });

controlsProgress.oninput = function () {
      tl.seek(tl.duration * (controlsProgress.value / 100));
    };

update とは対象が更新された時にコールバック関数を呼び出す方式。

seekメソッドはtimeline上の任意の時刻の状態を呼び出す。シークバーを動かすと、その時点の描画状態が表現される。

対象を数値にすると動かない

SVGオブジェクトは問題なくアニメーションさせることができた。しかし、生産数というJSオブジェクトを対象にするとうまく動かない。

はじめはこのサンプルを参考にした。コールバックを呼び出す条件をupdateとして、それをタイムライン上に表現した。

https://animejs.com/documentation/#update

<div id="count1">生産数</div>

// 先ほどのコードからの続き

 function getCountObjectByUpdate(countSize) {
  let count = {
    "生産数(update)": countSize,
  };
  let JSobjectProp = anime({
    targets: count,
    totalCount: countSize,
    easing: "linear",
    autoplay: false,
    round: 1,
    value: countSize,
    update: function () {
      var el = document.querySelector("#count1");
      el.innerHTML = JSON.stringify(count);
    },
  });

  return JSobjectProp;
}

tl.add(getCountObjectByUpdate(0),0);
tl.add(getCountObjectByUpdate(1),1000);
tl.add(getCountObjectByUpdate(2),2000);
tl.add(getCountObjectByUpdate(3),3000);

tl.addとすることで、タイムラインにアニメーションを追加させる。第二引数はオフセット値である。タイムラインの挿入場所を今回は絶対値で指定している。単位はミリ秒である。

このように実装すると、シークバーを動かした際に、数値が正しく変化しない。はじめに正方向に動かすと順調に生産数が増加するか、逆方向に動かすといきなり0になる。どういうことだ。

こうしたら動いた

updateではなくchangeを使う。

let JSobjectProp = anime({
    targets: count,
    totalCount: countSize,
    easing: "linear",
    autoplay: false,
    round: 1,
    value: countSize,
    change: function () {
      var el = document.querySelector("#count2");
      el.innerHTML = JSON.stringify(count);
    },
  });

なぜできたのか

change は、プロパティの値が変更されたタイミングで呼び出されるコールバックである。そのため、シークバーを移動させたときにも適切に値が更新され、意図した動作になったと考えられる。

updateの説明は、ドキュメントには以下のように書いてある。

Callback triggered on every frame as soon as the animation starts playing.

アニメーションが開始されてから毎フレームごとに呼び出すとある。しかし、これは説明と動作が異なる可能性がある。

現に、こういうIssueも挙げられている。
https://github.com/juliangarnier/anime/issues/774

原因はよくわからない。考えられるとすればコールバックのタイミングがドキュメントの説明とは異なるのか。実際は毎フレームではなく、内部状態から比較して変異があれば動作する、ということなのだろうか。可能性の一つとして考えられる。

想定される動きではないことは確かである。

コードのまとめ

全体のコードを以下に載せておく。
https://codepen.io/kyokucho1989/pen/azbwxBZ

Discussion