遊べるUI 初級

コピー成功

コードをコピーすると「Copied!」の吹き出しがポンと弾んで現れチェックマークに切り替わり、数秒でふわっと消えるコピーボタン。

#css#js#clipboard#feedback

ライブデモ

コード

HTML
<!-- コピー成功: コピー操作でCopied!ツールチップが弾んで現れ、チェックマークに切り替わって数秒で消える -->
<div class="stage">
  <div class="copy-field">
    <code class="copy-field__code">npm install wedelab-ui</code>
    <button class="copy-field__btn" type="button" aria-label="コードをコピー">
      <svg class="copy-field__icon copy-field__icon--copy" viewBox="0 0 24 24" aria-hidden="true">
        <rect x="8" y="7" width="11" height="13" rx="2" fill="none" stroke="currentColor" stroke-width="1.8"/>
        <path d="M5.5 16V5.5a1 1 0 0 1 1-1H16" fill="none" stroke="currentColor" stroke-width="1.8"/>
      </svg>
      <svg class="copy-field__icon copy-field__icon--check" viewBox="0 0 24 24" aria-hidden="true">
        <path d="M5 13l4.5 4.5L19 8" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
      </svg>
      <span class="copy-field__tooltip" role="status">Copied!</span>
    </button>
  </div>
  <p class="hint">クリックでコマンドをコピーします</p>
</div>
CSS
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  overflow: hidden;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(circle at 50% 30%, #1b1830 0%, #0d0e1a 72%);
  color: #f4f5fb;
}

.stage { display: grid; place-items: center; gap: 16px; padding: 20px; }

.copy-field {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: min(86vw, 300px);
  padding: 6px 6px 6px 18px;
  border-radius: 14px;
  background: rgba(255, 255, 255, .05);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .09), 0 10px 24px rgba(0, 0, 0, .4);
}

.copy-field__code {
  flex: 1 1 auto;
  min-width: 0;
  font: 500 13.5px/1 "Cascadia Code", "Consolas", monospace;
  color: #cdd3f0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: .01em;
}

.copy-field__btn {
  position: relative;
  flex: none;
  border: none;
  cursor: pointer;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  color: #cdd3f0;
  background: rgba(255, 255, 255, .07);
  -webkit-tap-highlight-color: transparent;
  transition: transform .12s ease;
  animation: copy-idle 3s ease-in-out infinite;
}
.copy-field__btn:active { transform: scale(.9); }
.copy-field__btn:focus-visible { outline: 2px solid #a78bfa; outline-offset: 3px; }

@keyframes copy-idle {
  0%, 100% { box-shadow: 0 0 0 rgba(167, 139, 250, 0); }
  50%      { box-shadow: 0 0 10px rgba(167, 139, 250, .38); }
}

.copy-field__icon {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 19px;
  height: 19px;
  transform: translate(-50%, -50%) scale(1);
  transition: opacity .22s ease, transform .35s cubic-bezier(.34, 1.56, .64, 1);
}
.copy-field__icon--check {
  opacity: 0;
  color: #22c55e;
  transform: translate(-50%, -50%) scale(.4);
}
.copy-field__btn.is-copied .copy-field__icon--copy {
  opacity: 0;
  transform: translate(-50%, -50%) scale(.4);
}
.copy-field__btn.is-copied .copy-field__icon--check {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

.copy-field__tooltip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  transform: translate(-50%, 6px) scale(.5);
  opacity: 0;
  padding: 6px 12px;
  border-radius: 10px;
  background: #201c38;
  color: #f4f5fb;
  font: 700 12.5px/1 "Segoe UI", system-ui, sans-serif;
  white-space: nowrap;
  box-shadow: 0 8px 20px rgba(0, 0, 0, .5), inset 0 0 0 1px rgba(255, 255, 255, .09);
  pointer-events: none;
  transition: opacity .22s ease, transform .22s ease;
}
.copy-field__tooltip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: #201c38;
}
.copy-field__btn.is-copied .copy-field__tooltip {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
  transition: opacity .3s ease, transform .5s cubic-bezier(.34, 1.56, .64, 1);
}

.hint { margin: 0; font-size: 13px; color: rgba(244, 245, 251, .5); text-align: center; }

@media (prefers-reduced-motion: reduce) {
  .copy-field__btn { animation: none; }
  .copy-field__icon,
  .copy-field__tooltip { transition-duration: .12s; }
  .copy-field__btn.is-copied .copy-field__tooltip { transition-duration: .12s; }
}
JavaScript
// コピー成功: クリックでクリップボードへコピーし、Copied!ツールチップを弾ませて表示する
(() => {
  const btn = document.querySelector('.copy-field__btn');
  const codeEl = document.querySelector('.copy-field__code');
  if (!btn || !codeEl) return; // null安全

  let hideTimer = null;

  const showFeedback = () => {
    btn.classList.remove('is-copied');
    void btn.offsetWidth; // reflow: 連続クリックでも毎回弾ませる
    btn.classList.add('is-copied');
    window.clearTimeout(hideTimer);
    hideTimer = window.setTimeout(() => btn.classList.remove('is-copied'), 1700);
  };

  btn.addEventListener('click', () => {
    const text = codeEl.textContent || '';
    // サンドボックスiframe等でクリップボード権限がなくても、見た目のフィードバックは必ず出す
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).catch(() => {});
      }
    } catch (e) {
      /* 権限なし・非対応環境は無視 */
    }
    showFeedback();
  });
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「コピー成功」の効果を追加してください。

