GODRICH

CSS パララックス スクロール 9選 — CSSだけ・JS不要

css パララックスは、スクロールする速度と背景や文字が動く速度をわざとずらし、奥行きを感じさせるCSSの表現です。この記事では9パターンを選び、animation-timelineによるスクロール駆動アニメーションだけで組み立てました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9つの並び順は効果の派手さではなく、animation-timelineがどこを基準にスクロール量を読むかという違いです。まず一枚の背景だけを動かす単純な形から始め(01)、層を増やして遠近感を作り(02・03)、要素そのものの大きさや位置を変え(04〜06)、position: stickyや横方向への応用を加え(07と05はこれより前に置いています)、最後にポインター入力と組み合わせる形(08)とscale全体を使う形(09)を置きました。9つすべての軸になっているのはCSSのanimation-timelineプロパティで、対応表によるとChrome 115以降とSafari 26以降では有効ですが、Firefoxは2026年9月時点の安定版ではまだフラグの奥にあり、@supports (animation-timeline: scroll())の分岐が外れるとtransform: noneに戻って静止画のまま止まります。グリッドの9つはiframeの中で2秒周期のループとして最初から自動再生されていて、指でスクロールを動かすことはできません。実際のスクロールで確かめたいときはzipを開いてローカルで動かしてください。

01背景がゆっくり動く

背景ひとつだけがscroll(root)というタイムラインに合わせてtranslateYでわずか24pxだけ持ち上がるので、ページを読み切るころにようやく手前の文字との間隔が広がって見えます。LPのヒーロー背景やセクション上部のバナーに向いています。

0.4배translateY단일 레이어
.bgslow__bg {
  position: absolute;
  inset: -20% -10%;
  animation: drift linear both;
  animation-timeline: scroll(root);
}

@keyframes drift {
  from { transform: translateY(0); }
  to   { transform: translateY(-24px); }
}

023レイヤーの奥行き

奥・中・手前に置いた3つの円がそれぞれ10px・26px・46pxという別々の距離だけ動くので、同じscroll(root)を参照していても層ごとの速度差がそのまま遠近感になります。ゲーム紹介ページやブランドストーリーセクションに向いています。

0.2·0.5·0.8배세 겹translateY
.layer3__back  { animation: drift-back linear both;  animation-timeline: scroll(root); }
.layer3__mid   { animation: drift-mid linear both;   animation-timeline: scroll(root); }
.layer3__front { animation: drift-front linear both; animation-timeline: scroll(root); }

@keyframes drift-back  { from { transform: translateY(0); } to { transform: translateY(-10px); } }
@keyframes drift-mid   { from { transform: translateY(0); } to { transform: translateY(-26px); } }
@keyframes drift-front { from { transform: translateY(0); } to { transform: translateY(-46px); } }

03文字が画像の上に浮く

画像はinsetの位置からほぼ動かず14pxしか進まないのに、見出しの文字だけがtranslateYで70px先まで駆け上がり、mix-blend-mode: differenceで画像と色を反転させながら重なって通り過ぎます。マガジン風ヒーローやフォトエッセイのタイトルに向いています。

1.6배mix-blend-modetranslateY
.textimg__image {
  animation: image-drift linear both;
  animation-timeline: scroll(root);
}
.textimg__text {
  mix-blend-mode: difference;
  animation: text-drift linear both;
  animation-timeline: scroll(root);
}

@keyframes image-drift { from { transform: translateY(0); }    to { transform: translateY(-14px); } }
@keyframes text-drift  { from { transform: translateY(60px); } to { transform: translateY(-70px); } }

04ヒーロー縮小パララックス

見出しがscale(1)からscale(.55)まで縮み、同時にopacityも1から.25まで落ちるので、スクロールし終えるころには次のセクションに主役を譲るように薄く小さくなっていきます。LPのファーストビューやアプリ紹介のイントロに向いています。

