【Javascript】で全てのアニメーションを停止・再開する方法:【TypeScript】を使用

【Javascript】で全てのアニメーションを停止・再開する方法:【TypeScript】を使用

2024-08-08

2024-08-13

JavaScriptを使ってWebページ上の全てのアニメーションを一時停止し、再開する方法についてご紹介します。この方法は、CSSアニメーション、JavaScriptライブラリ、そしてタイマー関連のアニメーションなど、を対象としています。

目的

Webページ上のアニメーションを一時停止し、再開する機能は、特にデバッグやユーザーエクスペリエンスの向上に役立ちます。この記事では、以下のアニメーションを一時停止・再開する方法を説明します。

  • CSSアニメーションとトランジション
  • JavaScriptライブラリのアニメーション
  • タイマー関連のアニメーション(setInterval、requestAnimationFrame)

実装の概要

まず、グローバル変数を定義して、元の関数やアニメーションの状態を保存します。その後、アニメーション停止・再開のための関数を作成します。

グローバル変数の定義

これらの変数は、アニメーションの状態を追跡するために使用します。

(window as any).isAnimationStopped = false;
(window as any).originalRAF = window.requestAnimationFrame;
(window as any).originalSetTimeout = window.setTimeout;
(window as any).originalSetInterval = window.setInterval;
(window as any).originalClearInterval = window.clearInterval;
(window as any).originalCancelAnimationFrame = window.cancelAnimationFrame;
(window as any).originalDate = Date;
(window as any).stoppedAnimations = new Set<Function>();
(window as any).intervals = [];
(window as any).animationFrames = [];

setIntervalとclearIntervalのラップ

setIntervalとclearIntervalのオリジナル関数をラップして、すべてのタイマーIDを追跡します。

(window as any).setInterval = function (
  callback: TimerHandler,
  delay?: number,
  ...args: any[]
): number {
  const id = (window as any).originalSetInterval(callback, delay, ...args);
  (window as any).intervals.push({ id, callback, delay });
  return id;
};

(window as any).clearInterval = function (id: number) {
  (window as any).originalClearInterval(id);
  const index = (window as any).intervals.findIndex(
    (interval: { id: number }) => interval.id === id
  );
  if (index > -1) {
    (window as any).intervals.splice(index, 1);
  }
};

requestAnimationFrameとcancelAnimationFrameのラップ

同様に、requestAnimationFrameとcancelAnimationFrameもラップして、アニメーションフレームIDを追跡します。

(window as any).requestAnimationFrame = function (
  callback: FrameRequestCallback
): number {
  const id = (window as any).originalRAF(callback);
  (window as any).animationFrames.push(id);
  return id;
};

(window as any).cancelAnimationFrame = function (id: number) {
  (window as any).originalCancelAnimationFrame(id);
  const index = (window as any).animationFrames.indexOf(id);
  if (index > -1) {
    (window as any).animationFrames.splice(index, 1);
  }
};

CSSアニメーションとトランジションの停止

CSSアニメーションとトランジションを一時停止するためのスタイルを追加します。

function stopCSSAnimations() {
  const style = document.createElement("style");
  style.innerHTML = `
    * {
      animation-play-state: paused !important;
      transition: none !important;
    }
  `;
  document.head.appendChild(style);
}

jQueryアニメーションの停止

jQueryを使用している場合、すべてのアニメーションを停止します。

function stopJQueryAnimations() {
  if ((window as any).jQuery) {
    (window as any).jQuery("*").stop(true, true);
  }
}

タイマー関連のアニメーションの停止

setIntervalとrequestAnimationFrameを停止します。

function stopIntervalAndRAFAnimations() {
  (window as any).intervals.forEach(({ id }: { id: number }) =>
    (window as any).originalClearInterval(id)
  );
  (window as any).intervals = [];

  (window as any).animationFrames.forEach((id: number) =>
    (window as any).originalCancelAnimationFrame(id)
  );
  (window as any).animationFrames = [];
}

