スクロールすると要素が奥行きを持って立体的に重なるアニメーション

おしゃれなサイトでよく見る、スクロール中に要素が奥から手前へ重なっていくような立体的なアニメーションの実装方法について解説しています。

本記事ではperspectivetranslateZは使わず、GSAPによるtranslateYscaleの補間だけでこの演出を実現する方法を紹介します。

後続の要素が固定されてシンプルに重なる実装については下記をご覧ください。 >>スクロールすると要素が固定され後続の要素が重なるアニメーション

実装例

早速ですが下記が実装例です。

スクロールに応じて要素が立体的に重なるアニメーション

スクロールに応じてグレーのパネルが重なり、立体感を演出しています。単調なスクロールに一味違うアクセントをつけられるかもしれません。

実際にはリストになっているアイテムや画像をアニメーションさせるとより自然な演出となります。Lenisなどのスムーススクロールライブラリとの相性も良いでしょう。

解説

ここから実装の中身を見ていきます。HTMLは次のとおりです。

index.html
<ul class="list" data-wrapper>
<li class="item" data-item></li>
<li class="item item--02" data-item></li>
<li class="item item--03" data-item></li>
</ul>

ラッパーの要素にdata-wrapper属性を、その子要素にdata-item属性を設定しているシンプルなリスト要素です。

CSSは次のとおりです。

style.css
.list {
margin: 0 auto;
width: 80%;
display: grid;
row-gap: 100px;
}
.item {
width: 100%;
height: 75vh;
background-color: #ddd;
}
.item--02 {
background-color: #bbb;
}
.item--03 {
background-color: #aaa;
}

パネル要素である.itemに対して便宜上個別に高さを指定していますが、必須ではありません。高さは中の要素に依存する形でOKです。

JavaScriptでは次のコードを実行しています。事前にGSAP本体とScrollTriggerを読み込んでいます。

script.js
// 各アイテムの y / scale の補間の端点
const ANIMATION_RANGE = {
yPercentStart: 250,
yPercentEnd: 30,
scaleStart: 0.75,
scaleEnd: 0.98,
};
// 線形補間
const lerp = (a, b, t) => {
return a + (b - a) * t;
};
// 0〜1 に正規化する(例: 3件なら index=0,1,2 → t=0, 0.5, 1)
const indexToUnitProgress = (index, length) => {
if (length <= 1) return 0;
return index / (length - 1);
};
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
const wrapper = document.querySelector("[data-wrapper]");
const items = gsap.utils.toArray("[data-item]"); // 「配列」として要素を取得
items.forEach((item, index) => {
const t = indexToUnitProgress(index, items.length);
const yPercent = lerp(
ANIMATION_RANGE.yPercentStart,
ANIMATION_RANGE.yPercentEnd,
t,
);
const scale = lerp(ANIMATION_RANGE.scaleStart, ANIMATION_RANGE.scaleEnd, t);
gsap.to(item, {
y: `${yPercent}%`,
scale,
ease: "none", // イージング関数を指定しない(線形アニメーション=linear)
scrollTrigger: {
trigger: item,
endTrigger: wrapper,
start: "center center",
end: "bottom center",
scrub: true,
},
});
});
});

立体感の演出

[data-item]属性の要素をy軸方向に移動させ、サイズを縮小しています。立体感を演出するために、移動距離と縮小率は要素ごとに変えています。

奥にある要素ほど小さく・上に位置するという人間の遠近感の知覚を利用しています。perspectivetranslateZを使わず、translateYscaleの線形補間だけで擬似的にZ軸の奥行きを表現しています。

ScrollTriggerの設定

要素の中央がビューポートの中央に重なる時にアニメーションがスタートし、[data-wrapper]属性の要素の底部がビューポートの中央に重なる時にアニメーションが終了します。詳細は次の通りです。

プロパティ設定値意味
startcenter centerトリガー要素の中央がビューポートの上端から50%(=ビューポート中央)に来たとき
endbottom centerendTrigger(wrapper)の底辺がビューポート中央に来たとき
scrubtrueスクロール量とアニメーション進行をリニアに同期

演出のカスタマイズ

今回の例では3つ目のアイテムが画面中央に来る頃には1つ目のアイテムは完全に見えなくなっていますが、y軸の移動距離と縮小率を調整することで3つのアイテムの重なりを画面内で表現することも可能です。

ANIMATION_RANGEの値を変更することで、要素の移動距離と縮小率を調整できます。それぞれ開始値と終了値を設定するようになっており、各要素にはインデックスに応じた線形補間の結果としてyPercentscaleが割り当てられます。

自動的に補間値を計算するような仕組みになっているため、要素が増減する場合にも対応できます。

アクセシビリティ上の注意

今回のようなスクロールに応じて要素が重なる動きは、めまいを誘発する可能性があるアニメーションで、WCAGでもprefers-reduced-motion: reduceへの配慮が推奨されています。

実装例でもgsap.matchMedia()で分岐し、OSで「視差効果を減らす」が有効になっていない場合にのみアニメーションを実行するようにしています。prefers-reduced-motion: reduceが有効な場合には、アニメーションは行わず素のリストとして表示されます。

まとめ

  • translateY+scaleの線形補間だけで立体的な重なりを表現できる
  • ANIMATION_RANGEを調整すれば演出をカスタマイズできる
  • prefers-reduced-motionへの配慮を行うことで、めまいを誘発しないアニメーションを実現できる

Webサイトにちょっとしたアクセントをつけたい時にサクッと実装できるモーションです。

参考サイト

この記事を書いた人

写真:ブール 代表 西川雅人

Masato Nishikawa

ウェブ制作会社・デザイナー様向けに、コーディングやWordPress実装を承っているフリーランスです。繁忙期のサポートや、継続的な外注先をお探しでしたら、お気軽にお声がけください。

コーディングや
WordPress実装の
ご相談を承っています。

新規制作から運用フェーズの修正まで柔軟に対応します。
お見積りや実装可否の確認など、お気軽にご連絡ください。