No.155
BUTTON CSSJS

Download Progress Button

押すとボタンの中を進捗が満ちていき、終わるとチェックマークに変わるダウンロードボタン。幅を変えずに transform だけで進捗を描くので、周りのレイアウトが動きません。

DEMO

CODE

HTML
<div class="demo-stage dlb-stage">
  <button type="button" class="dlb-btn" id="dlb-btn">
    <span class="dlb-fill" id="dlb-fill" aria-hidden="true"></span>
    <span class="dlb-label" id="dlb-label">ダウンロード</span>
  </button>
  <p class="dlb-status" id="dlb-status" role="status" aria-live="polite"></p>
</div>
CSS
.dlb-stage { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:.9rem; padding:2rem; background:#0B0B10; min-height:280px; }
.dlb-btn { position:relative; width:220px; height:52px; border:1px solid rgba(255,255,255,.18); border-radius:12px; overflow:hidden; cursor:pointer;
  background:#17171C; color:#fff; font-family:'Inter',sans-serif; font-weight:600; font-size:.92rem; }
.dlb-btn:focus-visible { outline:2px solid #E91B89; outline-offset:3px; }
.dlb-btn[aria-busy="true"] { cursor:progress; }
.dlb-fill { position:absolute; inset:0; background:linear-gradient(90deg,#E91B89,#7B37A8); transform-origin:left center; transform:scaleX(0); }
.dlb-label { position:relative; font-variant-numeric:tabular-nums; }
.dlb-btn.is-done { border-color:rgba(52,211,153,.6); }
.dlb-btn.is-done .dlb-fill { background:#10B981; transition: background .3s ease; }
.dlb-status { margin:0; min-height:1.2em; color:#9A9AA6; font-size:.78rem; }
@media (prefers-reduced-motion: reduce) {
  .dlb-btn.is-done .dlb-fill { transition-duration:.01ms; }
}
JS
(function(){
  var btn = document.getElementById('dlb-btn');
  var fill = document.getElementById('dlb-fill');
  var label = document.getElementById('dlb-label');
  var status = document.getElementById('dlb-status');
  if (!btn || !fill || !label) return;
  var DURATION = 1800, busy = false;
  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
  function finish(){
    fill.style.transform = 'scaleX(1)';
    btn.classList.add('is-done');
    label.textContent = '✓ 完了';
    status.textContent = 'ダウンロードが完了しました';
    btn.removeAttribute('aria-busy');
    setTimeout(function(){
      btn.classList.remove('is-done'); fill.style.transform = 'scaleX(0)';
      label.textContent = 'ダウンロード'; status.textContent = ''; busy = false;
    }, 2200);
  }
  btn.addEventListener('click', function(){
    if (busy) return;
    busy = true;
    btn.setAttribute('aria-busy', 'true');
    status.textContent = 'ダウンロード中…';
    if (reduce.matches) { finish(); return; }
    var start = performance.now();
    function tick(now){
      var p = Math.min((now - start) / DURATION, 1);
      var eased = 1 - Math.pow(1 - p, 3);
      fill.style.transform = 'scaleX(' + eased + ')';
      label.textContent = Math.round(eased * 100) + '%';
      if (p < 1) requestAnimationFrame(tick); else finish();
    }
    requestAnimationFrame(tick);
  });
})();

AI PROMPT

Claude Code Cursor v0
PROMPT
押すとボタンの中を進捗が満ちていき、終わるとチェックマーク付きの「完了」に変わるダウンロードボタンを作って。ボタンは幅と高さを固定し、overflow:hidden の中に絶対配置の塗り(グラデーション)を置いて transform: scaleX(0〜1)(transform-origin: left center)で伸ばす(width を動かすと周りのレイアウトが再計算されるので使わない)。進捗は requestAnimationFrame で経過時間から計算し、1 - (1 - p)^3 のイーズアウトで最後がゆっくりになるようにする。ラベルは「0%〜100%」を表示し、font-variant-numeric: tabular-nums で数字の幅をそろえて文字が揺れないようにする。完了したら塗りを緑に変え、ラベルを「✓ 完了」にし、2.2秒後に元へ戻す。処理中は aria-busy="true" を付けて二重クリックを無視し、状態は role="status" aria-live="polite" の段落で「ダウンロード中…」「ダウンロードが完了しました」と読み上げに伝える。prefers-reduced-motion: reduce の時は進捗を描かずにすぐ完了表示にする。実際のダウンロードでは fetch の ReadableStream で受信済みバイト数から p を計算する。

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

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

BOOKS · PR · AMAZON

📖 なかしまぁ先生のCSSアニメーション入門 📖 マイクロインタラクション ―UI/UXデザインの神が宿る細部 📖 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
→ PROGRESS BAR → COPY BUTTON → BAR LOADER

Download Progress Button(進捗付きダウンロードボタン)とは

押したボタン自身が進捗バーに変わり、完了すると結果を示すボタンです。ファイルのダウンロード・送信・書き出しのように数秒かかる処理で、「押せたのか」「今どこまで進んだのか」「終わったのか」をボタンの中だけで伝えられます。

当サイトの Progress Bar は独立した進捗バー、Copy Button は一瞬で終わる操作の完了表示、Bar Loader は進捗の分からない待ち時間の表示です。本ページは押した場所で進捗と完了までを見せる別テーマで、視線を動かさずに処理の状態を追えます。

要点は、幅を固定して transform で塗ること、処理中の二重クリックを防ぐこと、状態を読み上げにも伝えることの3点です。見た目の演出と実際の処理状態を必ず一致させ、進んでいないのに進捗が進むような表示はしないでください。

よくある質問

実際のダウンロードの進捗と連動させるには?

fetch で受け取った response.body(ReadableStream)を reader.read() で読み進め、受信したバイト数を Content-Length ヘッダーの値で割ると進捗 p が得られます。本デモの p を計算している部分をその値に置き換えるだけで、見た目の部分はそのまま使えます。Content-Length が無い応答では進捗が分からないため、不定の動き(ぐるぐる回る表示)に切り替えてください。

ボタンの幅を変える演出にしないのはなぜですか?

width を変えるとボタンの周りにある要素の位置も毎フレーム再計算され、動きがカクついたり、隣の文章がずれたりします。幅を固定して中の塗りを scaleX で伸ばせば、見た目は同じ進捗バーでもレイアウトは一切動きません。

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

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