scale 1→.6opacity 동반축소+페이드
.heroshrink__title {
  animation: shrink linear both;
  animation-timeline: scroll(root);
}

@keyframes shrink {
  from { transform: scale(1);    opacity: 1; }
  to   { transform: scale(.55); opacity: .25; }
}

05横タイムラインパララックス

縦方向のスクロール量をそのままtranslateXの入力として使い、カードの列を120pxぶん横へ流すので、指は縦に動かしているのに画面の中身は横のタイムラインとして読めます。沿革ページやプロダクトロードマップに向いています。

translateXscroll(root)가로 흐름
.hline__track {
  display: flex;
  gap: 14px;
  animation: travel linear both;
  animation-timeline: scroll(root);
}

@keyframes travel {
  from { transform: translateX(0); }
  to   { transform: translateX(-120px); }
}

06カードの奥行きレイヤー

重なった3枚のカードは9px・18pxとずらして配置してあり、スクロールに応じて手前のカードほど34px、後ろのカードほど47.6pxと大きく剥がれていくので、奥行きが一段ずつ見えてきます。機能紹介カードや料金プラン比較に向いています。

scale 단계별z-index 3겹translateY
.depthcards__card--1 { animation: peel-1 linear both; animation-timeline: scroll(root); }
.depthcards__card--2 { animation: peel-2 linear both; animation-timeline: scroll(root); }
.depthcards__card--3 { animation: peel-3 linear both; animation-timeline: scroll(root); }

@keyframes peel-1 { from { transform: translateY(0)   scale(1);   } to { transform: translateY(-34px)   scale(1); } }
@keyframes peel-2 { from { transform: translateY(9px)  scale(.94); } to { transform: translateY(-25px)   scale(1); } }
@keyframes peel-3 { from { transform: translateY(18px) scale(.88); } to { transform: translateY(-29.6px) scale(1); } }

07スティッキー+パララックス

position: stickyで固定した文章の後ろだけをscroll(root)で40pxずつ流すので、前景は画面に留まったまま背景だけが動き続けます。ストーリーテリングセクションや製品スペック説明に向いています。

position sticky배경만 이동고정+흐름
.stickypar__caption {
  position: sticky;
  top: 40%;
}
.stickypar__bg {
  animation: bg-drift linear both;
  animation-timeline: scroll(root);
}

@keyframes bg-drift {
  from { transform: translateY(0); }
  to   { transform: translateY(-40px); }
}

08マウス連動パララックス

ポインター位置を正規化した--mx・--myという2つのCSS変数に渡し、レイヤーごとの--depthを掛け合わせるだけで3つの円がそれぞれ違う幅で追いかけてきます。プロダクトショーケースやポートフォリオのヒーローに向いています。

pointermove레이어별 배율translate
.mousepar__layer--a { left: 18%; top: 24%; }
.mousepar__layer--b { left: 62%; top: 54%; }
.mousepar__layer--c { left: 40%; top: 68%; }

.mousepar__layer {
  transform: translate(
    calc(var(--mx, 0) * var(--depth) * 22px),
    calc(var(--my, 0) * var(--depth) * 22px)
  );
}

09スクロールでズームアウト

1.45倍に拡大された背景がscroll(root)に合わせてscale(1)まで縮んでいき、スクロールが進むほど周りのレイアウトが少しずつ現れてきます。商品詳細のファーストビューやギャラリーのイントロに向いています。

scale 1.4→1scroll(root)줌아웃
.zoomout__media {
  transform: scale(1.45);
  animation: zoom-out linear both;
  animation-timeline: scroll(root);
}

@keyframes zoom-out {
  from { transform: scale(1.45); }
  to   { transform: scale(1); }
}

どこで崩れるか — 落とし穴

