49
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptのrequestAnimationFrameでアニメーションを自由自在に操る

Last updated at Posted at 2019-02-12

はじめに

Webページをアニメーションさせようとしたときに、選択肢として楽なのは

  • CSSアニメーション
  • jQueryアニメーション

この辺だろうか。

これらは、以下の情報を指定することで、途中の状態をよしなにやってもらうものです。

  • 始点
  • 終点
  • 時間
  • 動かし方

ただ今回は、よしなにやってもらうのではなく、途中の状態も自分で指定する!という自由自在への道を開こうと思います。

自由自在の道にも更なる選択肢

よし、じゃあJavaScriptで自由自在アニメーションを実装しよう!ってなったときに、どういう風にするかっていうのざっくり言うと、

  • ループを回す
  • 経過時間や距離によって状態を算出し、要素に反映する

っていう感じに進むわけです。

じゃあ早速ループを回そー!・・・ん、ループ?
さあ、ここでループの回し方が複数あって迷うっていうイベントが発生します。

僕らを悩ませるループ

さあさあみんなで声を揃えてループを回しましょう。
わかるわかる、setTimeoutとかsetIntervalやろ?
ってなったあなたには朗報です。

ここに出でし、アニメーションループの申し子。
requestAnimationFrame

使うしかない。

setIntervalとrequestAnimationFrameの違い

まあまあ違う箇所はありますが、アニメーションとして注目すべきは
こいつらに指定したメソッドが叩かれるタイミングです。

setIntervalの場合

指定時間毎

requestAnimationFrameの場合

ブラウザの描画タイミング毎

指定時間と描画タイミング毎だと、どっちの方がよりアニメーションに適しているかは一目瞭然かと思います。
描画のタイミングで状態を変更する方が、圧倒的に良さげですね。

実装

ということで、requestAnimationFrameを使って実装していくわけです。

今回の出演者は、以下の二名です。

  • ループを司るもの: Updater
  • 要素の状態を司るもの: Animator

ではでは、中身を見ていきましょう。

まずはUpdater

class Updater {

  constructor() {
    this.targets = [];
  }

  // アニメーション対象を追加
  add(target) {
    this.targets.push(target);
  }

  // アニメーション対象を削除
  remove(target) {
    this.targets = this.targets.filter(element => element !== target);
  }

  // アニメーションループ開始
  start() {
    this.targets.forEach(element => element.start());
    window.requestAnimationFrame(this.update.bind(this));
  }

  // 各アニメーション対象に更新を通知
  update() {
    window.requestAnimationFrame(this.update.bind(this));
    this.targets.forEach(element => element.update());
  }
}

こんな感じ。

次に、Animator

class Animator {

  constructor() {
    this.startTime = 0;
  }

  // アニメーション開始時の時間を取得
  start() {
    this.startTime = performance.now();
  }

  // 描画毎にここが呼ばれる
  update() {
    // ここで経過時間をもとに進捗を算出し、要素に反映
  }

  // 進捗算出
  // e: easingType
  // t: currentTime
  // b: startValue
  // c: endValue
  // d: totalTime
  getProgress(e, t, b, c, d) {
    let progress = 0;

    if (t < 0) return b;
    if (t > d) return c;

    switch (e) {
      case 'linear':
        progress = c * t / d + b;
        break;
      case 'easeInSine':
        progress = -c * Math.cos(t / d * this.PI) + c + b;
        break;
      default:
        break;
    }
    return progress;
  }
}

こんな感じ。

あとは、

  • Updater, Animatorのインスタンスを作成
  • UpdaterにAnimatorのインスタンスをaddする
  • Updaterをstartする

という感じで準備が完了です。

Animatorのupdateメソッド内に、アニメーションの内容を書きます。

例えば、

update() {
  const elapsed = performance.now() - this.startTime
      , opacity = this.getProgress('easeInSine', elapsed, 0, 1, 1000);

  $('#js-content').css('opacity', opacity);
}

といった形で、1000秒かけてopacityを0→1にするアニメーションができました。

このように、経過時間を使って進捗を算出し、要素に反映する。
単純、だけどかなり柔軟に作り込むことができます。

はいっ、自由自在なアニメーションのできあがりー!

49
40
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?