No.175
SCROLL CSSJS

Tracing Beam

縦に並んだ記事の左側で、スクロールした分だけ光の線が伸び、先端の点が読んでいる位置を示すタイムライン。枠の中のスクロール量から割合を求め、scaleY で線を伸ばしています。

DEMO

STEP 01

企画

何を伝えるための動きなのかを先に決めます。目的のない演出は、読む人の邪魔になるだけです。

STEP 02

デザイン

動きの前後の状態と、かける時間を決めます。操作への反応は短めに収めると軽快に感じられます。

STEP 03

実装

transform と opacity を中心に組み立てると、レイアウトを動かさずに軽く動かせます。

STEP 04

検証

キーボードでの操作、動きを減らす設定、狭い画面での表示を確かめます。

STEP 05

公開

公開後も、動きが目的に役立っているかを見直し続けます。

CODE

HTML
<div class="demo-stage trb-stage">
  <div class="trb-scroller" id="trb-scroller" tabindex="0" role="region" aria-label="タイムラインのデモ(スクロールできます)">
    <div class="trb-inner">
      <div class="trb-rail" aria-hidden="true"><span class="trb-beam" id="trb-beam"></span><span class="trb-head" id="trb-head"></span></div>
      <article class="trb-item"><p class="trb-step">STEP 01</p><h3>企画</h3><p>何を伝えるための動きなのかを先に決めます。目的のない演出は、読む人の邪魔になるだけです。</p></article>
      <article class="trb-item"><p class="trb-step">STEP 02</p><h3>デザイン</h3><p>動きの前後の状態と、かける時間を決めます。操作への反応は短めに収めると軽快に感じられます。</p></article>
      <article class="trb-item"><p class="trb-step">STEP 03</p><h3>実装</h3><p>transform と opacity を中心に組み立てると、レイアウトを動かさずに軽く動かせます。</p></article>
      <article class="trb-item"><p class="trb-step">STEP 04</p><h3>検証</h3><p>キーボードでの操作、動きを減らす設定、狭い画面での表示を確かめます。</p></article>
      <article class="trb-item"><p class="trb-step">STEP 05</p><h3>公開</h3><p>公開後も、動きが目的に役立っているかを見直し続けます。</p></article>
    </div>
  </div>