JavaScriptアニメーションライブラリの停止

一般的なアニメーションライブラリのメソッドを呼び出して停止します。

function stopJSAnimations() {
  ["pause", "stop", "freeze"].forEach((methodName) => {
    document.querySelectorAll("*").forEach((el: any) => {
      if (typeof el[methodName] === "function") {
        try {
          el[methodName]();
        } catch (e) {
          console.warn(`Failed to call ${methodName} on element`, el, e);
        }
      }
    });
  });
}

すべてのアニメーションを停止する関数

すべての停止関数を呼び出して、アニメーションを一時停止します。

function stopAllAnimations() {
  (window as any).isAnimationStopped = true;

  stopCSSAnimations();
  stopJSAnimations();
  stopJQueryAnimations();
  stopIntervalAndRAFAnimations();

  window.dispatchEvent(new CustomEvent("animationsStopped"));
}

すべてのアニメーションを再開する関数

アニメーションを再開するための関数です。

function resumeAllAnimations() {
  (window as any).isAnimationStopped = false;

  document.querySelectorAll("*").forEach((element: Element) => {
    const el = element as HTMLElement;
    el.style.removeProperty("animation-play-state");
    el.style.removeProperty("transition");
  });

  window.requestAnimationFrame = (window as any).originalRAF;
  window.setTimeout = (window as any).originalSetTimeout;
  window.setInterval = (window as any).originalSetInterval;
  Date = (window as any).originalDate;

  (window as any).stoppedAnimations.forEach((resumeFunc: Function) =>
    resumeFunc()
  );
  (window as any).stoppedAnimations.clear();

  ["resume", "play", "start", "unfreeze"].forEach((methodName) => {
    document.querySelectorAll("*").forEach((el: any) => {
      if (typeof el[methodName] === "function") {
        try {
          el[methodName]();
        } catch (e) {
          console.warn(`Failed to call ${methodName} on element`, el, e);
        }
      }
    });
  });

  window.dispatchEvent(new CustomEvent("animationsResumed"));
}

ボタンのイベントリスナー

ボタンをクリックすると、アニメーションを停止または再開します。

document.getElementById("stopButton")?.addEventListener("click", () => {
  if ((window as any).isAnimationStopped) {
    resumeAllAnimations();
  } else {
    stopAllAnimations();
  }
});

全ての実装

