GSAP の ScrollTrigger.batch
を使えば、リストなどに簡単にアニメーションを設定することができる。
https://gsap.com/docs/v3/Plugins/ScrollTrigger/static.batch()/
batch でアニメーションを設定したときに、アニメーションが実行された要素に class を付けたいとか、要素に何か処理をしたい要望があったのでそのメモ
- TypeScript:
^5.5.4
- GSAP:
^3.12.5
cf. static-batch | GSAP | Docs & Learning
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
gsap.set(".listItem", {
autoAlpha: 0,
y: 50
});
ScrollTrigger.batch('.listItem', {
interval: 0.1,
start: "top-=200 center",
end: "bottom+=50 center"
once: true,
markers: true,
onEnter: (elements) => {
gsap.to(elements, {
stagger: 0.5,
autoAlpha: 1,
y: 0,
});
},
});
対象要素に class 名を付けたいだけなら attr
が便利
例えばアニメーションが始まった際に isLoaded
クラスを付け、data-status="loaded"
にするなら下記のような感じ
ScrollTrigger.batch('.listItem', {
interval: 0.1,
start: "top-=200 center",
end: "bottom+=50 center"
once: true,
markers: true,
onEnter: (elements) => {
gsap.to(elements, {
stagger: 0.5,
autoAlpha: 1,
y: 0,
attr: { class: ".listItem .isLoaded", "data-status": "loaded" }
});
},
});
⚠️ attr
の注意点
- attribute をまるっと置換してしまうので、必要なものは記述する必要があり HTML と密結合になってしまう
attr
は trigger になっている要素しか対象にしないので、子要素や親要素になにかしたい場合は対応できない (CSSで対応できる範囲でしか操作できない)
- 文字列を返す関数を設置できたが、ドキュメントには書かれてないので動作は担保されてなさそう
ScrollTrigger.batch('.listItem', {
onEnter: (elements) => {
gsap.to(elements, {
stagger: 0.5,
autoAlpha: 1,
y: 0,
attr: {
class: (_index, item) => {
const cx = item.classList.value;
return cx + ' isLoaded';
},
},
});
},
});
↑ これも動作したが、ドキュメントにこの方法は載ってないっぽいので動作保証はなさそう
🙆 1度だけ対象要素以外も操作するなら onEnter
内の gsap.to
内にイベントを設定するのが良さそう
後述するが、onToggle
は start, end を跨ぐ度に実行され、onLeave
はスクロール速度では発火しないケースがあった。
onEnter
さえすればアニメーションは発火するので、その中で操作する処理を書いてしまうのが確実かなという印象
ScrollTrigger.batch('.listItem', {
onEnter: (elements) => {
gsap.to(elements, {
stagger: 0.5,
autoAlpha: 1,
y: 0,
},
onStart: (index, item) => {
item?.classList.add('isLoaded');
item?.dataset.status = 'loaded';
const parent = item?.parentElement;
parent?.classList.add('isChildLoaded');
},
onLeave: (index, item) => {
},
onComplete: (arg: undefined) => {
},
});
},
});
onComplete
onComplete
はバッチになっているアニメーションが全て完了したら呼び出されるので便利だが、引数が無いので扱いづらい
Arrow function ではなく通常の関数にすることで this
から対象を引っ張ってくることもできるが _target
というアンダースコア付きのプロパティなので、アンチパターンではないかと思う。(操作できるにはできる)
ScrollTrigger.batch('.listItem', {
onEnter: (elements) => {
gsap.to(elements, {
stagger: 0.5,
autoAlpha: 1,
y: 0,
},
onStart: (index, item) => {
item?.classList.add('isLoaded');
item?.dataset.status = 'loaded';
const parent = item?.parentElement;
parent?.classList.add('isChildLoaded');
},
onComplete: function() {
const self = this;
const elemens = self?._targets;
if ( !Array.isArray(elemens) ) {
return;
}
elemens.forEach((el) => {
if (!el || el instanceof HTMLElement === false) {
return;
}
el.classList.add('isComplete');
el.dataset.status = 'complete';
});
}
});
},
});
🤔 要素を操作したい場合は onToggle
が使える
アニメーションが始まった際に親要素に isLoaded
クラスを付け、子要素のdata-status
属性をを loaded
にするなら下記のような感じ
ただし、onToggle
は start, end を跨いだタイミングで実行されるので 1回だけ実行させたいようなケースでは工夫が必要
ScrollTrigger.batch('.listItem', {
onEnter: (elements) => {
gsap.to(elements, {
stagger: 0.5,
autoAlpha: 1,
y: 0,
});
},
onToggle: (elements) = > {
elements.forEach((el) = > {
const parent = el.parentElement;
const child = el.firstElementChild;
parent?.classList.add("isLoaded");
child?.dataset.status = "loaded";
});
},
});
⚠️ onToggle
の注意点
onToggle
は start, end をまたぐ度に実行されるので、1度だけ操作したい・終了時に操作したい場合は扱いに注意が必要
once
のときスクロール速度が早いと onToggle
は実行されないケースがあったので確実性は微妙
🤔 対象からスクロールが出た際に何か実行するなら onLeave
公式ドキュメントに例が載っている onLeave
はバッチの対象から出る時に実行されるので、アニメーションが完了した後に要素に対して何か処理を行う際に使える
ScrollTrigger.batch('.listItem', {
onEnter: (elements) => {
gsap.to(elements, {
stagger: 0.5,
autoAlpha: 1,
y: 0,
});
},
onLeave: (elements) = > {
elements.forEach((el) = > {
el.dataset.status = "leaved";
const parent = el.parentElement;
parent?.classList.add("isLeaved");
});
},
});
⚠️ onLeave
の注意点
- スクロール速度によっては結構な確率で発火しない、感覚的に end の
onToggle
の方がまだ発火される可能性が高い気がする
onEnter
からアニメーションの時間分 setTimeout させるほうが確実性はありそう
Timeline .vars に onStart
, onComplete
の項目もあったが、ScrollTrigger.batch
では onStart
, onComplete
は発火しないっぽい (※ ScrollTrigger .vars のドキュメントが薄く vars の中身は同じだと思っていたが)
対象要素が複数なので、何をもって start なのか complete なのか判断が難しいので使えないのは分からなくもない
ScrollTrigger.batch('.listItem', {
onEnter: (elements) => {
gsap.to(elements, {
stagger: 0.5,
autoAlpha: 1,
y: 0,
});
},
onStart: (evt: any) => {
console.log('onStart', evt);
},
onComplete: (evt: any) => {
console.log('onComplete', evt);
},
});
onEnter
内で実行するのが無難
once: true
のとき、onToggle
, onLeave
はスクロール速度によっては発火されない場合がある
onEnter
, onToggle
と onLeave
, onToggle
の順番は担保されない
attr
, onToggle
の発火順も担保されてない
onToggle
は start 時と end 時に実行される
- end になる前に start を逆方向にスクロールして跨いでも再度実行される
- class 名を追加したいだけなら
attr
が便利だが、class 名をまるっと置換してしまうので注意が必要
attr
は対象の要素しか操作できないので、親や子要素など対象要素を起点に他の要素を操作したい場合は別のアプローチが必要
attr
の value に文字列を返す関数を設定できたがドキュメントに載ってないので動作が担保されるかは怪しい
- アニメーション完了時に何かをしたいのであれば
onEnter
内でアニメーションの時間分 setTimeout
させるのが無難そう (微妙だけど)
- onLoad 時に、既にアニメーションが終了しているスクロール位置の場合は、スクロール量とコンテンツの位置から GSAP を動作させず完了とさせる処理を挟むほうが良さそう
Sample
↓ スクロールを上下させると once: true
でも end に到達する前に start を跨ぐ度に onToggle
が実行されるのが確認できる
See the Pen
GSAP batch animation callback by KIKIKI (@kikiki_kiki)
on CodePen.
batch でアニメーションが開始された要素と、その要素を起点に DOM 操作したかっただけだったんだけど、結構沼だった。
そして、このあと ScrollTrigger.batch
使わなくてもできるじゃん… となってしまった訳ですが、それはまた別のお話…
おわり。゚(゚ ◜ᴗ◝゚)゚。
[参考]