</div>
CSS
.trb-stage { display:flex; align-items:center; justify-content:center; padding:1.2rem; background:#0B0B10; min-height:320px; }
.trb-scroller { width:100%; max-width:400px; height:300px; overflow-y:auto; border-radius:14px; background:#111117; border:1px solid rgba(255,255,255,.08); outline:none; }
.trb-scroller:focus-visible { box-shadow:0 0 0 3px #E91B89; }
.trb-inner { position:relative; padding:1.2rem 1.2rem 1.2rem 3rem; }
.trb-rail { position:absolute; left:1.4rem; top:1.4rem; bottom:1.4rem; width:2px; background:rgba(255,255,255,.1); }
.trb-beam { position:absolute; left:0; top:0; width:100%; height:100%; transform-origin:top; transform:scaleY(0); background:linear-gradient(#5BAADE, #7B37A8 60%, #E91B89); transition: transform .1s linear; }
.trb-head { position:absolute; left:50%; top:0; width:12px; height:12px; margin:-6px 0 0 -6px; border-radius:50%; background:#fff; box-shadow:0 0 0 4px rgba(233,27,137,.35), 0 0 16px #E91B89; transition: transform .1s linear; }
.trb-item { padding-bottom:1.6rem; color:#C9C9D1; font-size:.82rem; line-height:1.7; }
.trb-item h3 { margin:.1rem 0 .3rem; color:#fff; font-size:1rem; }
.trb-item p { margin:0; }
.trb-item .trb-step { font-family:'JetBrains Mono',monospace; font-size:.7rem; letter-spacing:.08em; color:#F472B6; }
@media (prefers-reduced-motion: reduce) {
  .trb-beam, .trb-head { transition-duration:.01ms; }
}
JS
(function(){
  var sc = document.getElementById('trb-scroller');
  var beam = document.getElementById('trb-beam');
  var head = document.getElementById('trb-head');
  if (!sc || !beam || !head) return;
  var rail = beam.parentNode, ticking = false;
  function update(){
    ticking = false;
    var max = sc.scrollHeight - sc.clientHeight;
    var p = max > 0 ? sc.scrollTop / max : 1;
    beam.style.transform = 'scaleY(' + p + ')';
    head.style.transform = 'translateY(' + (p * rail.clientHeight) + 'px)';
  }
  sc.addEventListener('scroll', function(){ if (!ticking) { ticking = true; requestAnimationFrame(update); } }, { passive: true });
  window.addEventListener('resize', update);
  update();
})();

AI PROMPT

Claude Code Cursor v0
PROMPT
縦に並んだ記事の左側で、スクロールした分だけ光の線が伸び、先端の点が読んでいる位置を示すタイムライン(Tracing Beam)を作って。高さ300px・overflow-y:auto の枠に記事を5つ並べ、左に余白を取って細い縦線(レール)を position:absolute で置く。レールの中に、上から下へのグラデーションの線を transform-origin: top・transform: scaleY(0〜1) で伸ばす(height を動かさない)。割合は 枠の scrollTop ÷ (scrollHeight − clientHeight) で求め、スクロールしきれない時は1にする。先端には白い丸に光の box-shadow を付けた点を置き、transform: translateY(割合 × レールの高さ) で線の先に合わせる。scroll イベントは passive: true で受け、requestAnimationFrame で1フレームに1回だけ更新する。枠は tabindex=0 と role="region"・aria-label を付けてキーボードでもスクロールできるようにし、overscroll-behavior は指定しない(枠の端まで来たらページ全体のスクロールへ引き継ぐ)。線と点は aria-hidden="true"。@media (prefers-reduced-motion: reduce) で追従の transition を無効化する。ページ全体のスクロールに合わせる場合の書き方も紹介する。

Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift

AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する

BOOKS · PR · AMAZON

📖 なかしまぁ先生のCSSアニメーション入門 📖 UIデザインの教科書 新版 マルチデバイス時代のインターフェース設計 📖 Fundamentals of Web Animation with GSAP

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

学びを止めない · AMAZONサブスク

📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

RELATED EFFECTS

↗ MORE
→ SCROLL PROGRESS BAR → SCROLL-DRIVEN PROGRESS → STICKY SECTIONS

Tracing Beam(スクロールで伸びる光の線)とは

記事や手順の一覧の横で、スクロールした分だけ光の線が伸び、先端の点が今読んでいる位置を示す演出です。Aceternity UI にも同じ系統のコンポーネント(Tracing Beam)があり、公式の説明では SVG の線に沿って、スクロールに合わせて光が進むとされています。本ページは SVG を使わず、div の線を scaleY で伸ばす作りにしています。

当サイトの Scroll Progress Bar はページの上端に横棒で進み具合を示す表示、Scroll-driven Progress は CSS のスクロール連動アニメーションで作る進捗、Sticky Sections はスクロールに合わせて区画が貼り付く演出です。本ページは縦に並んだ内容の横に、読み進めた距離を線で示す別テーマです。

手順・沿革・更新履歴のように、上から順に読むことに意味がある内容と相性が良い演出です。線はあくまで補助なので、内容そのものは線がなくても順番が分かるよう、番号や見出しを付けておいてください。

よくある質問

ページ全体のスクロールに合わせるには?

枠の scrollTop の代わりに window.scrollY を、枠の高さの差の代わりに「document.documentElement.scrollHeight − window.innerHeight」を使えば、ページ全体の進み具合になります。CSS だけで作るなら animation-timeline: scroll() で線の伸びをスクロールに結びつける方法もありますが、MDN の互換性データでは Chrome と Edge は115、Safari は26から対応で、Firefox の正式版は未対応とされています。対応していないブラウザ向けに、本ページの JavaScript 版を残しておくと確実です。

線を height ではなく scaleY で伸ばすのはなぜですか?

height を変えると、スクロールのたびにレイアウトが計算し直されます。scaleY は描画の段階で伸び縮みさせるだけなので、スクロールに合わせて頻繁に更新しても軽く動きます。transform-origin を top にしておくと、上端を固定したまま下へ伸びていきます。

コードは無料で使えますか?

はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。