純CSSスクロール出現(animation-timeline: view())

animation-timeline: view() を使い、JavaScript無しで複数カードをビューポート進入時にopacity+translateYで出現させます。@supportsでガードし非対応ブラウザでは普通に表示。

#css#scroll#scroll-driven

ライブデモ

使用例(お題: SaaS FlowDesk)

この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- FlowDesk 機能一覧。純CSS(view())で各機能カードがスクロール進入時に出現 -->
<div class="cdr-scroller">
  <header class="cdr-hero">
    <p class="cdr-kicker">FlowDesk / FEATURES</p>
    <h1>毎日の業務を、なめらかに。</h1>
    <p class="cdr-lead">チームの仕事をひとつにまとめる<br>4つのコア機能をご紹介します。</p>
    <span class="cdr-arrow" aria-hidden="true">&#8595;</span>
  </header>

  <section class="cdr-list">
    <article class="cdr-card">
      <span class="cdr-ico" aria-hidden="true">&#128203;</span>
      <h2>スマートボード</h2>
      <p>案件・タスクをドラッグで整理。担当と期限がひと目でわかります。</p>
      <span class="cdr-num">01</span>
    </article>
    <article class="cdr-card">
      <span class="cdr-ico" aria-hidden="true">&#9889;</span>
      <h2>ワークフロー自動化</h2>
      <p>条件に合えば通知・割り当てを自動実行。手作業のミスを減らします。</p>
      <span class="cdr-num">02</span>
    </article>
    <article class="cdr-card">
      <span class="cdr-ico" aria-hidden="true">&#128202;</span>
      <h2>リアルタイム分析</h2>
      <p>進捗と負荷をダッシュボードで可視化。意思決定が速くなります。</p>
      <span class="cdr-num">03</span>
    </article>
    <article class="cdr-card">
      <span class="cdr-ico" aria-hidden="true">&#128274;</span>
      <h2>権限とセキュリティ</h2>
      <p>役割ごとのアクセス制御と監査ログ。安心して全社展開できます。</p>
      <span class="cdr-num">04</span>
    </article>
  </section>
</div>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --navy: #0f1b34;
  --blue: #4f7cff;
  --white: #ffffff;
}

body {
  font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", sans-serif;
  background: var(--navy);
  color: var(--white);
  -webkit-font-smoothing: antialiased;
}

/* 内部スクロール領域(view()の基準) */
.cdr-scroller {
  width: 100%;
  height: 100vh;
  max-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--blue) transparent;
  scroll-behavior: smooth;
}

