記事一覧

24種類のよく使うスクロールアニメーション、実際に動かして理解する

フェードインからスクロールスパイまで、よく使うスクロールアニメーション24種類を名前ごとにまとめ、記事の中ですぐに動かせるようにしました。コードも一緒に見ることができます。

目次

    ランディングページの独創的なスクロール効果に出会ったとき、名前がわからず関連情報の検索やAIの活用に苦労することが多くあります。そこで、実務でよく使われるスクロールアニメーション24種を名前ごとに分類し、この投稿を通じて各効果を直接確認し体験できるように構成しました。

    登場効果 01–08

    該当要素が画面に入るタイミングで1回再生される効果です。主にIntersectionObserverで要素の表示有無を検知し、CSS transitionを使ってアニメーションを実装します。

    01 フェードイン · Scroll-triggered fade-in

    該当要素が画面に入ると、下から上へなめらかに浮かび上がりながらフェードインします。

    01 フェードイン

    IntersectionObserverでビューポートへの進入を検知して is-visible クラスを付与し、要素のアニメーションは CSS transition(opacity と transform)で処理する方法をおすすめします。scrollイベントを毎フレーム監視しないため性能負荷が少なく、ランディングページやブログのカードなど様々な場所で効果的に使えます。ただし、JavaScriptの実行エラー時に要素が表示されないおそれがあるため、初期非表示のスタイルはJSが有効な環境のみに適用されるよう、クラス(.js)の配下で定義するのがより安定した実装方法です。

    02 スライドイン · Slide-in

    該当要素が左右の外側からスライドして入ってきます。

    02 スライドイン

    各方向ごとにtranslateXの初期値だけを変えて設定し、IntersectionObserverで要素が画面に入ったらクラスを付与して元の位置(transform: none)に戻るように実装してください。左右を交互に配置すると、タイムラインや機能紹介セクションに視覚的なリズム感を加えられます。ただし移動距離が大きいと横スクロールバーが出ることがあるため、親要素に overflow-x: clip を設定することをおすすめします。

    03 スタッガー · Stagger animation

    カードのまとまりが一度に表示されず、順番に一つずつ現れています。

    03 スタッガー

    各カードにCSS変数 --i で順番を指定し、transition-delay: calc(var(--i) * 80ms) を適用して遅延時間を設定してください。グリッド全体がビューポートに入ったときに親要素へクラスを付与する方式なので、コードが簡潔になるという利点があります。ただし項目が多いと遅延が積み重なり、最後のカードの表示が遅れることがあるため、全体の所要時間が1秒前後になるよう間隔を調整するか、上限を設けることをおすすめします。

    04 テキストリビール · Text reveal (split text)

    見出しを文字単位に分割し、下から順番に現れるように実装してください。

    04 テキストリビール

    JavaScriptで文字列の各文字をspanタグで包み、順次的な遅延時間とともに overflow: hidden が設定されたコンテナの中で translateY(100%) から 0 へ移動するアニメーションを実装できます。ヒーローセクションの強調フレーズに適した手法です。ただし、テキストを分割するとスクリーンリーダーが文字単位で読み上げる現象が起きるため、文全体には aria-label を設定し、各spanには aria-hidden="true" を適用してアクセシビリティを確保する必要があります。

    05 マスクリビール · Clip-path reveal

    画像がカーテンが開くように、clip-pathで片側から現れます。

    05 マスクリビール

    clip-path: inset(0 100% 0 0) で右側を完全に隠した後、画面に入ったときに inset(0 0 0 0) へ切り替えて左側から表示されるように実装します。画像自体の変形や位置移動が起きないためレイアウトの安定性が高く、ポートフォリオのサムネイルや記事のメイン画像に適しています。補間をなめらかに保つためには、開始値と終了値の関数形(inset同士、またはpolygonの頂点数など)を揃えて設定する必要がある点に注意してください。

    06 ブラーイン · Blur-in reveal

    ぼやけていた要素がピントが合うように鮮明になりながら現れます。

    06 ブラーイン

    filter: blur(12px) と opacity 0 から始めて、画面に入ったときに blur(0) と opacity 1 へ切り替える効果です。写真や大きなタイポグラフィに幻想的な演出を加えられ、画像の遅延読み込み時のプレースホルダーからの切り替えにも使えます。ただし blur 効果はピクセル単位での再描画が必要なため、大きな要素に多数適用すると低スペック端末で性能が落ちることがあります。要素の数と大きさを考慮して慎重に使ってください。

    07 タイプライター · Typewriter effect

    文字がカーソルと一緒に一文字ずつタイピングされるように表示されます。

    07 タイプライター

    setTimeoutを使って一定間隔で文字列を一文字ずつ出力し、カーソルの点滅効果はCSS keyframesで別途実装する方式をご提案します。ヒーローセクションやターミナル風のフレーズ演出に適しています。ハングルは Array.from で分割すると完成形の単位で処理されるため、絵文字などのサロゲートペア文字が崩れるのを防げます。また、再開時には以前のタイマーを必ず整理して、テキストが重なる問題を予防する必要があります。

    08 カウントアップ · Count-up animation

    数字が画面に入ると、0から目標の値までなめらかに上がっていきます。

    08 カウントアップ

    requestAnimationFrameで各フレームの経過時間の割合(0〜1)を算出し、easeOutCubicなどのイージング関数を適用して現在の値を求めたあと、toLocaleString('ko-KR')で千の位の区切りカンマを表示します。時間基準で計算するため、モニターのリフレッシュレートに関係なく一定の速度で動作し、数字の幅の変化によるレイアウトの揺れを防ぐために font-variant-numeric: tabular-nums プロパティの適用をおすすめします。

    スクロールに連動する効果 09–16

    スクロール位置を連動させて実現するインタラクションの方式です。スクロール位置を0から1の間の進行率に変換し、サイズ、位置、色を算出する仕組みです。最新ブラウザのCSS Scroll-driven Animation(animation-timeline)を使えば、JavaScriptなしでも実装できます。

    09 スクロール連動 · Scroll-linked animation

    読んだ分だけ、画面最上部の進行バーが満たされていきます。

    09 スクロール連動

    スクロール位置(0〜1)をアニメーションの進行度に同期させる方法です。CSSの場合はanimation-timeline: scroll(root) プロパティを使ってキーフレームをスクロールに連動させ、非対応ブラウザでは scrollY / (scrollHeight - innerHeight) の計算でscaleXの値を求めます。これは読了率や段階表示のUI実装に適しています。性能の最適化のため、widthプロパティを変更するとレイアウトの再計算が起きるので、transformプロパティの利用をおすすめします。

    10 パララックス · Parallax scrolling

    遠くにある層はゆっくり、近くにある層は速く動いて、奥行き感を生み出します。

    10 パララックス

    多層のレイヤーを重ね、各レイヤーごとに異なる係数をスクロール進行率に掛けて translateY で移動させる方式です。遠景の背景には低い係数を適用して速度を落とし、近景の前景には高い係数を適用してスピード感を出します。ヒーローセクションやストーリーテリング型のページの実装に向いています。ただし、過度な動きはユーザーに疲労感を与えることがあるため、prefers-reduced-motion の設定時には機能を無効にすることをおすすめします。

    11 スクロールズーム · Scale on scroll

    小さなカードの中の画像が、スクロールに合わせて画面いっぱいに大きくなります。

    11 スクロールズーム

    sticky で固定したステージ内で、進行率に応じて scale と border-radius を補間します。サイズの変化は width/height ではなく transform: scale で処理すると、レイアウトの再計算がなく滑らかになります。製品紹介やセクション切り替えの「没入感」を出す演出によく使われます。拡大された要素の中の文字も一緒に大きくなるため、テキストは別のレイヤーに置いたほうがよいでしょう。

    12 画像シーケンス · Scroll-scrubbed image sequence

    あらかじめ描いておいた60枚のフレームを、スクロール位置に応じて切り替えながら日の出のシーンを再生します。

    12 画像シーケンス

    フレーム画像をあらかじめ読み込み(キャンバス60枚を直接レンダリング)、「進行率 × (全フレーム数 - 1)」の計算で求めたフレームを、キャンバスに drawImage で描画して実装します。動画の currentTime を制御する方式より即座に反応し、逆再生もなめらかなので、製品の回転演出に効果的です。実務では、フレーム数が増えると画像の容量負担が生じるため、事前読み込みの最適化と解像度の調整が実装の要になります。

    13 SVG ライン描画 · SVG line drawing

    スクロールするほど、SVG の線がペンで描いたようにつながっていきます。

    13 SVG ライン描画

    path の全長を getTotalLength() で求めて stroke-dasharray に設定し、stroke-dashoffset を 全長 × (1 - 進行率) まで減らすと、線が描かれていくように見えます。経路、サイン、ダイアグラムの演出に向いています。SVG を viewBox で拡大縮小すると non-scaling-stroke などの設定によって長さの計算がずれることがあるので、その場合は pathLength="1" を使うと簡単です。

    14 テキストフィル · Text fill on scroll

    読む速度に合わせて、段落の単語が一つずつ濃く塗られていきます。

    14 テキストフィル

    文章を単語単位の span に分け、進行率 × 単語数の分だけ先頭側の単語にアクティブなクラスを付けて色を塗ります。重要なメッセージをゆっくり読ませたいランディングページによく使われます。単語を span に分けるとスクリーンリーダーが途切れて読むことがあるので、元の文を aria-label として併せて置いてください。

    15 背景色の切り替え · Background color transition

    セクションを通過するたびに、ページの背景色が次のセクションの色へ混ざるように変わります。

    15 背景色の切り替え

    各セクションに data-bg の色を設定し、画面の中央がどの二つのセクションの間にあるかを計算して、二つの色を線形補間した値を body の背景に入れます。セクションの境界がはっきり途切れないため、ブランドストーリーやポートフォリオによく合います。背景が暗くなるときに文字色も一緒に変えないとコントラストが崩れるので、前景色も同じように補間してください。

    16 マーキー · Marquee, scroll velocity

    途切れなく流れる文字の帯が、スクロールの速さに合わせて速くなり、方向によっては逆向きに流れます。

    16 マーキー

    requestAnimationFrame のループで、基本速度に「スクロールの変化量」を足して帯を動かし、位置を帯の半分の幅で割った余りで包み込むことで、無限に続くように見せます。スクロールの向きが変わったら符号を反転させると、流れも逆になります。速度の値を毎フレーム少しずつ減衰させないと、止まった後も速く回り続けるので注意してください。

    固定・スナップ・ナビゲーション 17–24

    画面の一部を固定したり、一画面ずつ送ったり、今読んでいる位置を知らせたりする手法です。position: sticky や scroll-snap のような CSS だけでできるものが多くあります。

    17 スティッキー · position: sticky

    セクション見出しは、自分のセクションを通過している間、画面の上に貼りついたままになります。

    17 スティッキー

    position: sticky は、普段は通常の要素と同じように流れますが、指定した top の値に達すると、親ブロックの中でだけ固定されます。親が終わると次の見出しが押し上げるように入れ替わるため、リストの見出しや表のヘッダーによく合います。JS はまったく不要です。祖先要素に overflow: hidden/auto があると、その要素がスクロールの基準になってしまい、sticky が動かないように見えるので注意してください。

    18 ピン + スクラブ · Pin + scrub

    ステージは画面に固定され、スクロールした分だけ中のアニメーションが前後に再生されます。

    18 ピン + スクラブ

    長いラッパー(例: 300vh)の内側に position: sticky の要素を配置すると、その領域を通過している間、固定(ピン)の効果が適用されます。ラッパーの getBoundingClientRect().top の値を使って 0〜1 の進行度を求め、それを transform などに直接代入すると、スクロールバーを再生ヘッド(スクラブ)のように制御できます。これは GSAP ScrollTrigger の pin/scrub の原理と同じです。スクロールイベントでは requestAnimationFrame を使い、レイアウトの読み取りを一度にまとめて性能を最適化してください。ラッパーの高さが短すぎるとアニメーションがすぐ終わってしまうため、コンテンツの段階に合った高さを設定してください。

    19 スクロールテリング · Scrollytelling

    左の本文のステップが画面の中央に来ると、右側の固定グラフィックがそのステップに合わせて切り替わります。

    19 スクロールテリング

    グラフィック領域は position: sticky を使って固定し、各コンテンツ段階ごとに IntersectionObserver を適用してください。rootMargin を '-50% 0px -50% 0px' に設定すると、画面中央を通過する単一の段階だけが有効になり、別途スクロール計算をしなくても状態を切り替えられます。これは、データジャーナリズムや製品紹介のように説明と視覚資料を組み合わせる構成に効果的です。ただし、段階同士の間隔が狭すぎると状態が頻繁に切り替わることがあるので、各段階に十分な高さを確保してください。

    20 カードスタック · Stacking cards

    カードが上に重なるように積み上がり、下に敷かれたカードは少しずつ小さくなります。

    20 カードスタック

    各カードに position: sticky と少しずつ異なる top の値を設定すると、次のカードが前のカードの上にせり上がって積み重なります。奥行き感は JS で加えます。次のカードがどれくらい近づいてきたかの割合を求め、下のカードに scale を適用します。カードはすべて同じ親の中に兄弟要素として置く必要があり、親の高さが終わるとスタック全体も一緒に上へ動いてしまいます。カードごとに別のラッパーで包むと sticky の範囲がラッパー内に狭まり、積み重なりません。ご注意ください。

    21 スクロールスナップ · CSS scroll snap

    一度スクロールすると、パネルが画面ぴったりの大きさで止まります。

    21 スクロールスナップ

    スクロールコンテナに scroll-snap-type: y mandatory を、各パネルに scroll-snap-align: start を設定すると、スクロールが終わった時点でブラウザが最も近いパネルに合わせて止まります。JS なしでフルページのセクションや画像のカルーセルを作れ、タッチやキーボード操作もそのまま動きます。mandatory はパネルが画面より長いと内側の内容が飛ばされてしまうことがあるため、長いパネルには proximity の利用を検討してください。ドットの表示だけは JS で現在のパネルを読み取って更新しています。

    22 横スクロール · Horizontal scroll

    縦にスクロールすると、固定された舞台の中のトラックが横へ移動します。

    22 横スクロール

    ピン + スクラブと同じ構造です。長いラッパーの中の sticky ステージに横方向のトラックを置き、進行度 p に (トラック幅 − 画面幅) を掛けて translateX で動かします。ラッパーの高さを「横移動距離 + 画面の高さ」に合わせると、縦スクロールの速さと横移動の速さが同じになり、自然に見えます。トラックに overflow: hidden を使う代わりに、ステージに overflow: clip か hidden を使ってください。ただし sticky 要素自身の祖先には設定しないよう注意してください。

    23 隠れるヘッダー · Hide-on-scroll header

    下にスクロールするとヘッダーが隠れ、少しでも上にスクロールするとすぐに再表示されます。

    23 隠れるヘッダー

    スクロールイベントで前回の scrollY と現在の値を比べて方向を求め、下方向なら translateY(-100%) を与えるクラスをヘッダーに付けます。transform だけを変えるのでレイアウトの再計算が起きず、滑らかに動きます。画面の狭いモバイルで読む領域を広げたいときに役立ちます。小さな揺れでチラつかないように数ピクセルのしきい値を設け、ページの一番上付近では常に表示されるようにしてください。

    24 スクロールスパイ · Scrollspy

    横の目次が、いま読んでいるセクションに合わせてハイライトされます。

    24 スクロールスパイ

    各本文セクションに IntersectionObserver を適用し、画面上部基準(rootMargin '0px 0px -70% 0px')に入ったセクションの ID と一致する目次リンクにアクティブ用のクラスを付けます。目次は position: sticky を使って横に固定します。これはドキュメントやブログの右側目次でよく使われる方法です。セクションが短く検出領域をすり抜けてしまう場合に備えて、最後に入ったセクションを覚えておくと、アクティブ状態が外れてしまうのを防げます。

    AIに作ってもらうとき

    名前と一緒に実装方法を具体的に指定していただければ、より正確で完成度の高い結果を得られます。

    • CSSだけで: "スクロール進行バーを CSS animation-timeline だけで作って"
    • GSAP ScrollTrigger で: "セクションをピン固定して、スクラブで段階的なアニメーションを作って"
    • Framer Motion で: "カードがスタッガーでフェードインするように useInView で作って"

    最後に、モーションは控えめなほど洗練された印象になります。1ページあたりのアニメーション効果は2〜3個に抑え、prefers-reduced-motion を有効にしているユーザーには、動かさずに最終状態だけを表示してください。本文のデモalso この原則に従って制作しています。

    コメント 0

    1. コメントを読み込んでいます。