GODRICH

【コピペOK】タイムライン デザイン css 9選

会社の沿革ページや通販サイトの注文状況のように、いくつかの段階を線と丸でつないで順番に見せる部品を「タイムライン」と呼びます。タイムライン デザイン cssで検索して出てくるような、線が伸びたり丸が光ったりする動きを、AOS・GSAPなしの「CSSだけ・JS不要」で9個集めました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

9個は人気順ではなく、実際の画面が経験する順番に並べています。まずタイムライン自体がどう描かれるか(01〜02)、次に横並びのステッパーが進み具合をどう見せるか(03〜04)、画面にコンテンツとしてどう乗るか(05〜06)、最後に最新のCSSだけでスクロールや並び順まで扱う方法(07〜09)という流れです。

01縦線の描画

会社の沿革ページや履歴書の経歴のように縦に並ぶ一覧で、線が上から下へ伸びながら描かれます。transform: scaleY(0)からscaleY(1)へ、transform-origin: topだけで作れます。

scaleYtransform-origin1.4s
.rail {
  width: 3px;
  height: 100%;
  background: $color;
  transform-origin: top;
  transform: scaleY(1);

  @supports (animation-timeline: view()) {
    transform: scaleY(0);
    animation: draw linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 70%;
  }
}
@keyframes draw {
  from { transform: scaleY(0); }
  to   { transform: scaleY(1); }
}

02ドットが順番にポップ

線上のドットが上から順に、弾むように一つずつ現れます。オンボーディングのチェックリストや注文処理のステップ表示に向いていて、SCSSの@forで作ったnth-child別のanimation-delayだけを変えています。

scaleanimation-delaynth-child
.dot {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: $color;
  transform: scale(0);
  opacity: 0;
  animation: pop 2s cubic-bezier(.2, 1.4, .4, 1) both;
}