.cdr-hero {
  height: 260px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
  padding: 0 22px;
  background:
    radial-gradient(circle at 25% 15%, rgba(79,124,255,.4), transparent 55%),
    radial-gradient(circle at 80% 90%, rgba(79,124,255,.22), transparent 55%),
    var(--navy);
}
.cdr-kicker { letter-spacing: .3em; font-size: .64rem; color: #9db5ff; }
.cdr-hero h1 { font-size: 1.8rem; font-weight: 800; }
.cdr-lead { font-size: .86rem; line-height: 1.7; color: #aeb9da; }
.cdr-arrow { font-size: 1.4rem; color: var(--blue); animation: cdrBob 1.6s ease-in-out infinite; }
@keyframes cdrBob {
  0%, 100% { transform: translateY(0); opacity: .6; }
  50% { transform: translateY(8px); opacity: 1; }
}

.cdr-list {
  max-width: 540px;
  margin: 0 auto;
  padding: 34px 24px 90px;
  display: grid;
  gap: 18px;
}
.cdr-card {
  position: relative;
  padding: 22px 22px 22px 70px;
  border-radius: 16px;
  background: rgba(255,255,255,.045);
  border: 1px solid rgba(79,124,255,.2);
  box-shadow: 0 18px 40px -28px rgba(0,0,0,.9);
}
.cdr-ico {
  position: absolute;
  left: 20px; top: 22px;
  font-size: 1.6rem;
  line-height: 1;
}
.cdr-num {
  position: absolute;
  right: 18px; top: 18px;
  font-weight: 800;
  font-size: .8rem;
  color: rgba(157,181,255,.5);
}
.cdr-card h2 { font-size: 1.1rem; font-weight: 800; margin-bottom: 6px; }
.cdr-card p { font-size: .86rem; line-height: 1.7; color: #b9c4e2; }

/* 対応ブラウザのみ:純CSSのスクロール駆動アニメ */
@supports (animation-timeline: view()) {
  .cdr-card {
    /* 初期は透明+下ずらし。view進入で復帰 */
    opacity: 0;
    transform: translateY(40px) scale(.97);
    animation: cdrReveal linear both;
    animation-timeline: view();
    /* 枠の下端手前から中央までで出現を完了させる */
    animation-range: entry 5% cover 32%;
  }
  @keyframes cdrReveal {
    to { opacity: 1; transform: translateY(0) scale(1); }
  }
}

/* 動きを減らす設定では常時表示 */
@media (prefers-reduced-motion: reduce) {
  .cdr-card { opacity: 1 !important; transform: none !important; animation: none !important; }
  .cdr-arrow { animation: none; }
}
JavaScript
// FlowDesk 機能一覧:出現演出そのものは純CSS(animation-timeline: view())。
// JSは「操作前でも演出が見えるように」枠内を軽く自動スクロールするだけ。
(() => {
  const scroller = document.querySelector('.cdr-scroller');
  if (!scroller) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return; // 動きを減らす設定では自動スクロールしない

  let auto = true;
  const stop = () => { auto = false; };
  // ユーザー操作が入ったら自動送りを止める
  ['wheel', 'touchstart', 'pointerdown'].forEach(ev =>
    scroller.addEventListener(ev, stop, { passive: true }));

  setTimeout(function step() {
    if (!auto) return;
    const max = scroller.scrollHeight - scroller.clientHeight;
    if (scroller.scrollTop >= max - 1) return; // 最下部で停止
    scroller.scrollTop += 1.6;
    requestAnimationFrame(step);
  }, 700);
})();

コード

HTML
<!-- 純CSSのスクロール出現。JS無し・内部にスクロール領域を作る -->
<div class="cdr-scroller">
  <header class="cdr-hero">
    <p class="cdr-kicker">CSS SCROLL-DRIVEN</p>
    <h1>JS不要で出現</h1>
    <p class="cdr-lead">animation-timeline: view() で<br>ビューポート進入をCSSだけで検知</p>
    <span class="cdr-arrow" aria-hidden="true">&#8595;</span>
  </header>

  <section class="cdr-list">
    <article class="cdr-card">
      <span class="cdr-num">01</span>
      <h2>view() タイムライン</h2>
      <p>要素がスクロール枠に入る進捗に合わせてアニメを駆動します。</p>
    </article>
    <article class="cdr-card">
      <span class="cdr-num">02</span>
      <h2>JavaScriptゼロ</h2>
      <p>IntersectionObserver無しでフェードアップを実現できます。</p>
    </article>
    <article class="cdr-card">
      <span class="cdr-num">03</span>
      <h2>@supports でガード</h2>
      <p>非対応ブラウザでは初期状態のまま普通に表示されます。</p>
    </article>
    <article class="cdr-card">
      <span class="cdr-num">04</span>
      <h2>滑らかで軽量</h2>
      <p>コンポジタ側で動くため、メインスレッドを塞ぎません。</p>
    </article>
  </section>
</div>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --bg: #0d1117;
  --ink: #eef1ff;
  --accent: #5eead4;
  --accent2: #818cf8;
}

body {
  font-family: "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

/* 内部スクロール領域(プレビュー枠内で完結) */
.cdr-scroller {
  width: 100%;
  height: 100vh;
  max-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scroll-behavior: smooth;
}

.cdr-hero {
  height: 280px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  text-align: center;
  background:
    radial-gradient(circle at 25% 15%, rgba(129,140,248,.4), transparent 55%),
    radial-gradient(circle at 80% 85%, rgba(94,234,212,.32), transparent 55%),
    var(--bg);
}
.cdr-kicker { letter-spacing: .34em; font-size: .68rem; color: var(--accent); }
.cdr-hero h1 { font-size: 2.3rem; font-weight: 800; letter-spacing: .02em; }
.cdr-lead { font-size: .88rem; line-height: 1.6; color: #b7bce0; }
.cdr-arrow { font-size: 1.5rem; animation: cdrBob 1.6s ease-in-out infinite; }
@keyframes cdrBob {
  0%, 100% { transform: translateY(0); opacity: .6; }
  50% { transform: translateY(8px); opacity: 1; }
}

.cdr-list {
  max-width: 560px;
  margin: 0 auto;
  padding: 36px 24px 90px;
  display: grid;
  gap: 22px;
}
.cdr-card {
  position: relative;
  padding: 26px 26px 26px 64px;
  border-radius: 16px;
  background: rgba(255,255,255,.045);
  border: 1px solid rgba(255,255,255,.09);
  box-shadow: 0 18px 40px -28px rgba(0,0,0,.9);
}
.cdr-num {
  position: absolute;
  left: 22px; top: 26px;
  font-weight: 800;
  font-size: 1.1rem;
  background: linear-gradient(120deg, var(--accent), var(--accent2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.cdr-card h2 { font-size: 1.12rem; margin-bottom: 6px; }
.cdr-card p { font-size: .9rem; line-height: 1.6; color: #c2c6e4; }

/* 対応ブラウザのみ:純CSSのスクロール駆動アニメ */
@supports (animation-timeline: view()) {
  .cdr-card {
    /* 初期は透明+下ずらし。view進入で復帰 */
    opacity: 0;
    transform: translateY(40px) scale(.97);
    animation: cdrReveal linear both;
    animation-timeline: view();
    /* 枠の下端手前から中央までで出現を完了させる */
    animation-range: entry 5% cover 32%;
  }
  @keyframes cdrReveal {
    to { opacity: 1; transform: translateY(0) scale(1); }
  }
}

/* 動きを減らす設定では常時表示 */
@media (prefers-reduced-motion: reduce) {
  .cdr-card { opacity: 1 !important; transform: none !important; animation: none !important; }
  .cdr-arrow { animation: none; }
}
JavaScript
// 出現演出そのものは純CSS(animation-timeline: view())。
// JSは「操作前でも演出が見えるように」枠内を軽く自動スクロールするだけ。
(() => {
  const scroller = document.querySelector('.cdr-scroller');
  if (!scroller) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return; // 動きを減らす設定では自動スクロールしない

  let auto = true;
  const stop = () => { auto = false; };
  // ユーザー操作が入ったら自動送りを止める
  scroller.addEventListener('wheel', stop, { passive: true });
  scroller.addEventListener('touchstart', stop, { passive: true });
  scroller.addEventListener('pointerdown', stop);

  setTimeout(function step() {
    if (!auto) return;
    const max = scroller.scrollHeight - scroller.clientHeight;
    if (scroller.scrollTop >= max - 1) return; // 最下部で停止
    scroller.scrollTop += 1.6;
    requestAnimationFrame(step);
  }, 700);
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「純CSSスクロール出現(animation-timeline: view())」の効果を追加してください。

# 追加してほしい効果
純CSSスクロール出現(animation-timeline: view())(スクロール演出)
animation-timeline: view() を使い、JavaScript無しで複数カードをビューポート進入時にopacity+translateYで出現させます。@supportsでガードし非対応ブラウザでは普通に表示。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 純CSSのスクロール出現。JS無し・内部にスクロール領域を作る -->
<div class="cdr-scroller">
  <header class="cdr-hero">
    <p class="cdr-kicker">CSS SCROLL-DRIVEN</p>
    <h1>JS不要で出現</h1>
    <p class="cdr-lead">animation-timeline: view() で<br>ビューポート進入をCSSだけで検知</p>
    <span class="cdr-arrow" aria-hidden="true">&#8595;</span>
  </header>

  <section class="cdr-list">
    <article class="cdr-card">
      <span class="cdr-num">01</span>
      <h2>view() タイムライン</h2>
      <p>要素がスクロール枠に入る進捗に合わせてアニメを駆動します。</p>
    </article>
    <article class="cdr-card">
      <span class="cdr-num">02</span>
      <h2>JavaScriptゼロ</h2>
      <p>IntersectionObserver無しでフェードアップを実現できます。</p>
    </article>
    <article class="cdr-card">
      <span class="cdr-num">03</span>
      <h2>@supports でガード</h2>
      <p>非対応ブラウザでは初期状態のまま普通に表示されます。</p>
    </article>
    <article class="cdr-card">
      <span class="cdr-num">04</span>
      <h2>滑らかで軽量</h2>
      <p>コンポジタ側で動くため、メインスレッドを塞ぎません。</p>
    </article>
  </section>
</div>

【CSS】
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --bg: #0d1117;
  --ink: #eef1ff;
  --accent: #5eead4;
  --accent2: #818cf8;
}

body {
  font-family: "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

/* 内部スクロール領域(プレビュー枠内で完結) */
.cdr-scroller {
  width: 100%;
  height: 100vh;
  max-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scroll-behavior: smooth;
}

.cdr-hero {
  height: 280px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  text-align: center;
  background:
    radial-gradient(circle at 25% 15%, rgba(129,140,248,.4), transparent 55%),
    radial-gradient(circle at 80% 85%, rgba(94,234,212,.32), transparent 55%),
    var(--bg);
}
.cdr-kicker { letter-spacing: .34em; font-size: .68rem; color: var(--accent); }
.cdr-hero h1 { font-size: 2.3rem; font-weight: 800; letter-spacing: .02em; }
.cdr-lead { font-size: .88rem; line-height: 1.6; color: #b7bce0; }
.cdr-arrow { font-size: 1.5rem; animation: cdrBob 1.6s ease-in-out infinite; }
@keyframes cdrBob {
  0%, 100% { transform: translateY(0); opacity: .6; }
  50% { transform: translateY(8px); opacity: 1; }
}

.cdr-list {
  max-width: 560px;
  margin: 0 auto;
  padding: 36px 24px 90px;
  display: grid;
  gap: 22px;
}
.cdr-card {
  position: relative;
  padding: 26px 26px 26px 64px;
  border-radius: 16px;
  background: rgba(255,255,255,.045);
  border: 1px solid rgba(255,255,255,.09);
  box-shadow: 0 18px 40px -28px rgba(0,0,0,.9);
}
.cdr-num {
  position: absolute;
  left: 22px; top: 26px;
  font-weight: 800;
  font-size: 1.1rem;
  background: linear-gradient(120deg, var(--accent), var(--accent2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.cdr-card h2 { font-size: 1.12rem; margin-bottom: 6px; }
.cdr-card p { font-size: .9rem; line-height: 1.6; color: #c2c6e4; }

/* 対応ブラウザのみ:純CSSのスクロール駆動アニメ */
@supports (animation-timeline: view()) {
  .cdr-card {
    /* 初期は透明+下ずらし。view進入で復帰 */
    opacity: 0;
    transform: translateY(40px) scale(.97);
    animation: cdrReveal linear both;
    animation-timeline: view();
    /* 枠の下端手前から中央までで出現を完了させる */
    animation-range: entry 5% cover 32%;
  }
  @keyframes cdrReveal {
    to { opacity: 1; transform: translateY(0) scale(1); }
  }
}

/* 動きを減らす設定では常時表示 */
@media (prefers-reduced-motion: reduce) {
  .cdr-card { opacity: 1 !important; transform: none !important; animation: none !important; }
  .cdr-arrow { animation: none; }
}

【JavaScript】
// 出現演出そのものは純CSS(animation-timeline: view())。
// JSは「操作前でも演出が見えるように」枠内を軽く自動スクロールするだけ。
(() => {
  const scroller = document.querySelector('.cdr-scroller');
  if (!scroller) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return; // 動きを減らす設定では自動スクロールしない

  let auto = true;
  const stop = () => { auto = false; };
  // ユーザー操作が入ったら自動送りを止める
  scroller.addEventListener('wheel', stop, { passive: true });
  scroller.addEventListener('touchstart', stop, { passive: true });
  scroller.addEventListener('pointerdown', stop);

  setTimeout(function step() {
    if (!auto) return;
    const max = scroller.scrollHeight - scroller.clientHeight;
    if (scroller.scrollTop >= max - 1) return; // 最下部で停止
    scroller.scrollTop += 1.6;
    requestAnimationFrame(step);
  }, 700);
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。