DEMO
npm install motion
CODE
<div class="demo-stage cpb-stage">
<div class="cpb-box">
<code class="cpb-code" id="cpb-code">npm install motion</code>
<button type="button" class="cpb-btn" id="cpb-btn" aria-label="コマンドをコピー">
<svg class="cpb-ic cpb-ic-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
<svg class="cpb-ic cpb-ic-done" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>
</button>
<span class="cpb-tip" id="cpb-tip" role="status" aria-live="polite"></span>
</div>
</div>
.cpb-stage { display:flex; align-items:center; justify-content:center; padding:2rem; background:#0B0B10; min-height:280px; }
.cpb-box { position:relative; display:flex; align-items:center; gap:.8rem; padding:.7rem .7rem .7rem 1.1rem; background:#15151B; border:1px solid rgba(255,255,255,.12); border-radius:12px; }
.cpb-code { font-family:'JetBrains Mono',monospace; font-size:.9rem; color:#EDEDED; }
.cpb-btn { position:relative; width:40px; height:40px; border:1px solid rgba(255,255,255,.14); border-radius:9px; background:#1E1E26; color:#C9C9D1; cursor:pointer; transition: background .2s ease, border-color .2s ease, transform .15s ease; }
.cpb-btn:hover { background:#26262F; }
.cpb-btn:active { transform:scale(.92); }
.cpb-btn:focus-visible { outline:2px solid #E91B89; outline-offset:2px; }
.cpb-ic { position:absolute; inset:0; margin:auto; width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; transition: opacity .2s ease, transform .3s cubic-bezier(.34,1.56,.64,1); }
.cpb-ic-done { opacity:0; transform:scale(.4); color:#34D399; stroke-dasharray:24; stroke-dashoffset:24; }
.cpb-btn.is-done { border-color:rgba(52,211,153,.5); background:rgba(52,211,153,.12); }
.cpb-btn.is-done .cpb-ic-copy { opacity:0; transform:scale(.4); }
.cpb-btn.is-done .cpb-ic-done { opacity:1; transform:scale(1); stroke-dashoffset:0; transition: opacity .2s ease, transform .3s cubic-bezier(.34,1.56,.64,1), stroke-dashoffset .35s ease .08s; }
.cpb-tip { position:absolute; right:0; bottom:calc(100% + 8px); padding:.3rem .6rem; border-radius:6px; background:#EDEDED; color:#111; font-size:.72rem; font-family:'Inter',sans-serif; opacity:0; transform:translateY(4px); transition: opacity .2s ease, transform .2s ease; pointer-events:none; }
.cpb-tip.is-on { opacity:1; transform:none; }
@media (prefers-reduced-motion: reduce) {
.cpb-btn, .cpb-ic, .cpb-tip, .cpb-btn.is-done .cpb-ic-done { transition-duration:.01ms; }
}
(function(){
var btn = document.getElementById('cpb-btn');
var tip = document.getElementById('cpb-tip');
var code = document.getElementById('cpb-code');
if (!btn || !tip || !code) return;
var timer;
function done(ok){
btn.classList.toggle('is-done', ok);
tip.textContent = ok ? 'Copied!' : 'コピーできませんでした';
tip.classList.add('is-on');
clearTimeout(timer);
timer = setTimeout(function(){ btn.classList.remove('is-done'); tip.classList.remove('is-on'); tip.textContent = ''; }, 1600);
}
btn.addEventListener('click', function(){
var text = code.textContent;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(function(){ done(true); }, function(){ done(false); });
} else {
var ta = document.createElement('textarea');
ta.value = text; ta.setAttribute('readonly', ''); ta.style.position = 'absolute'; ta.style.left = '-9999px';
document.body.appendChild(ta); ta.select();
var ok = false;
try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
document.body.removeChild(ta);
done(ok);
}
});
})();
AI PROMPT
コードの横に置くコピーボタンを作って。クリックで navigator.clipboard.writeText() に書き込み、成功したらコピーアイコンが縮んで消え、代わりにチェックマークが弾むように現れる(cubic-bezier(.34,1.56,.64,1) のオーバーシュート)。チェックは SVG の path を stroke-dasharray/stroke-dashoffset で描くと「線が引かれる」動きになる。同時にボタン上に「Copied!」の吹き出しを出し、1.6秒後に元へ戻す。吹き出しは role="status" aria-live="polite" にしてスクリーンリーダーにも完了を伝えること。Clipboard API は https か localhost でしか使えないので、window.isSecureContext が false の時は textarea を作って document.execCommand('copy') で代替し、失敗時は「コピーできませんでした」と表示する。ボタンは aria-label で目的を名付け、:focus-visible のアウトラインを残す。@media (prefers-reduced-motion: reduce) で遷移時間を .01ms にする。
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アソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
コードやURL、クーポン番号の横に置き、押すだけでクリップボードへコピーするボタンです。コピーは画面上に結果が出ない操作なので、押したあとに成功を知らせる小さなアニメーションが「本当にコピーできたか」の不安を消す役割を持ちます。
当サイトの Toast Notification は画面の端に独立した通知を出す演出で、本ページは押したボタン自身が状態を変えて答える別テーマです。視線を動かさずに完了が分かるため、コードブロックのように連続してコピーする場面に向きます。
実装の要点は、成功と失敗の両方を扱うことです。http 環境や権限の拒否で書き込みが失敗することがあるため、旧方式への切り替えと、失敗した旨の表示までを1つの部品にまとめておくと使い回せます。
navigator.clipboard が使えないのはどんな時ですか?
Clipboard API は安全なコンテキスト(https か localhost)でしか使えません。http のページや、一部の埋め込み iframe では undefined になるか、書き込みが拒否されます。本実装は window.isSecureContext を見て、使えない時は textarea と document.execCommand('copy') の旧方式へ切り替えます。
コピー完了は見た目だけで伝えてよいですか?
アイコンの切り替えだけでは、画面を見ていない人やスクリーンリーダーの利用者に完了が伝わりません。吹き出しを role="status" aria-live="polite" にしておくと、テキストが入った時点で読み上げられます。失敗時にも同じ場所へ理由を出すと、押したのに何も起きないという状態を防げます。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。