要素をふわっと表示したいときのScrollTriggerをめぐる冒険
前田 大地
要素がビューポート内に入ったときにふわっと表示される、あれ。
古くはjQueryプラグインのinviewとかで実装され、脱jQueryが進むとVanilla JSのIntersection Observerが使われるようになった記憶があります。
ここ数年だと、GSAPのScrollTriggerを使うのがいちばんポピュラーな方法だと思います。
ということで今回は、GSAPとScrollTriggerを使って、要素をふわっと表示させるためのサンプルコードをいくつか紹介します。
目次
方法1. ひとつの要素をふわっと表示
ページ内にふわっと表示させたい要素がひとつだけの場合、下記のように記述できます。
// 要素がひとつ gsap.set(".fadeIn", { opacity: 0, y: 30, }); gsap.to(".fadeIn", { opacity: 1, y: 0, duration: 1, scrollTrigger: { trigger: ".fadeIn", start: "top 90%", once: true, } });
「.fadeIn」という名前のclassを付与した要素がビューポートに入ったとき、ふわっと表示されます。一旦、消しておいて、その後、表示させる。基本ですね。
方法2. 複数の要素をふわっと表示
ふわっと表示させたい要素がページ内に複数あると、方法1のコードではうまくいきません。なぜなら、最初の要素が画面内に入ったとき、他のすべての要素のアニメーションが一気に開始されてしまうからです。
「gsap.utils.toArray」を使えば、それぞれの要素を独立して判定できます。
// 複数の要素 gsap.set(".fadeIn", { opacity: 0, y: 30, }); gsap.utils.toArray(".fadeIn").forEach(elm => { gsap.to(elm, { opacity: 1, y: 0, duration: 1, scrollTrigger: { trigger: elm, start: "top 90%", once: true, } }); });
これで、「.fadeIn」を付与した要素が複数あっても、あとはGSAPがうまいことやってくれます。
方法3. 複数の要素が同時に画面内に入ったとき開始タイミングをちょっとずつズラして表示
例えば、横並びの要素などが複数同時に画面内に入ったとき、まとめてふわっと表示してしまうのは味気ないので、アニメーション開始のタイミングをちょっとずつズラして、順番にふわっと表示されるような演出がしたいケースがあったとします。
そんなときは「ScrollTrigger.batch」を使いましょう。
// 順番に表示 gsap.set(".fadeIn", { opacity: 0, y: 30, }); ScrollTrigger.batch(".fadeIn", { onEnter: batch => { gsap.to(batch, { opacity: 1, y: 0, duration: 1, stagger: .2, //ズラす秒数 }); }, start: "top 90%", once: true, });
batchを使うと、同時にビューポートに入った要素を自動でグルーピングしてくれます。グルーピングされた要素に対してstaggerを指定すれば、アニメーション開始のタイミングをちょっとずつズラすことができます。
・・・ただし、この方法にはちょっとした問題があります。
方法4. 方法3に加えてbatchMaxで過剰な遅延を予防する
方法3のコードが問題になる場合とは、「.fadeIn」要素がページ内にたくさんあるときです。
ページを下のほうにスクロールした状態でページを再読み込みすると、現在のビューポートよりも上にある要素が一気にonEnter判定されます。ビューポートよりも上に「.fadeIn」の要素が100個あったら、100個に対してstaggerが適用されてしまうのです。そうなると、20秒(0.2秒×100)たたないとビューポート内の要素が表示されません。まずいですね。
過剰な遅延を回避するいちばん簡単な解決策は、「batchMax」を指定することです。
// 順番に表示(batchMaxあり) gsap.set(".fadeIn", { opacity: 0, y: 30, }); ScrollTrigger.batch(".fadeIn", { onEnter: batch => { gsap.to(batch, { opacity: 1, y: 0, duration: 1, stagger: .2, //ズラす秒数 }); }, start: "top 90%", once: true, batchMax: 10, //最大10個でグルーピング });
例えば、batchMaxを10にした場合、onEnter判定された要素をさらに10個ずつグルーピングしてくれるため、このケースだと最大でも2秒(0.2秒×10)の遅延になります。これくらいなら許容範囲ですね。
・・・しかし、batchMaxを使うことの弊害もあります。
方法5. ビューポートよりも上にある要素は除外しつつズラして表示
batchMaxは、たくさんの要素をきちんと遅延させて表示したいようなケースに不向きです。batchMaxで指定した数以上の要素があると、batchMaxを使ったせいで変なところでグルーピングされてしまい、ふわっと表示される順番が思い通りいかずに、かっちょ悪くなったりします。
とはいえ、batchMaxを使わずに過剰な遅延を防止するスマートな解決方法が私には思いつきません。
ですから、ここはもう力技で解決することにします。「ビューポートよりも上にあるか?」を要素ごとにチェックして、ビューポートよりも上にある要素は除外した上で、残った要素をズラして表示するコードを書いてみます。
// 順番に表示(画面外の要素は除外) gsap.set(".fadeIn", { opacity: 0, y: 30, }); ScrollTrigger.batch(".fadeIn", { onEnter: batch => { let i = 0; batch.forEach( elm => { // 要素がビューポートよりも上にあるか判定 const rect = elm.getBoundingClientRect(); if (rect.bottom <= 0) { // 即座に表示 gsap.set(elm, { opacity: 1, y: 0, }); } else { // ふわっと表示 gsap.to(elm, { opacity: 1, y: 0, delay: i * 0.2, duration: 1, }); i++; } }); }, start: "top 90%", once: true, });
なんか、ちょっと、ややこしくなってまいりました。この方法だとstaggerが使えないので、delayを使って、ちょっとずつズラす動きを再現します。
とりあえず期待通りに動いてくれるようになったので、これくらいで勘弁してやりましょう。
所感
要素をふわっと表示させたいだけなのに、細かいところを気にし始めると、なんだかとっても大変になりました。みなさん、これどうやって実装してるんでしょうね。細かいことは気にしないのか、もっとスマートな方法があるのか、よく分かりませんが、まあ、なんだかんだでGSAPは便利、ということで。