gsap

GSAPコールバックの使い方|onComplete・onUpdate・onRepeatを解説

GSAPのonStart、onUpdate、onRepeat、onComplete、onReverseComplete、onInterruptの発火タイミングと使い分けを解説します。引数の渡し方、eventCallback、Timeline.call、Promise、再生・逆再生・中断時の注意点までサンプル付きで確認できます。

この記事の目次
  1. GSAPコールバックの発火タイミング一覧
  2. onStart:開始時に1回実行する
  3. delay中には発火しない
  4. onUpdate:進捗を毎tick受け取る
  5. onUpdate内で重い処理をしない
  6. onRepeat:繰り返しの境界で実行する
  7. yoyoでもrepeat cycleとして数える
  8. onComplete:最後まで終わった後に実行する
  9. 関数をその場で実行しない
  10. callbackへ引数を渡す
  11. onReverseComplete:逆再生で先頭へ戻ったとき
  12. onInterrupt:完了前の中断を扱う
  13. eventCallback()で作成後に変更する
  14. Timeline全体のcallbackを設定する
  15. Timeline.call()で途中に関数を置く
  16. addPause()で正確に一時停止する
  17. Promiseのthen()とonCompleteの使い分け
  18. restart・reverse・seek時の注意点
  19. ScrollTriggerのcallbackとは分けて考える
  20. component破棄時はcleanupする
  21. よくあるエラー
  22. onCompleteがすぐ実行される
  23. onRepeatが発火しない
  24. onCompleteが発火しない
  25. onUpdateで画面が重くなる
  26. callbackを複数登録したのに1つしか動かない
  27. まとめ

GSAPでは、TweenやTimelineの開始・更新・繰り返し・完了に合わせて関数を実行できます。最もよく使うのはonStartonUpdateonRepeatonCompleteです。

gsap.to(".box", {
  x: 240,
  duration: 1,
  repeat: 1,
  onStart: () => console.log("start"),
  onUpdate: () => console.log("update"),
  onRepeat: () => console.log("repeat"),
  onComplete: () => console.log("complete"),
});

この例では、初回再生の開始時にonStart、playheadが進むたびにonUpdate、2周目へ入るときにonRepeat、すべて終わったときにonCompleteが発火します。repeat: 1は「初回 + 1回」の合計2回再生です。

GSAPコールバックの発火タイミング一覧

callback 発火するタイミング 主な用途
onStart animationが開始するとき 状態表示、操作の一時無効化
onUpdate playheadが動いて描画更新されるたび 進捗表示、別の値との同期
onRepeat 新しいrepeat cycleへ入るたび 周回数の表示、cycleごとの処理
onComplete 順方向の再生が最後まで完了したとき 次の処理、UI状態の確定
onReverseComplete 逆再生で先頭へ戻り切ったとき 閉じるanimation後の状態復元
onInterrupt 完了前にanimationが中断されたとき 中断時のcleanup、状態の整合

callbackはanimationの「時間」を監視します。DOMのtransitionendanimationendとは別の仕組みで、GSAPが管理するplayheadの状態に応じて実行されます。

onStart:開始時に1回実行する

onStartは、animationのtimeが0から進み始めるときに発火します。

const button = document.querySelector(".start-button");

gsap.to(".box", {
  x: 240,
  duration: 1,
  onStart: () => {
    button.disabled = true;
    button.textContent = "再生中";
  },
  onComplete: () => {
    button.disabled = false;
    button.textContent = "もう一度再生";
  },
});

同じTweenをrestart()すると、timeが再び0から進むためonStartも再度発火します。「Tween作成時に生涯1回だけ」とは限りません。

delay中には発火しない

delayが設定されている場合、Tweenを作成した瞬間ではなく、delayが終わって実際にplayheadが進み始めたときに発火します。作成直後の処理が必要なら、callbackの外で実行します。

onUpdate:進捗を毎tick受け取る

onUpdateは、animationのplayheadが動く描画更新ごとに呼ばれます。Tweenのprogress()はrepeatを除く現在cycleの進捗、totalProgress()はrepeatを含む全体進捗を0〜1で返します。

const progress = document.querySelector(".progress");

const tween = gsap.to(".box", {
  x: 240,
  duration: 2,
  paused: true,
  onUpdate: updateProgress,
});

function updateProgress() {
  const percent = Math.round(tween.progress() * 100);
  progress.value = percent;
  progress.textContent = `${percent}%`;
}

tween.play();

onUpdate内で重い処理をしない

onUpdateは短時間に何度も実行されます。毎回network requestを送る、巨大な配列を作る、同じDOMを検索する、consoleへ大量出力すると、animationが重くなる原因になります。

DOM参照は外で取得し、必要最小限の値だけ更新します。analytics送信や保存処理は、通常onCompleteなど回数の少ないcallbackへ置きます。

onRepeat:繰り返しの境界で実行する

onRepeatは、repeatが0より大きいとき、新しいiteration cycleへ入るたびに発火します。