9つとも動きの正体はtransformだけで、位置やサイズを直接書き換えるコードは使っていません。ひとつ目の落とし穴は06の3枚のカードを組み立てているときに踏んだもので、@keyframesの開始値をtranslateY(9px) scale(.94)のような静止状態の初期transformに合わせずに0から書いてしまうと、スクロールが始まった瞬間、カードが本来の位置から一瞬だけ飛ぶように見えました。static側の初期transformと@keyframesのfromをそろえてから、この飛びは消えました。

2つ目は07のスティッキーを組み立てたときのことです。.stickyparに背景をはみ出させないためのoverflow: hiddenを付けたところ、内側の.stickypar__captionにposition: stickyを与えても画面に張り付かず、背景と一緒にただ流れていってしまいました。原因はoverflow: hiddenを持つ祖先そのものが新しいスクロールコンテナになり、stickyが基準にする範囲がその祖先の高さに縮められてしまうことにあり、overflow: hiddenをclipのような別プロパティに置き換えると直りました。修正済みの9ファイルを開くには x6462unj という文字列が必要です。下のカードからzipを取ってください。

3つ目は05の横タイムラインで踏んだものです。animation-timeline: scroll(root)はページ全体のスクロール量を基準にするため、このセクションがページのどのあたりに置かれるかによってtranslateXが動き終わるタイミングがずれてしまい、ページの下の方に置くとカードが流れ切る前に画面から消えていきました。ページ内の特定の範囲だけを基準にしたいときはscroll(root)ではなくview()を使う書き方に切り替える必要があり、この記事の9つはどれもscroll(root)で統一して比較しやすくしています。

アクセシビリティ

9つともprefers-reduced-motion: reduceで回転や移動、拡大縮小のアニメーションが止まりますが、要素そのものは消えません。01の背景はtranslateYを止めてその場に固定され、06の3枚のカードはtranslateY(9px) scale(.94)とtranslateY(18px) scale(.88)というずらした位置のまま動かなくなり、09の背景はいちばん外側のscale(1.45)ではなく縮み切った後のscale(1)で止まるので、動きを止めた状態でも構図が完成した形で表示されます。

@media (prefers-reduced-motion: reduce) {
  .bgslow__bg, .layer3__back, .layer3__mid, .layer3__front,
  .textimg__image, .textimg__text, .heroshrink__title,
  .hline__track, .depthcards__card--1, .depthcards__card--2,
  .depthcards__card--3, .stickypar__bg, .mousepar__layer,
  .zoomout__media { animation: none !important; }
  .depthcards__card--2 { transform: translateY(9px) scale(.94); }
  .depthcards__card--3 { transform: translateY(18px) scale(.88); }
  .zoomout__media { transform: scale(1); }
}

スクロール以外のCSSアニメーションはカテゴリーページに一覧があり、このサイトが何を目指しているかは紹介ページにまとめています。

FAQ

9つを同じページに並べても動きが干渉しませんか

干渉しません。それぞれのアニメーションは自分のクラス名に結びついた@keyframesとanimation-timelineしか参照しておらず、01の背景と09の背景を隣り合わせに置いても、片方のscroll(root)がもう片方の進み方を変えることはありません。conic-gradientのような共有リソースも使っていないため、9つ同時に設置しても見た目の競合は起きません。

05はなぜ縦にスクロールしているのに横へ動くのですか

animation-timeline: scroll(root)は、スクロールバーの向きに関係なく、ページ全体を読み終えるまでの進み具合を0%から100%の一本の数値として渡すだけの仕組みだからです。05はそのタイムラインをtranslateXの@keyframesに結びつけているため、指は縦に動かしていてもtransformの軸はXになり、カードの列は横方向のタイムラインとして流れて見えます。

08のマウス連動パララックスはスマホでも同じように動きますか

指で触れている間だけは08と同じ計算が働きますが、常にカーソル位置を送り続けるパソコンとは違い、スマホは指を離すと--mx・--myの更新が止まってしまいます。01から07と09はscroll(root)というスクロール量そのものを基準にしているため、指でスワイプしている間はスマホでもパソコンと変わらない速さで動きます。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。