DEMO
ボタンを押すと処理が始まります
CODE
<div class="demo-stage msl-stage">
<div class="msl-box">
<div class="msl-window" aria-hidden="true"><ol class="msl-list" id="msl-list"></ol></div>
<p class="msl-status" id="msl-status" role="status" aria-live="polite">ボタンを押すと処理が始まります</p>
<button type="button" class="msl-btn" id="msl-btn">処理を開始</button>
</div>
</div>
.msl-stage { display:flex; align-items:center; justify-content:center; padding:1.5rem; background:#0B0B10; min-height:300px; }
.msl-box { width:100%; max-width:320px; display:flex; flex-direction:column; align-items:center; gap:.9rem; }
.msl-window { position:relative; width:100%; height:132px; overflow:hidden; }
.msl-list { list-style:none; margin:0; padding:0; transition: transform .45s cubic-bezier(.22,1,.36,1); }
.msl-item { display:flex; align-items:center; gap:.7rem; height:44px; color:#8A8A96; font-size:.88rem; transition: color .3s ease; }
.msl-item.is-current { color:#fff; }
.msl-item.is-done { color:#C9C9D1; }
.msl-ic { flex:0 0 20px; height:20px; box-sizing:border-box; border-radius:50%; border:2px solid currentColor; display:flex; align-items:center; justify-content:center; font-size:.7rem; }
.msl-item.is-current .msl-ic { border-color:rgba(233,27,137,.3); border-top-color:#E91B89; animation: msl-spin .8s linear infinite; }
.msl-item.is-done .msl-ic { border-color:#34D399; background:#34D399; color:#0B0B10; }
.msl-status { margin:0; min-height:1.3em; color:#C9C9D1; font-size:.8rem; text-align:center; font-variant-numeric:tabular-nums; }
.msl-btn { min-height:44px; padding:0 1.3rem; border:0; border-radius:999px; background:#D1177A; color:#fff; font-weight:600; font-size:.88rem; cursor:pointer; }
.msl-btn:focus-visible { outline:2px solid #fff; outline-offset:3px; }
@keyframes msl-spin { to { transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
.msl-list, .msl-item { transition-duration:.01ms; }
.msl-item.is-current .msl-ic { animation:none; border-color:#E91B89; }
}
(function(){
var list = document.getElementById('msl-list');
var status = document.getElementById('msl-status');
var btn = document.getElementById('msl-btn');
if (!list || !status || !btn) return;
var STEPS = ['ファイルを読み込み中', '画像を最適化中', 'コードを変換中', 'テストを実行中', '公開用に書き出し中'];
var ROW = 44, WAIT = 1100, cur = -1, timer = 0;
var items = STEPS.map(function(t){
var li = document.createElement('li');
li.className = 'msl-item';
li.innerHTML = '<span class="msl-ic"></span><span></span>';
li.lastChild.textContent = t;
list.appendChild(li);
return li;
});
function render(){
items.forEach(function(li, i){
li.classList.toggle('is-done', i < cur);
li.classList.toggle('is-current', i === cur);
li.firstChild.textContent = i < cur ? '✓' : '';
});
var c = Math.max(0, Math.min(cur, STEPS.length - 1));
list.style.transform = 'translateY(' + (ROW - c * ROW) + 'px)';
}
function advance(){
cur++;
render();
if (cur < STEPS.length) {
status.textContent = (cur + 1) + ' / ' + STEPS.length + ' ' + STEPS[cur];
timer = setTimeout(advance, WAIT);
} else {
status.textContent = 'すべての処理が完了しました';
btn.textContent = 'もう一度';
timer = 0;
}
}
btn.addEventListener('click', function(){
if (timer) {
clearTimeout(timer); timer = 0;
if (items[cur]) items[cur].classList.remove('is-current');
status.textContent = '処理を中止しました';
btn.textContent = '処理を開始';
return;
}
cur = -1; btn.textContent = '中止する'; advance();
});
render();
})();
AI PROMPT
時間のかかる処理で、いま何をしているかを段階ごとに見せる Multi Step Loader を作って。段階の名前(例:ファイルを読み込み中・画像を最適化中・コードを変換中・テストを実行中・公開用に書き出し中)を ol と li で縦に並べ、1行の高さを44pxにそろえる。一覧は高さ132px(3行分)・overflow:hidden の窓に入れる(窓の上下に背景色のグラデーションを重ねて端を薄くすると、上下の行の文字のコントラストが4.5:1を下回るので重ねない)。現在の段が常に真ん中の行に来るよう、一覧を transform: translateY(44px − 現在の番号 × 44px) で .45秒かけてすべらせる。各行の左には丸いアイコンを置き、現在の段は枠の上側だけ色を付けて回転させ(スピナー)、終わった段は緑の丸に「✓」を入れ、これからの段は灰色の文字にする(不透明度で薄くすると背景とのコントラストが4.5:1を下回るので、色で差を付ける)。進み具合は role="status" aria-live="polite" の段落で「2 / 5 画像を最適化中」のように伝え、最後は「すべての処理が完了しました」と表示する。開始ボタンは処理中に「中止する」ボタンへ変え(押せない状態にはしない)、中止したら今の段の回転も止め、終わったら「もう一度」にする。窓の中の一覧は aria-hidden="true" にし、読み上げは status の段落に任せる。@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アソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
数秒から数十秒かかる処理の間に、「いま何をしているか」を段階ごとに表示する読み込み表示です。Aceternity UI にも同じ名前のコンポーネント(Multi Step Loader)があり、公式の説明では「読み込みに時間がかかる画面のためのステップ表示」とされています。AIの生成処理や、ファイルの書き出し・アップロードの画面で使われます。
当サイトの Stepper Progress は利用者が自分で進める手続きの段階表示、Progress Bar は割合で進み具合を示す横棒、Skeleton Loader は読み込み中の画面の骨組みを見せる表示です。本ページはシステムが自動で進める処理の中身を1段ずつ見せる別テーマです。
何をしているかが分かると、同じ待ち時間でも「止まっているのでは」という不安が減ります。段階の名前は利用者にも意味が分かる言葉で書き、表示は実際の処理の進み具合と必ず一致させてください。
段階の表示は実際の処理とどう連動させますか?
本デモは一定の時間ごとに次の段へ進めていますが、実際には各処理が終わったタイミングで次の段へ進める関数を呼びます。たとえば非同期処理を順番に await し、1つ終わるごとに表示を更新します。表示だけを時間で進めると、処理がまだ終わっていないのに完了したように見えるため、必ず実際の処理の完了に合わせてください。
処理中にボタンを押せなくしないのはなぜですか?
押せない状態(disabled)のボタンは、なぜ押せないのかが伝わりにくく、キーボードで操作している人はフォーカスの行き場を失うこともあります。本実装は処理中のボタンを「中止する」に変え、いつでも押せる意味のある操作にしています。時間のかかる処理ほど、途中でやめられる手段があると安心して待てます。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。