こんにちは、かつコーチです。
前回は無限スクロールを扱いました。
今回は「実践UI実装」シリーズの最終回として、クーポンコードやシェア用URLをワンクリックでコピーできるコピーボタンを実装します。
初心者向けのテーマなので、これまでよりもシンプルな内容から丁寧に見ていきましょう。
コピーボタンとは?
Clipboard APIとは
Clipboard APIは、ブラウザからOSのクリップボード(コピー&ペーストの一時保管場所)を操作するための標準機能です。
navigator.clipboard.writeText()というメソッドを呼ぶだけで、指定した文字列をクリップボードにコピーできます。
以前はdocument.execCommand("copy")という古い方法が使われていましたが、現在は非推奨とされており、Clipboard APIを使うのが標準的な方法です。
どんな場面で使われるか
代表的な使いどころは次のようなものです。
- クーポンコードやプロモコードのコピー
- SNSシェア用のURLコピー
- APIキーやコマンドなど、長い文字列のコピー
「範囲選択してCtrl+C」という手間をなくし、ボタン1つでコピーを完結させることで、ユーザーの離脱を防ぐ効果があります。
基本の実装手順
手順1:HTMLとCSSでコピー対象とボタンを用意する
まずはコピーしたいテキストと、ボタンを用意します。
<div class="copy-box">
<input type="text" id="coupon-code" value="WELCOME2026" readonly />
<button id="copy-btn" class="copy-btn">コピー</button>
</div>
.copy-box {
display: flex;
gap: 8px;
max-width: 320px;
}
#coupon-code {
flex: 1;
padding: 8px 10px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 1rem;
}
.copy-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: #2563eb;
color: #fff;
cursor: pointer;
}
.copy-btn.copied {
background: #16a34a;
}
inputにはreadonlyを付けておき、ユーザーが誤って中身を書き換えられないようにしています。
手順2:Clipboard APIでコピー処理を書く
続いて、ボタンを押したときの処理を書いていきます。
const copyBtn = document.getElementById("copy-btn");
const couponInput = document.getElementById("coupon-code");
copyBtn.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(couponInput.value);
copyBtn.textContent = "コピーしました!";
copyBtn.classList.add("copied");
} catch (err) {
copyBtn.textContent = "コピーに失敗しました";
console.error("クリップボードへのコピーに失敗:", err);
}
});
navigator.clipboard.writeText()は非同期処理(Promise:処理の完了を後から受け取れる仕組み)なので、awaitを使って完了を待ってからボタンの見た目を変えています。
コピーが成功したらボタンの文字と色を変えて、ユーザーに「ちゃんとコピーできましたよ」と伝えるのがポイントです。
手順3:一定時間後に表示を元に戻す
コピー成功の表示を出しっぱなしにせず、数秒後に元のボタン表示へ戻します。
copyBtn.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(couponInput.value);
copyBtn.textContent = "コピーしました!";
copyBtn.classList.add("copied");
setTimeout(() => {
copyBtn.textContent = "コピー";
copyBtn.classList.remove("copied");
}, 2000);
} catch (err) {
console.error("クリップボードへのコピーに失敗:", err);
}
});
2秒後に文字と色を元に戻すことで、ボタンが「押せる状態」に戻ったことも視覚的に伝わります。
よくあるつまずきポイント・エラー対処
つまずき:ローカルの動作確認では動くのに本番環境で急に動かなくなった
私が最初にこの機能を実装したとき、localhostでの動作確認では問題なくコピーできていたのに、本番にアップロードした途端に動かなくなり、かなり焦った経験があります。
❌ Before:HTTP環境でも動く前提でコードを書いていた
copyBtn.addEventListener("click", () => {
navigator.clipboard.writeText(couponInput.value);
// エラーハンドリングがなく、失敗しても気づけない
});
コンソールを確認すると、navigator.clipboard is undefinedというエラーが出ていました。
原因は、Clipboard APIがHTTPS環境(またはlocalhost)でしか使えない仕様になっていたことでした。
当時デプロイしていたテスト環境がまだHTTP接続だったため、navigator.clipboardオブジェクト自体が存在せず、writeTextを呼ぼうとしてエラーになっていたのです。
✅ After:Clipboard APIが使えない場合の対処も含めて書く
copyBtn.addEventListener("click", async () => {
if (!navigator.clipboard) {
alert("お使いの環境ではコピー機能を利用できません(HTTPS環境が必要です)");
return;
}
try {
await navigator.clipboard.writeText(couponInput.value);
copyBtn.textContent = "コピーしました!";
copyBtn.classList.add("copied");
setTimeout(() => {
copyBtn.textContent = "コピー";
copyBtn.classList.remove("copied");
}, 2000);
} catch (err) {
console.error("クリップボードへのコピーに失敗:", err);
alert("コピーに失敗しました。お手数ですが手動でコピーしてください");
}
});
navigator.clipboardが存在するかどうかを最初にチェックし、存在しない場合はユーザーに分かりやすく伝えるようにしました。
この一件以来、「ブラウザの新しいAPIを使うときは、必ずHTTPS前提かどうかを最初に確認する」というのを習慣にしています。
本番環境は基本的にHTTPS化されていることが多いですが、社内の検証環境などでHTTP運用のままになっているケースもあるので、油断は禁物です。
応用・一歩先の使い方
複数のコピーボタンをまとめて扱う
商品一覧など、コピーボタンがページ内に複数ある場合は、イベント委任でまとめて処理すると効率的です。
document.addEventListener("click", async (e) => {
const btn = e.target.closest(".copy-btn");
if (!btn) return;
const targetId = btn.dataset.target;
const text = document.getElementById(targetId).value;
try {
await navigator.clipboard.writeText(text);
const original = btn.textContent;
btn.textContent = "コピーしました!";
setTimeout(() => (btn.textContent = original), 2000);
} catch (err) {
console.error("コピーに失敗:", err);
}
});
data-target属性でコピー元のinputのIDを指定しておけば、ボタンの数が増えても1つのイベントリスナーだけで対応できます。
readonlyのinput以外(テキストや変数)をコピーする
input要素だけでなく、任意の文字列や変数の値をそのままコピーしたい場面もよくあります。
const shareUrl = `${location.origin}/campaign/2026-summer`;
document.getElementById("share-btn").addEventListener("click", async () => {
await navigator.clipboard.writeText(shareUrl);
});
navigator.clipboard.writeText()は文字列さえ渡せばよいので、inputの値に限らず、JavaScript側で組み立てた文字列をそのままコピー対象にできます。
まとめ
「実践UI実装」シリーズは、ハンバーガーメニューから始まり、モーダル・トースト通知・ドラッグ&ドロップ・無限スクロール、そして今回のコピーボタンまで扱ってきました。
振り返ってみると、どの機能も基本の考え方は共通していて、「HTML/CSSで見た目のパターンをあらかじめ用意しておき、JavaScriptはそこにclassを付けたり外したりして状態を切り替えるだけ」というシンプルな構造でした。
この考え方さえ押さえておけば、今回扱っていない別のUI機能に出会っても、応用して実装できるはずです。
この記事のポイント
navigator.clipboard.writeText()でクリップボードへのコピーが簡単に実装できる- コピー処理は非同期(Promise)なので
awaitで完了を待ってからUIを更新する - Clipboard APIはHTTPS環境(またはlocalhost)でしか使えない点に注意が必要
navigator.clipboardの存在チェックとtry/catchで、使えない環境でもエラーで落ちないようにする- イベント委任を使えば、複数のコピーボタンを1つのリスナーでまとめて処理できる
次に読むべき記事
次回からは、外部のデータを取得して画面に反映する「fetch API」の基本を扱っていきます。
これまでのUI実装に、実際のサーバーとの通信を組み合わせられるようになる、大事な一歩です。
→ 次の記事:fetch APIの基本:外部データを取得する方法
