CSSのScroll-driven Animation

横スクロールがある場合の判定表示Sample


概要

- CSSの `Scroll-driven Animation` を使うことで、JavaScriptを使わずにスクロールをトリガーにしたアニメーションの実装が可能 - 2025/10/29現在、Firefoxが未対応 - `scroll-timeline` を使って スクロール進捗(0%→100%) に同期させ、96%以降で矢印を0にフェードする - オーバーフローが無い場合はタイムライン自体が無効なので、矢印は最初から出ない(JSは不要) - `animation-range: 0% 96%` の `96%` を変えると「どのくらい末尾に近づいたら消すか」を調整可能(例:98% でより末端だけに) - オーバーフローが無い場合は、`scroll-timeline` が無効になり矢印は出ない(初期 opacity:0 のまま) - RTLや縦書きに合わせたい場合は scroll-timeline-axis を block に変えたり、direction を考慮

デモ

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7

コード

CSS

:root {
  --hint-w: 44px;
  --hint-bg: rgba(255,255,255,0.95);
  --hint-fg: #333;
}
body { font-family: system-ui, sans-serif; margin: 24px; }

.scroll-area {
  position: relative;
  max-inline-size: 560px;            /* デモ用。不要なら削除OK */
  border: 1px solid #ddd;
  border-radius: 8px;
  background: #fff;
  /* 兄弟 (.right-hint) から --x タイムラインを参照できるように */
  timeline-scope: --x;
}

/* ====== 要素内の横スクロールを確実に起こす構造 ====== */
.scroller {
  overflow-x: auto;                  /* ご要望の overflow-x:auto */
  overflow-y: hidden;
  display: block;                    /* ビューポート化(幅固定) */
  inline-size: 100%;
  padding: 12px;
  box-sizing: border-box;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;

  /* スクロール進捗タイムライン(横方向) */
  scroll-timeline-name: --x;
  scroll-timeline-axis: inline;
}
.track {
  display: inline-flex;
  gap: 12px;
  /* 中身の合計幅に合わせて広がる=scroller幅を超えやすくする */
  min-inline-size: max-content;
}

.card {
  inline-size: 200px;
  block-size: 100px;
  border-radius: 8px;
  border: 1px solid #e5e5e5;
  background: #fafafa;
  padding: 12px;
  box-sizing: border-box;
  flex: 0 0 auto;
}

/* 右端の矢印ヒント(フォールバックでは非表示のまま) */
.right-hint {
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;               /* 右端 */
  inline-size: var(--hint-w);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  color: var(--hint-fg);
  background: linear-gradient(to left, var(--hint-bg) 45%, rgba(255,255,255,0));
  border-radius: 8px;
  opacity: 0;                        /* タイムライン未対応 or 非スクロール時は非表示 */
}
.right-hint::before {
  content: "➔";                      /* お好みで "→" など */
  font-size: 20px;
  line-height: 1;
  animation: nudge 1.6s ease-in-out infinite;
}
@keyframes nudge {
  0%, 100% { transform: translateX(2px); opacity: .9; }
  50%      { transform: translateX(6px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .right-hint::before { animation: none; }
}

/* ===== Scroll-Driven Animations に対応している場合だけ有効化 ===== */
@supports (animation-timeline: scroll()) or (scroll-timeline-name: --dummy) {
  .right-hint {
    /* スクロール進捗に同期して可視状態→不可視へ */
    animation-name: fadeRightHint;
    animation-timeline: --x;         /* .scroller のスクロール進捗 */
    /* 0%〜96%の間は 0% キーフレーム(=表示)を維持し、
      96%→100% で 1 まで進んでフェードアウト。
      範囲外は fill:both で端の状態を保持。 */
    animation-range: 0% 96%;
    animation-duration: 1ms;         /* 時間値はダミー(タイムライン駆動) */
    animation-timing-function: linear;
    animation-fill-mode: both;
  }
  @keyframes fadeRightHint {
    0%   { opacity: 1; }             /* まだ右に余地あり → 表示 */
    100% { opacity: 0; }             /* 末尾付近〜末尾 → 消える */
  }
}

HTML

<div class="scroll-area">
  <div class="scroller" aria-label="横スクロール領域">
    <div class="track">
      <div class="card">Item 1</div>
      <div class="card">Item 2</div>
      <div class="card">Item 3</div>
      <div class="card">Item 4</div>
      <div class="card">Item 5</div>
      <div class="card">Item 6</div>
      <div class="card">Item 7</div>
    </div>
  </div>
  <div class="right-hint" aria-hidden="true" title="横にスクロールできます"></div>
</div>

参照