スクロール連動アニメーション、実はCSSだけでもかなり自然にできます!
スクロールに合わせて要素がふわっと現れるアニメーション、コーディングを毎日作っている弊社も何度も実装してきました。
多くの場面では IntersectionObserver
を使ったJavaScript実装が定番ですが、「そこまでしなくても良いのでは?」と感じるケースも少なくありません。
特に最近では、「とにかく軽く仕上げたい」「JSが使えない環境でも動かしたい」という要望が増えていて、CSSだけで完結する方法を模索することも増えました。
そんな中で活躍するのが、ここ数年で登場した animation-timeline
(旧称 scroll-timeline
)や :has()
といった新しいCSSの機能たちです。
この記事では、弊社が実際に使っている「JSに頼らないスクロール連動アニメーションの実装法」をご紹介します!
結論:animation-timeline
× :has()
で、JSなしのフェードインが実現可能に
まず最初に結論からお伝えします。
CSSだけでスクロール連動フェードインを実現する鍵は、次の2つの機能にあります。
1. animation-timeline
: スクロール連動のCSSアニメーション
これは、スクロール位置をトリガーとしてCSSアニメーションを再生するための新しい仕様です。
Chrome 115+ や Edge 115+ で使用可能で、将来的にSafariやFirefoxでも対応が進むと見られています。
このように、animation-timeline
と view()
を組み合わせることで、ある要素がビューポートに入った瞬間からアニメーションを開始できます。しかも、animation-range
を調整すれば、アニメーションの開始タイミングと完了タイミングも自在にコントロール可能です。
2. :has()
: 要素の状態をCSSだけで監視する新時代のセレクタ
CSSで「子要素の状態によって親要素のスタイルを変える」といった、これまでJSが必須だった表現も、:has()
によって可能になりました。
たとえば、以下のような使い方で、疑似的なスクロール連動表現も実現できます。
もちろんこれは厳密な「スクロールトリガー」ではありませんが、特定の状態(クラスの付与など)をCSSで検知して反応する構造が組めるのは大きな進化です。
それでは、実際に:has()
やanimation-timeline
を用いた「JSレスのアニメーション基本形」について解説していきます。
まずは基本形: :has()
× opacity
でCSSだけの“なんちゃってフェードイン”
まずは比較的対応範囲の広いCSS構文から。
「ページを下にスクロールしてきたとき、要素がふわっと表示されるようにしたい」という場面で、IntersectionObserver
を使わず、CSSだけである程度再現できる手法として注目されているのが、:has()
擬似クラスの活用です。
:has()
とは?
element:has(child)
のような構文で、「ある条件を満たす子要素があるとき、親要素にスタイルを適用する」というCSSセレクタ。
これはまさに“親要素が子の状態を監視する”ような働きをします。
たとえば以下のようなCSS:
section:has(.is-visible) .item {
opacity: 1;
transform: none;
}
これにより、.is-visible
というクラスが何らかのトリガーで付与されたとき、その親セクション内の .item
に対してスタイルが切り替わります。
どうやって「スクロール連動」にするのか?
ここで使うのが position: sticky
や top
の制御です。
CSSだけでは「今、画面に見えているかどうか」を直接検出することはできませんが、sticky
や擬似クラスとの組み合わせで、「画面内に来たら何かが起こる」という挙動をなんとなく演出することが可能です。
例:
※ :in-view
はまだ一部ブラウザのみ対応の疑似クラス(今後に期待)
完全ではないが、“それっぽく”は可能
この方法は、JavaScriptのようにピンポイントで「画面内に入ったときだけ処理を走らせる」という厳密な制御はできませんが、以下のような状況では実用に耐えます。
- 表示タイミングに多少のズレがあっても許容されるUI
- 軽量化を優先したLPやキャンペーンページ
- 「静的ページだけどちょっと動きを加えたい」時
ブラウザ対応状況
:has()
は現時点(2025年4月)で Safari、Chrome、Edge は対応済み- Firefox は未対応だが、将来的なサポート予定あり
- モバイルSafariにも対応しており、スマホ案件でも使える
実務Tips:transition
を使ってふわっと感を出す
要素に opacity
+ transform: translateY()
を使い、transition
を組み合わせると、JSなしでもそれっぽくなります。
.is-visible
はJSで付けてもよいですが、ユーザー操作やCSSアニメーションでも可能です。あくまで軽量に済ませたい場合の応急処置として使えます。
モダンな手法:animation-timeline
を使った本格的なスクロール連動フェードイン
「CSSだけでスクロール連動アニメーションをやりたい」
この願いを本気で叶えるのが、animation-timeline
(旧名:scroll-timeline)です。
これは、スクロール位置をアニメーションの進行度として扱える新しいCSS仕様です。
いよいよ2023年中頃からChrome・Edgeで安定実装され、実務でも使える場面が出てきました。
基本構文:スクロールをアニメーションのトリガーにする
以下が最もシンプルな記述例です。
各プロパティの意味
プロパティ | 意味 |
---|---|
@keyframes | アニメーションの内容(今回はフェード+下から) |
animation-timeline: view() | この要素がビューポートに入るのをトリガーとしてアニメーション進行 |
animation-range | アニメーションがいつ始まり、いつ終わるかを指定 |
具体的な挙動イメージ
- スクロールして
.item
が画面の下端に出現したときにアニメーションがスタート(entry 0%
) - その後、要素が画面内で30%を占める位置に達したときにアニメーション終了(
cover 30%
)
つまり、スクロールとともにじわじわアニメーションが進む感覚です。
しかも、完全にCSSだけで。
view-timeline の応用
一部の高度な例では、要素ごとに view-timeline-name
を指定し、複数のトリガーを制御することもできます。
これにより、複数のタイムラインを使い分ける高度なアニメーションも可能になります。
SPAやセクションごとに演出を変えたい場合に有効です。
使用上の注意点
- 2025年4月時点では、Chrome系とSafari Technology Previewのみ対応(Firefoxは未対応)
animation-range
のチューニングには多少慣れが必要- 未対応ブラウザでは何も起こらない → フォールバックを意識した設計が重要
現場Tips:「初期非表示」のままにせず自然に出す
opacity: 0
のまま固定されてしまうバグを防ぐために、アニメーションが効かない環境では必ず opacity: 1
に戻すCSSを設定しておくのがベストです:
これにより、非対応環境でも「無事に表示される」状態がキープされ、レイアウトの破綻を防げます。
💡 実務での使いどころ
- Chrome/Edgeベースの管理画面や社内ツールなど → 十分実用可能
- 公共サイトや不特定多数向けのUI → フォールバック必須
- モバイル優先のプロダクト → まだ様子見が無難
対応状況とフォールバック戦略
前述した通り、animation-timeline
を使ったスクロールアニメーションは魅力的ですが、すべてのユーザーに保証された体験ではないというのが現状です。
とはいえ、実務においては「対応していないなら使わない」ではなく、対応しているなら積極的に使い、対応していない場合に備えるという考え方が重要です。
このような設計アプローチをProgressive Enhancement(段階的強化)と呼びます。
CSSでフォールバックを書くのが基本
未対応ブラウザでは animation-timeline
の指定そのものが無視されるため、初期状態のCSSだけが適用される形になります。
このときに、最低限でも要素が表示される状態を維持することが重要です。
.item {
opacity: 1; /* デフォルトで表示されるように */
transform: none;
}
@supports (animation-timeline: view()) {
.item {
opacity: 0;
transform: translateY(2rem);
animation: fadeIn linear;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
}
このように @supports
を使えば、対応ブラウザだけでスクロール連動アニメーションを有効化し、他の環境では静的表示にフォールバックさせることができます。
JS版との併用も視野に(必要なら)
案件によっては、スクロールアニメーションがどうしても必要な要件であることもあります。
その場合、まずはCSSベースで構築し、非対応ブラウザでは JS(IntersectionObserver
)で上書き実装するという選択肢もあります。
if (!CSS.supports('animation-timeline: view()')) {
// IntersectionObserver でアニメーション処理を追加
}
ただし、CSSとJSで二重実装になるため、コストと保守性には注意が必要です。
モダンブラウザのみをターゲットにできる環境であれば、まずはCSSのみで十分対応可能です。
実務での判断ポイント
- 基本表示(opacity: 1)さえ確保されていれば、アニメーションなしでもUXは成立
- アニメーションはあくまで装飾。必須機能にならないように設計する
- スマホ中心のサービスでは、対応状況を必ずチェック
prefers-reduced-motion
も忘れずに
ユーザーのOS設定でアニメーションを抑える設定(視覚的負担を軽減したいユーザー向け)をしている場合は、それに従うべきです。
@media (prefers-reduced-motion: reduce) {
.item {
animation: none !important;
transition: none !important;
}
}
これはアクセシビリティの観点でも非常に重要で、一部の自治体・公共系サイトでは必須要件となっているケースもあります。
サンプルコードとライブデモ
ここでは、実際に使える2つの実装例を紹介します。
- CSSだけで軽量に実装できる基本版
animation-timeline
を使った本格スクロール連動版
それぞれの特性に応じて、実務に活かせる形に落とし込んでいます。
パターン①:軽量なフェードイン(CSS + :has + クラス制御)
この場合、.is-visible
はJSやCMS側から付けてもOK。CSSだけでもクラスの状態を使って擬似的にフェードイン風を実現できます。
「完全にJSレスにする」よりも、JSを使うとしても最小限に抑える、という考え方に近いです。
パターン②:animation-timeline
を使ったスクロール連動アニメーション
このスタイルを適用すると、.scroll-fade
クラスが付いた要素が画面下から入ってくるような自然な動きで表示されます。
ブラウザ対応さえクリアしていれば、非常に滑らかで軽量なUIが作れます。
ライブデモを作るときの構成ポイント
- 1画面に複数の
.item
を並べて、連続でスクロールフェードインさせる - 視認性を上げるために
box-shadow
やborder-radius
で装飾を追加 - スマホでも確認しやすいように縦方向のレイアウトに調整
また、動作確認はブラウザの検証ツールで「モバイル表示」+「ゆっくりスクロール」が鉄則です。
実務Tips:JSなしでも自然に見せるための工夫
CSSだけでスクロール連動のアニメーションを実装する場合、「表示されるタイミング」と「動きの自然さ」が重要になります。
ただ単にフェードインするだけでは、見せ方として弱い・違和感が出ることもあります。
この章では、JSを使わずにアニメーションを“自然に・心地よく”見せるための工夫や注意点を紹介します。
1. transform: translateY()
を併用して“動き”を加える
ただ opacity: 0 → 1
の変化だけでは、動きがのっぺりしがちです。
そこで、Y軸方向への軽いスライドインを組み合わせると、フェード感+立体感が加わり、より自然な演出になります。
アニメーション中に transform
を変えることで、GPUレンダリングが効きやすくなり、パフォーマンス面でも優秀です。
2. アニメーションは一度だけでよい(繰り返しに注意)
スクロールイン・アウトで何度も出たり消えたりするUIはユーザーのストレスになります。
基本的に、一度表示されたらそのままにしておくのがベストです。
CSSだけで一度限りの制御は難しいですが、以下のような設計で「一度表示されたら非表示に戻らない」挙動が実現できます。
- 一度
.is-visible
クラスを付けたら除去しない animation-fill-mode: both;
を指定してアニメーション終了後の状態を保持する
3. ユーザー設定(prefers-reduced-motion
)を尊重する
モーションに敏感なユーザーのために、アニメーションを抑制する設定も取り入れておくと、アクセシビリティが一段階アップします。
このひと手間があるだけで、自治体案件・医療系サイトなどの品質要件も満たせることが多いです。
4. 初期状態の設計が重要(レイアウト崩れの防止)
フェードイン前の状態で height: 0
や display: none
などを使うと、出現時にレイアウトが跳ねたりズレたりするリスクがあります。opacity
と transform
のみによるアニメーションなら、空間の確保はそのままなので揺れが起きにくいのがメリット。
visibility
の制御を併用すると、アクセシビリティ的にもより適切です。
実務導入の考え方まとめ
ポイント | 内容 |
---|---|
自然な動き | transform + opacity をセットで使う |
一度だけ表示 | animation-fill-mode やクラス制御で「戻らない」ように |
配慮ある設計 | prefers-reduced-motion への対応はマスト |
崩れ防止 | display: none は避け、スペースを維持したままフェード |
最後に:スクロールアニメーションも“CSSだけでやってみる”時代へ
「スクロールで要素がフェードインする」――
今やWeb UIの定番とも言えるこの演出ですが、JavaScriptを使わず、CSSだけでもかなりのレベルまで表現できる時代になってきました。
今回紹介したCSSだけのアプローチを振り返る
手法 | 特徴 | 向いているケース |
---|---|---|
:has() + クラス制御 | 疑似的に出現演出が可能 / 対応ブラウザ多め | 軽量なLP、簡易UI、JSレス志向の案件 |
animation-timeline | スクロールに連動した本格的なアニメーション | Chrome/Edge前提のアプリ・管理画面など |
@supports + prefers-reduced-motion | フォールバックやアクセシビリティ対応に有効 | 実務での品質担保に必須の設計 |
これらを組み合わせれば、「ユーザー体験を損なわずに、実装コストを下げる」という、非常に現実的なアプローチが可能になります。
プロの現場での考え方:完璧よりも“最適解”を選ぶ
今回のようなアニメーションは「必須機能」ではなく「演出」です。
つまり、対応していない環境で「表示されない」ことが致命的にはなりません。
だからこそ、以下のような判断軸が重要になります。
- モバイルファースト or 対応範囲が広い案件 →
:has()
のようなベーシックCSSで十分 - モダンブラウザ前提 or パフォーマンス重視 →
animation-timeline
を積極活用 - 高品質・高要求なUI → JS+CSSの併用でリッチに仕上げる
最小限のコードで、最大限の表現を引き出す。
そんな設計こそが、今のフロントエンドに求められる“質のいいコーディング”だと考えます。
ぜひ“使わないJS”を考えるトレーニングに
スクロールアニメーション=JavaScriptという思考にとらわれず、
「このUI、本当にJSが必要?」と一度立ち止まってみる。
そんな視点を持つだけで、設計の引き出しが一段と増えます。
あなたの次の案件に、このCSSだけのフェードイン技術、ぜひ取り入れてみてください。
次回もお楽しみに!