上記すべてをまとめたものです。

  // グローバル変数の定義
  (window as any).isAnimationStopped = false;
  (window as any).originalRAF = window.requestAnimationFrame;
  (window as any).originalSetTimeout = window.setTimeout;
  (window as any).originalSetInterval = window.setInterval;
  (window as any).originalClearInterval = window.clearInterval;
  (window as any).originalCancelAnimationFrame = window.cancelAnimationFrame;
  (window as any).originalDate = Date;
  (window as any).stoppedAnimations = new Set<Function>();
  (window as any).intervals = [];
  (window as any).animationFrames = [];

  // setIntervalをラップしてグローバルにIDを保持
  (window as any).setInterval = function (
    callback: TimerHandler,
    delay?: number,
    ...args: any[]
  ): number {
    const id = (window as any).originalSetInterval(callback, delay, ...args);
    (window as any).intervals.push({ id, callback, delay });
    return id;
  };

  // clearIntervalをラップしてグローバル配列から削除
  (window as any).clearInterval = function (id: number) {
    (window as any).originalClearInterval(id);
    const index = (window as any).intervals.findIndex(
      (interval: { id: number }) => interval.id === id
    );
    if (index > -1) {
      (window as any).intervals.splice(index, 1);
    }
  };

  // requestAnimationFrameをラップしてグローバルにIDを保持
  (window as any).requestAnimationFrame = function (
    callback: FrameRequestCallback
  ): number {
    const id = (window as any).originalRAF(callback);
    (window as any).animationFrames.push(id);
    return id;
  };

  // cancelAnimationFrameをラップしてグローバル配列から削除
  (window as any).cancelAnimationFrame = function (id: number) {
    (window as any).originalCancelAnimationFrame(id);
    const index = (window as any).animationFrames.indexOf(id);
    if (index > -1) {
      (window as any).animationFrames.splice(index, 1);
    }
  };

  // CSSアニメーションとトランジションを停止する関数
  function stopCSSAnimations() {
    const style = document.createElement("style");
    style.innerHTML = `
      * {
        animation-play-state: paused !important;
        transition: none !important;
      }
    `;
    document.head.appendChild(style);
  }

  // jQueryアニメーションを停止する関数
  function stopJQueryAnimations() {
    if ((window as any).jQuery) {
      (window as any).jQuery("*").stop(true, true);
    }
  }

  // タイマー関連のアニメーションを停止する関数
  function stopIntervalAndRAFAnimations() {
    // すべてのsetIntervalをクリア
    (window as any).intervals.forEach(({ id }: { id: number }) =>
      (window as any).originalClearInterval(id)
    );
    (window as any).intervals = [];

    // すべてのrequestAnimationFrameをキャンセル
    (window as any).animationFrames.forEach((id: number) =>
      (window as any).originalCancelAnimationFrame(id)
    );
    (window as any).animationFrames = [];
  }

  // JavaScriptアニメーションライブラリの停止を試みる関数
  function stopJSAnimations() {
    ["pause", "stop", "freeze"].forEach((methodName) => {
      document.querySelectorAll("*").forEach((el: any) => {
        if (typeof el[methodName] === "function") {
          try {
            el[methodName]();
          } catch (e) {
            console.warn(`Failed to call ${methodName} on element`, el, e);
          }
        }
      });
    });
  }

  // すべてのアニメーションを停止する関数
  function stopAllAnimations() {
    (window as any).isAnimationStopped = true;

    stopCSSAnimations();
    stopJSAnimations();
    stopJQueryAnimations();
    stopIntervalAndRAFAnimations();

    // カスタムイベントをディスパッチ
    window.dispatchEvent(new CustomEvent("animationsStopped"));
  }

  // すべてのアニメーションを再開する関数
  function resumeAllAnimations() {
    (window as any).isAnimationStopped = false;

    // CSSアニメーションとトランジションを再開
    document.querySelectorAll("*").forEach((element: Element) => {
      const el = element as HTMLElement;
      el.style.removeProperty("animation-play-state");
      el.style.removeProperty("transition");
    });

    // オリジナルの関数を復元
    window.requestAnimationFrame = (window as any).originalRAF;
    window.setTimeout = (window as any).originalSetTimeout;
    window.setInterval = (window as any).originalSetInterval;
    Date = (window as any).originalDate;

    // 停止したアニメーションを再開
    (window as any).stoppedAnimations.forEach((resumeFunc: Function) =>
      resumeFunc()
    );
    (window as any).stoppedAnimations.clear();

    // JavaScriptアニメーションライブラリの再開を試みる
    ["resume", "play", "start", "unfreeze"].forEach((methodName) => {
      document.querySelectorAll("*").forEach((el: any) => {
        if (typeof el[methodName] === "function") {
          try {
            el[methodName]();
          } catch (e) {
            console.warn(`Failed to call ${methodName} on element`, el, e);
          }
        }
      });
    });

    // カスタムイベントをディスパッチ
    window.dispatchEvent(new CustomEvent("animationsResumed"));
  }

  // アニメーション停止ボタンのイベントリスナー
  document.getElementById("stopButton")?.addEventListener("click", () => {
    if ((window as any).isAnimationStopped) {
      resumeAllAnimations();
    } else {
      stopAllAnimations();
    }
  });

下記のようなボタンを追加で試すことができると思います。

<button id="stopButton" class="stop-button">アニメーションを停止</button>

まとめ

以上が、JavaScriptを使用してWebページ上のすべてのアニメーションを一時停止し、再開する方法の概要です。これにより、開発中のデバッグや特定のユーザーインタラクションの際に、アニメーションを制御することが容易になります。

Recommend