@for $i from 1 through 5 {
  .dot:nth-child(#{$i}) { animation-delay: #{($i - 1) * 140}ms; }
}

@keyframes pop {
  0%   { transform: scale(0);    opacity: 0; }
  60%  { transform: scale(1.25); opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}

03横ステップの塗りつぶし

会員登録やアンケートのように横に並ぶステップの間で、通過した分だけ連結線が左から色で塗りつぶされます。scaleXが自分の幅を基準に伸びるので、隣のステップの大きさを知らなくても動きます。

scaleXflex: 1 1 0600ms
.bar {
  flex-grow: 1;
  flex-shrink: 1;
  flex-basis: 0;
  height: 3px;
  overflow: hidden;
}
.fill {
  width: 100%;
  height: 100%;
  background: $color;
  transform: scaleX(0);
  transform-origin: left;
}
.step.is-done + .bar .fill { transform: scaleX(1); }

04アクティブステップのパルス

処理中の注文やリアルタイム通知バッジのように、今進んでいる段階の丸の後ろでリングが膨らみながら薄くなるのを繰り返します。レーダーの信号のように「今ここが動いている」という合図だけを伝えます。

scaleopacity1.6s
.ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid $color;
  animation: pulse 1.6s ease-out infinite;
}
@keyframes pulse {
  0%   { transform: scale(.7); opacity: .8; }
  70%  { transform: scale(1.8); opacity: 0; }
  100% { transform: scale(1.8); opacity: 0; }
}

05左右交互カードスライド

プロジェクトのマイルストーン紹介や沿革ページで、中央の縦線を基準に奇数のカードは左から、偶数のカードは右から寄ってきます。画面に入ってきたカードごとに、view()が自動で始まります。

translateXopacity500ms
.row--l .card { transform: translateX(-24px); }
.row--r .card { transform: translateX(24px); }

@supports (animation-timeline: view()) {
  .card {
    opacity: 0;
    animation: in ease-out both;
    animation-timeline: view();
    animation-range: entry 0% cover 60%;
  }
}
@keyframes in { to { opacity: 1; transform: translateX(0); } }

06チェックアウトステッパー

ECの決済や予約フォームのように、カート・配送・決済・完了の4段階のうち終わった段階は丸の中の数字がSVGのチェックマークに変わります。二本の棒で斜線を作る代わりにpolylinestroke-dashoffsetを減らして描くので、線の継ぎ目がずれません。

stroke-dashoffsetscaleXSVG
.check polyline {
  stroke-dasharray: 16;
  stroke-dashoffset: 16;
}
.step.is-checked .check polyline {
  animation: draw .5s ease-out forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }

.step.is-checked + .bar .fill { transform: scaleX(1); }

07スクロールで進捗線を描画(scroll())

長文記事の読了進捗線やドキュメントのサイドタイムラインのように、箱の中をスクロールすると隣の縦線がその分だけ描かれます。animation-timeline: scroll(nearest block)一つが、スクロールのイベントリスナーとrequestAnimationFrameのループの代わりをします。

animation-timeline: scroll()scaleYJS 0줄
.rail {
  width: 3px;
  height: 100%;
  background: $color;
  transform-origin: top;
  transform: scaleY(1);

  @supports (animation-timeline: scroll()) {
    transform: scaleY(0);
    animation: draw linear both;
    animation-timeline: scroll(nearest block);
  }
}
@keyframes draw { to { transform: scaleY(1); } }

08view()でイベントをリビール

スクロールで1つずつ現れるイベントタイムラインや長文記事のセクション登場のように、IntersectionObserverなしでカードごとに画面へ入ってくる範囲の分だけopacitytranslateYが動きます。

animation-timeline: view()animation-rangeJS 0줄
.card {
  opacity: 1;

  @supports (animation-timeline: view()) {
    opacity: 0;
    transform: translateY(16px);
    animation: reveal ease-out both;
    animation-timeline: view();
    animation-range: entry 0% cover 40%;
  }
}
@keyframes reveal { to { opacity: 1; transform: translateY(0); } }

09sibling-index()で自動採番

ドラッグで並び替えるチェックリストのように項目の追加・削除が多い一覧で、nth-childのルールなしに兄弟の並び順をそのまま番号として使います。counter-setに計算式を入れられるのがポイントです。

sibling-index()counter-setcalc()
.step {
  counter-increment: n;
  &::before { content: counter(n); }
}
@supports (z-index: calc(sibling-index())) {
  .step {
    counter-increment: none;
    counter-set: n calc(sibling-index());
    animation-delay: calc((sibling-index() - 1) * 120ms);
  }
}

タイムラインCSSアニメーションはどこで壊れるか

animation-timeline: scroll()view()、そしてsibling-index()の三つは、いずれもブラウザ対応がまだ分かれています。実測(2026年9月10日、インストール済みのChrome 152基準)は次のとおりです。

機能 Chrome・Edge Safari Firefox
animation-timeline scroll()・view() 115+ 未対応 未対応(フラグの裏)
sibling-index()sibling-count() 129+ 未対応 未対応

対応状況はMDNのanimation-timelineドキュメントsibling-index()の草案仕様で確認できます。デモを実際にレンダリングしていて本当につまずいた落とし穴も一つあります。このデモをgifに書き出すレンダースクリプトはアニメーションの時計をcurrentTimeへ直接押し込んでコマを取り出す仕組みですが、07・08のようにscroll()view()を使うアニメーションへ絶対時間の値(ミリ秒)をそのまま入れたところ、「進捗ベースのアニメーションには絶対時間を使えない」というエラーでレンダリングごと落ちました。時計にセットする値はCSS.percent(進捗率)でなければならないと分かってから07・08は正しく撮影できるようになりました。FirefoxとSafariでは07・08のanimation-timelineがそのまま無視されるため、@supports not (animation-timeline: scroll())(08はview())で線・カードを最初から全部描かれた最終状態に固定してあります——これらの修正を反映した9個のファイルを固める圧縮のパスワードはq3b8jjmaで、vanilla/react/どちらのコードも下のzipからまとめて受け取れます。

アクセシビリティ

OSの動作を減らす設定(prefers-reduced-motion: reduce)をオンにすると、9個とも動きが止まります。01・03・07・08のように線やカードが描かれていく項目は、進捗0ではなくすべて描き終わった最終状態に固定され、02・05・09のように一つずつ現れる項目も全部すでに現れた状態で止まります。06のチェックアイコンと連結線も同じく、描く途中のアニメーションだけを止めて完了状態を保ちます。06のチェックアイコンは装飾ではなく状態情報のため、スクリーンリーダー用にaria-label="完了"を丸要素へ別途付けています。09のようにcounter()で描く数字はスクリーンリーダーが読み取れないため、囲む<li>要素にaria-label="{n}番目のステップ"を入れています。04のパルスリングだけは完全に消さず、opacity: .6の固定値を残して点滅なしに「今ここが進行中」という合図を保っています。prefers-reduced-motionの対応状況はMDNのドキュメントで確認できます。

同じスクロール連動のタイムラインはプログレスバーCSSアニメーション9選にもまとめてあり、CSSアニメーションの記事一覧はCSSカテゴリーから見られます。

FAQ

scroll()・view()が使えないブラウザでは、タイムラインが動かないのですか?

vanilla/react/のどちらにも@supports not (animation-timeline: scroll())(08はview())という条件分岐が入っているため、進捗状態なしで最初から全部描かれた見た目のまま止まります——両方とも同じ動きです。FirefoxやSafariで07・08を本当にスクロールへリアルタイムで反応させたいときは、scrollイベントかIntersectionObserverで進捗を計算してCSS変数として渡すJavaScriptを自分で追加する必要があります。

09番のsibling-index()の代わりに、普通にnth-childで番号を振ってはいけませんか?

固定された並びの一覧ならnth-childでも十分です。sibling-index()が必要になるのは、ドラッグで順番を入れ替えたり項目が追加・削除されたりする一覧のときです——nth-childは項目数ぶんのルールを先に全部書いておく必要がありますが、sibling-index()は今DOMにある兄弟の並び順をそのまま読むので、ルールを書き直す必要がありません。

9個のうち縦型と横型を同じページで一緒に使ってもいいですか?

01・02・05・07・08のように縦に流れるものと、03・04・06のように横に流れるものは、ページの中で役割が違うのが普通です——縦型は本文コンテンツ(沿革・イベント)に、横型はフォーム上部の進行状態表示に使います。同じ画面の一区画の中で向きを混ぜると、どちらが「今進行中」なのか分かりにくくなるので、区画を分けて使うことをおすすめします。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。