# 追加してほしい効果
コピー成功(遊べるUI)
コードをコピーすると「Copied!」の吹き出しがポンと弾んで現れチェックマークに切り替わり、数秒でふわっと消えるコピーボタン。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- コピー成功: コピー操作でCopied!ツールチップが弾んで現れ、チェックマークに切り替わって数秒で消える -->
<div class="stage">
  <div class="copy-field">
    <code class="copy-field__code">npm install wedelab-ui</code>
    <button class="copy-field__btn" type="button" aria-label="コードをコピー">
      <svg class="copy-field__icon copy-field__icon--copy" viewBox="0 0 24 24" aria-hidden="true">
        <rect x="8" y="7" width="11" height="13" rx="2" fill="none" stroke="currentColor" stroke-width="1.8"/>
        <path d="M5.5 16V5.5a1 1 0 0 1 1-1H16" fill="none" stroke="currentColor" stroke-width="1.8"/>
      </svg>
      <svg class="copy-field__icon copy-field__icon--check" viewBox="0 0 24 24" aria-hidden="true">
        <path d="M5 13l4.5 4.5L19 8" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
      </svg>
      <span class="copy-field__tooltip" role="status">Copied!</span>
    </button>
  </div>
  <p class="hint">クリックでコマンドをコピーします</p>
</div>

【CSS】
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  overflow: hidden;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(circle at 50% 30%, #1b1830 0%, #0d0e1a 72%);
  color: #f4f5fb;
}

.stage { display: grid; place-items: center; gap: 16px; padding: 20px; }

.copy-field {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: min(86vw, 300px);
  padding: 6px 6px 6px 18px;
  border-radius: 14px;
  background: rgba(255, 255, 255, .05);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .09), 0 10px 24px rgba(0, 0, 0, .4);
}

.copy-field__code {
  flex: 1 1 auto;
  min-width: 0;
  font: 500 13.5px/1 "Cascadia Code", "Consolas", monospace;
  color: #cdd3f0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: .01em;
}

.copy-field__btn {
  position: relative;
  flex: none;
  border: none;
  cursor: pointer;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  color: #cdd3f0;
  background: rgba(255, 255, 255, .07);
  -webkit-tap-highlight-color: transparent;
  transition: transform .12s ease;
  animation: copy-idle 3s ease-in-out infinite;
}
.copy-field__btn:active { transform: scale(.9); }
.copy-field__btn:focus-visible { outline: 2px solid #a78bfa; outline-offset: 3px; }

@keyframes copy-idle {
  0%, 100% { box-shadow: 0 0 0 rgba(167, 139, 250, 0); }
  50%      { box-shadow: 0 0 10px rgba(167, 139, 250, .38); }
}

.copy-field__icon {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 19px;
  height: 19px;
  transform: translate(-50%, -50%) scale(1);
  transition: opacity .22s ease, transform .35s cubic-bezier(.34, 1.56, .64, 1);
}
.copy-field__icon--check {
  opacity: 0;
  color: #22c55e;
  transform: translate(-50%, -50%) scale(.4);
}
.copy-field__btn.is-copied .copy-field__icon--copy {
  opacity: 0;
  transform: translate(-50%, -50%) scale(.4);
}
.copy-field__btn.is-copied .copy-field__icon--check {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

.copy-field__tooltip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  transform: translate(-50%, 6px) scale(.5);
  opacity: 0;
  padding: 6px 12px;
  border-radius: 10px;
  background: #201c38;
  color: #f4f5fb;
  font: 700 12.5px/1 "Segoe UI", system-ui, sans-serif;
  white-space: nowrap;
  box-shadow: 0 8px 20px rgba(0, 0, 0, .5), inset 0 0 0 1px rgba(255, 255, 255, .09);
  pointer-events: none;
  transition: opacity .22s ease, transform .22s ease;
}
.copy-field__tooltip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: #201c38;
}
.copy-field__btn.is-copied .copy-field__tooltip {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
  transition: opacity .3s ease, transform .5s cubic-bezier(.34, 1.56, .64, 1);
}

.hint { margin: 0; font-size: 13px; color: rgba(244, 245, 251, .5); text-align: center; }

@media (prefers-reduced-motion: reduce) {
  .copy-field__btn { animation: none; }
  .copy-field__icon,
  .copy-field__tooltip { transition-duration: .12s; }
  .copy-field__btn.is-copied .copy-field__tooltip { transition-duration: .12s; }
}

【JavaScript】
// コピー成功: クリックでクリップボードへコピーし、Copied!ツールチップを弾ませて表示する
(() => {
  const btn = document.querySelector('.copy-field__btn');
  const codeEl = document.querySelector('.copy-field__code');
  if (!btn || !codeEl) return; // null安全

  let hideTimer = null;

  const showFeedback = () => {
    btn.classList.remove('is-copied');
    void btn.offsetWidth; // reflow: 連続クリックでも毎回弾ませる
    btn.classList.add('is-copied');
    window.clearTimeout(hideTimer);
    hideTimer = window.setTimeout(() => btn.classList.remove('is-copied'), 1700);
  };

  btn.addEventListener('click', () => {
    const text = codeEl.textContent || '';
    // サンドボックスiframe等でクリップボード権限がなくても、見た目のフィードバックは必ず出す
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).catch(() => {});
      }
    } catch (e) {
      /* 権限なし・非対応環境は無視 */
    }
    showFeedback();
  });
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。