const tween = gsap.to(".box", {
  x: 240,
  duration: 0.8,
  repeat: 2,
  repeatDelay: 0.3,
  onRepeat: () => {
    console.log(`iteration: ${tween.iteration()}`);
  },
  onComplete: () => {
    console.log("全3回の再生が完了");
  },
});

repeat: 2は合計3回再生です。onRepeatは初回開始時には発火せず、2周目と3周目へ入るときに発火します。

yoyoでもrepeat cycleとして数える

yoyo: trueを付けると、繰り返すたびに再生方向が交互になります。それでもcycleの境界ではonRepeatが発火します。

gsap.to(".box", {
  x: 240,
  duration: 1,
  repeat: 3,
  yoyo: true,
  onRepeat: () => console.log("方向を切り替えて次のcycleへ"),
});

onComplete:最後まで終わった後に実行する

onCompleteは、repeatを含む順方向のanimationが最後まで到達したときに発火します。

gsap.to(".dialog", {
  autoAlpha: 0,
  y: -16,
  duration: 0.25,
  onComplete: () => {
    document.querySelector(".dialog").hidden = true;
  },
});

要素を非表示にする処理を先に実行するとfade animationが見えません。見た目のanimationが終わってからDOM状態を確定したいときにonCompleteを使います。

関数をその場で実行しない

callbackには関数の「戻り値」ではなく、後から実行する関数を渡します。

function finish() {
  console.log("完了");
}

// 正しい: 関数参照を渡す
gsap.to(".box", { x: 240, onComplete: finish });

// 誤り: Tween作成時にfinish()を実行してしまう
gsap.to(".box", { x: 240, onComplete: finish() });

引数が必要ならarrow functionで包むか、専用のParams設定を使います。

callbackへ引数を渡す

最も読みやすいのはarrow functionで必要な値を渡す方法です。

function finish(id, status) {
  console.log(id, status);
}

gsap.to(".box", {
  x: 240,
  onComplete: () => finish(42, "completed"),
});

GSAPにはonCompleteParamsonUpdateParamsonRepeatParamsなどもあります。

gsap.to(".box", {
  x: 240,
  onComplete: finish,
  onCompleteParams: [42, "completed"],
});

animationを設定データとして組み立てる場合はParamsが便利です。単純な処理ならarrow functionのほうが流れを追いやすいでしょう。

onReverseComplete:逆再生で先頭へ戻ったとき

onCompleteは順方向の終端、onReverseCompleteは逆方向でtime 0へ戻り切ったときに対応します。

const tween = gsap.to(".panel", {
  xPercent: 0,
  duration: 0.4,
  paused: true,
  onComplete: () => console.log("開き切った"),
  onReverseComplete: () => console.log("閉じ切った"),
});

document.querySelector(".open").addEventListener("click", () => {
  tween.play();
});

document.querySelector(".close").addEventListener("click", () => {
  tween.reverse();
});

途中からreverse()して先頭へ戻った場合も、先頭へ到達すればonReverseCompleteが発火します。

onInterrupt:完了前の中断を扱う

animationが完了する前にkillされたなど、中断された場合はonCompleteが発火しません。完了と中断の両方で状態を戻す必要があるなら、共通関数を用意します。

function releaseUI() {
  document.body.classList.remove("is-animating");
}

const tween = gsap.to(".box", {
  x: 240,
  duration: 2,
  onStart: () => document.body.classList.add("is-animating"),
  onComplete: releaseUI,
  onInterrupt: releaseUI,
});

document.querySelector(".cancel").addEventListener("click", () => {
  tween.kill();
});

callbackの中で「必ず完了した」と仮定する処理と、中断時にも必要なcleanupを分けてください。

eventCallback()で作成後に変更する

Tween / TimelineのeventCallback()を使うと、作成後にcallbackを取得・設定・削除できます。event typeの文字列は大文字小文字を区別します。

const tween = gsap.to(".box", {
  x: 240,
  duration: 1,
  paused: true,
});

tween.eventCallback("onComplete", () => {
  console.log("後から設定したcallback");
});

const handler = tween.eventCallback("onComplete");
console.log(handler);

tween.play();

// 削除する場合
tween.eventCallback("onComplete", null);

同じevent typeには1つのcallbackだけを設定できます。後から別のonCompleteを設定すると、以前のものは上書きされます。複数処理が必要なら、1つのhandlerから複数関数を呼び出します。

Timeline全体のcallbackを設定する

TimelineのvarsにもTweenと同様のcallbackを設定できます。Timeline全体の開始・更新・完了を監視したい場合に使います。

const timeline = gsap.timeline({
  onStart: () => console.log("sequence開始"),
  onComplete: () => console.log("sequence完了"),
});

timeline
  .to(".title", { autoAlpha: 1, y: 0 })
  .to(".image", { autoAlpha: 1, scale: 1 })
  .to(".button", { autoAlpha: 1, y: 0 });

個々のTweenへonCompleteを付けるのと、Timeline全体へ付けるのは意味が違います。最後のTween構成が変わる可能性があるなら、sequence完了処理はTimeline側へ置くと保守しやすくなります。

GSAP Tweenの基本はGSAP Tweenの使い方で整理しています。

Timeline.call()で途中に関数を置く

