DEMO
CODE
<div class="demo-stage ipg-stage">
<div class="ipg-box">
<div class="ipg-head"><span id="ipg-label">アップロード中</span><span class="ipg-val" id="ipg-val">残り時間を計算中…</span></div>
<div class="ipg-track" id="ipg-track" role="progressbar" aria-labelledby="ipg-label" aria-valuemin="0" aria-valuemax="100">
<span class="ipg-bar ipg-b1"></span><span class="ipg-bar ipg-b2"></span>
</div>
<button type="button" class="ipg-btn" id="ipg-btn">進み具合が分かる表示にする</button>
</div>
</div>
.ipg-stage { display:flex; align-items:center; justify-content:center; padding:2rem; background:#0B0B10; min-height:260px; }
.ipg-box { width:100%; max-width:340px; display:flex; flex-direction:column; gap:.8rem; }
.ipg-head { display:flex; justify-content:space-between; gap:1rem; color:#EDEDED; font-size:.84rem; }
.ipg-val { color:#A1A1AA; font-size:.76rem; font-variant-numeric:tabular-nums; }
.ipg-track { position:relative; height:6px; border-radius:3px; background:rgba(255,255,255,.12); overflow:hidden; }
.ipg-bar { position:absolute; top:0; bottom:0; left:0; width:100%; border-radius:3px; background:linear-gradient(90deg,#E91B89,#7B37A8); transform-origin:left center; }
.ipg-track:not([aria-valuenow]) .ipg-b1 { animation: ipg-a 2s cubic-bezier(.65,.8,.35,1) infinite; }
.ipg-track:not([aria-valuenow]) .ipg-b2 { animation: ipg-b 2s cubic-bezier(.4,0,.2,1) 1s infinite; transform:translateX(-100%); }
.ipg-track[aria-valuenow] .ipg-b1 { transform:scaleX(var(--ipg-p, 0)); transition: transform .2s linear; }
.ipg-track[aria-valuenow] .ipg-b2 { display:none; }
.ipg-btn { align-self:flex-start; min-height:44px; padding:0 1rem; border-radius:9px; border:1px solid rgba(255,255,255,.2); background:transparent; color:#EDEDED; font-size:.8rem; cursor:pointer; }
.ipg-btn:focus-visible { outline:2px solid #E91B89; outline-offset:2px; }
@keyframes ipg-a { 0% { transform:translateX(-100%) scaleX(.3); } 60%, 100% { transform:translateX(100%) scaleX(.6); } }
@keyframes ipg-b { 0% { transform:translateX(-100%) scaleX(.15); } 100% { transform:translateX(100%) scaleX(.4); } }
@media (prefers-reduced-motion: reduce) {
.ipg-track:not([aria-valuenow]) .ipg-b1 { animation:none; transform:none; opacity:.45; }
.ipg-track:not([aria-valuenow]) .ipg-b2 { display:none; }
.ipg-track[aria-valuenow] .ipg-b1 { transition-duration:.01ms; }
}
(function(){
var track = document.getElementById('ipg-track');
var val = document.getElementById('ipg-val');
var btn = document.getElementById('ipg-btn');
if (!track || !val || !btn) return;
var timer = 0, p = 0;
function tick(){
track.setAttribute('aria-valuenow', String(p));
track.style.setProperty('--ipg-p', p / 100);
val.textContent = p < 100 ? p + '%' : '完了';
if (p >= 100) { clearInterval(timer); timer = 0; return; }
p = Math.min(100, p + 5);
}
function toIndeterminate(){
clearInterval(timer); timer = 0;
track.removeAttribute('aria-valuenow');
val.textContent = '残り時間を計算中…';
btn.textContent = '進み具合が分かる表示にする';
}
function toDeterminate(){
p = 0;
btn.textContent = '分からない表示に戻す';
tick();
timer = setInterval(tick, 200);
}
btn.addEventListener('click', function(){
if (track.hasAttribute('aria-valuenow')) toIndeterminate(); else toDeterminate();
});
})();
AI PROMPT
終わりまでの時間が分からない処理で使う、不定(indeterminate)の進捗バーを作って。高さ6px・overflow:hidden のトラックに role="progressbar"・aria-valuemin="0"・aria-valuemax="100" を付け、見出しと aria-labelledby で関連付ける。進み具合が分からない間は aria-valuenow を付けない(これが「不定」の状態を表す)。トラックの中に、transform-origin: left center の帯を2本重ね、1本目は translateX(-100%) scaleX(.3) → translateX(100%) scaleX(.6) を2秒(60%の時点で右端へ抜ける)、2本目は translateX(-100%) scaleX(.15) → translateX(100%) scaleX(.4) を2秒・1秒遅れで流して、長さの違う帯が追いかけ合うように見せる。CSS は .ipg-track:not([aria-valuenow]) の時だけアニメーションさせ、aria-valuenow が付いたら帯を1本にして transform: scaleX(割合) で通常の進捗バーとして描く(width は動かさない)。ボタンで「進み具合が分かる表示」と「分からない表示」を切り替え、分かる表示では0%から5%ずつ進めて100%で「完了」にする。@media (prefers-reduced-motion: reduce) では流れる帯を止め、薄い色のバーを静止表示する。 Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift
AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する
BOOKS · PR · AMAZON
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
学びを止めない · AMAZONサブスク
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
処理がどこまで進んだか分からない間、細い帯がバーの中を流れ続けることで「処理中である」ことを示す進捗表示です。アップロードの開始直後や、サーバーの応答を待っている間など、割合を出せない場面で使われます。
当サイトの Bar Loader も進み具合の分からない待ち時間にバーが左右に流れるローダーで、Progress Bar は割合を示す通常の進捗バー、Scroll Progress Bar はページのスクロール位置を示すバーです。本ページは不定の表示に加えて、割合が分かった時点で通常のバーへ切り替える流れと、その状態を aria-valuenow で伝える方法までを扱います。
見た目を切り替えるだけでなく、aria-valuenow の有無で「分からない」と「何%」の状態を正しく伝えることが大切です。割合が分かったらすぐに数字の表示へ切り替えると、利用者は待ち時間の見通しを立てられます。
不定の状態はどうやって支援技術に伝えますか?
WAI-ARIA 1.2 では、role="progressbar" の値が分からない(不定の)時は aria-valuenow を省くこととされています。進み具合が分かった時点で aria-valuenow に数値を入れ、分からなくなったら属性ごと取り除きます。HTML の progress 要素を使う場合も同じ考え方で、value 属性を付けない progress 要素は不定の状態になり、CSS の :indeterminate で選べます。
不定のバーとスピナーはどう使い分けますか?
どちらも「終わりが分からない待ち時間」を示しますが、不定のバーは後で割合の表示に切り替える余地があり、画面の上端や入力欄の下など横に長い場所に置きやすい形です。スピナーはボタンの中やカードの中央など、狭い場所に向いています。進み具合が分かる処理なら、どちらよりも割合を示すバーのほうが待ち時間の見通しを伝えられます。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。