timeline.call()は、Timelineの任意位置へcallbackを挿入します。3番目のposition parameterで実行位置を指定できます。

const timeline = gsap.timeline();

timeline
  .to(".box", { x: 200, duration: 1 })
  .call(updateLabel, ["前半完了"], ">")
  .to(".box", { rotation: 180, duration: 0.6 })
  .call(updateLabel, ["全工程完了"], ">");

function updateLabel(message) {
  document.querySelector(".status").textContent = message;
}

Timeline全体のonCompleteは終端で発火し、call()は途中を含む指定位置で発火します。animationの開始時刻を大量のdelayで管理せず、Timelineとposition parameterで関係を表現します。

addPause()で正確に一時停止する

特定位置でTimelineを止め、利用者の操作を待つ場合はaddPause()を使います。

const timeline = gsap.timeline();

timeline
  .to(".step-1", { autoAlpha: 1 })
  .addPause("+=0", () => {
    console.log("確認待ち");
  })
  .to(".step-2", { autoAlpha: 1 });

document.querySelector(".next").addEventListener("click", () => {
  timeline.resume();
});

addPause()はplayheadをpause位置へ正確に合わせます。単にcall(() => timeline.pause())と書くより、一時停止の意図が明確です。

Promiseのthen()とonCompleteの使い分け

Tween / Timelineはthen()を使って、完了後の処理をPromise chainへ接続できます。

await gsap.to(".box", {
  x: 240,
  duration: 0.6,
});

console.log("animation完了後の処理");

次のように明示しても同じ流れを表せます。

gsap.to(".box", { x: 240, duration: 0.6 })
  .then(() => saveResult())
  .catch((error) => console.error(error));

単純に完了時のUIを更新するならonComplete、複数の非同期処理を順に書くならawait / then()が読みやすい場合があります。onUpdateonRepeatの代わりにはなりません。

restart・reverse・seek時の注意点

callbackは最初の自動再生だけでなく、playhead操作でも発火し得ます。

  • restart(): 先頭へ戻して再生するため、onStartと完了系callbackが再度発火し得る
  • reverse(): 先頭へ到達するとonReverseCompleteが発火する
  • seek() / progress(): callbackを抑制する引数を利用できる場合がある
  • kill(): animationを停止・破棄し、通常の完了とは区別する

callback内でAPI送信や課金など重複できない副作用を実行する場合、animationが再生し直される可能性を考慮してください。必要ならapplication側で送信済み状態を管理します。

ScrollTriggerのcallbackとは分けて考える

ScrollTriggerにはonEnteronLeaveonEnterBackonLeaveBackなど、viewportとtrigger位置の関係で発火するcallbackがあります。一方、この記事のonStartonCompleteはTween / Timelineのplayheadを基準にします。

scrubを使うとscroll位置がplayheadを直接動かすため、通常の自動再生とは感覚が変わります。ScrollTrigger固有のstart / endやtoggleActionsはScrollTriggerの発火タイミング、scroll連動はScrollTriggerのscrubの使い方で解説しています。

component破棄時はcleanupする

SPAやcomponent内でanimationを作る場合、画面から離れた後もcallbackがDOMを更新しないようにcleanupします。GSAPのcontext()は、その中で作成したanimationをまとめてrevertできます。

const scope = document.querySelector(".feature");

const context = gsap.context(() => {
  gsap.to(".box", {
    x: 240,
    onComplete: () => console.log("完了"),
  });
}, scope);

// component破棄時
context.revert();

ReactではGSAP公式のuseGSAP() hookもcleanupを扱います。event listenerを自分で追加した場合は、それもcleanup関数でremoveしてください。

よくあるエラー

onCompleteがすぐ実行される

onComplete: finish()と書いていないか確認します。括弧を付けるとTween作成時に実行されます。onComplete: finishまたはonComplete: () => finish()とします。

onRepeatが発火しない

repeatの既定値は0です。repeat: 1以上を設定し、Tweenが途中でkill・pauseされていないか確認します。

onCompleteが発火しない

repeat: -1は無限repeatなので、順方向の最終完了へ到達しません。途中でkillされた場合も通常のonCompleteではなく中断として扱います。

onUpdateで画面が重くなる

DOM検索、layout測定、network request、console出力を毎tick実行していないか確認します。DOM参照を外へ出し、更新量を減らしてください。

callbackを複数登録したのに1つしか動かない

同じevent typeへ後から設定したcallbackは前のものを上書きします。1つのhandlerから複数の関数を呼ぶか、必要な処理をまとめます。

まとめ

GSAPのcallbackは、animationのplayheadに応じて処理を実行します。

  • 開始はonStart
  • 毎tickの進捗はonUpdate
  • repeatの境界はonRepeat
  • 順方向の完了はonComplete
  • 逆再生で先頭へ戻ったときはonReverseComplete
  • 完了前の中断はonInterrupt

作成後に変更するならeventCallback()、Timeline途中へ処理を置くならcall()、正確に一時停止するならaddPause()を使います。callbackをanimation制御の境界として使い、毎tickの重い処理や再生し直したときの副作用重複を避けることが重要です。