遊べるUI 中級

弾むチェック

クリックでチェックマークが跳ねながら描かれ、ボックスが弾性スケールし背景色がふわっと波及するチェックボックス。同意フォームなどに使えます。

#css#svg#checkbox#animation

ライブデモ

コード

HTML
<!-- 弾むチェック: チェックマークが跳ねながら描かれ、ボックスが弾性スケールし波紋が広がる -->
<div class="stage">
  <label class="elastic-check">
    <input type="checkbox" class="elastic-check__input" />
    <span class="elastic-check__box" aria-hidden="true">
      <svg class="elastic-check__icon" viewBox="0 0 24 24">
        <rect class="elastic-check__rect" x="2" y="2" width="20" height="20" rx="6" />
        <path class="elastic-check__mark" d="M6 12.5l4 4 8-9" />
      </svg>
    </span>
    <span class="elastic-check__label">通知を受け取る</span>
  </label>
  <p class="hint">クリックしてON/OFF</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%, #151b2e 0%, #0d0e1a 72%);
  color: #f4f5fb;
}

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

.elastic-check {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 12px 20px;
  border-radius: 16px;
  cursor: pointer;
  background: rgba(255, 255, 255, .04);
  -webkit-tap-highlight-color: transparent;
}

/* ネイティブinputは視覚的に隠すがフォーカス・操作性は維持する */
.elastic-check__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.elastic-check__box {
  position: relative;
  width: 30px;
  height: 30px;
  flex: none;
  animation: check-idle 3.4s ease-in-out infinite;
}
.elastic-check__input:checked ~ .elastic-check__box { animation: none; }
.elastic-check__input:focus-visible ~ .elastic-check__box {
  outline: 2px solid #34d399;
  outline-offset: 3px;
  border-radius: 8px;
}

@keyframes check-idle {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(52, 211, 153, 0)); }
  50%      { filter: drop-shadow(0 0 5px rgba(52, 211, 153, .35)); }
}

.elastic-check__icon { width: 100%; height: 100%; overflow: visible; }

.elastic-check__rect {
  fill: transparent;
  stroke: rgba(255, 255, 255, .35);
  stroke-width: 1.6;
  transition: fill .35s ease, stroke .35s ease;
}
.elastic-check__input:checked ~ .elastic-check__box .elastic-check__rect {
  fill: #1c8f6b;
  stroke: #34d399;
}

.elastic-check__mark {
  fill: none;
  stroke: #f4f5fb;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 22;
  stroke-dashoffset: 22;
  transition: stroke-dashoffset .5s cubic-bezier(.34, 1.56, .64, 1);
}
.elastic-check__input:checked ~ .elastic-check__box .elastic-check__mark {
  stroke-dashoffset: 0;
}

/* 波紋(::after)。チェックが入るたびJSでクラスを再付与して背景色を波及させる */
.elastic-check__box::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 8px;
  background: #34d399;
  opacity: 0;
  transform: scale(.4);
  pointer-events: none;
  z-index: -1;
}
.elastic-check__box.is-wave::after {
  animation: wave-ripple .55s ease-out;
}
@keyframes wave-ripple {
  0%   { opacity: .5; transform: scale(.4); }
  100% { opacity: 0;  transform: scale(2.1); }
}

/* ボックス自体の弾性スケール(チェック・解除どちらでも再生) */
.elastic-check__box.is-pop { animation: elastic-pop .5s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes elastic-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.22); }
  70%  { transform: scale(.92); }
  100% { transform: scale(1); }
}

.elastic-check__label {
  font-size: 15px;
  font-weight: 600;
  color: #f4f5fb;
}

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

@media (prefers-reduced-motion: reduce) {
  .elastic-check__box { animation: none; }
  .elastic-check__mark { transition-duration: .15s; }
  .elastic-check__box.is-wave::after,
  .elastic-check__box.is-pop { animation: none; }
}
JavaScript
// 弾むチェック: state変化のたびにボックスを弾性スケールさせ、ONの時だけ波紋を広げる
(() => {
  const input = document.querySelector('.elastic-check__input');
  const box = document.querySelector('.elastic-check__box');
  const hint = document.querySelector('.hint');
  if (!input || !box) return; // null安全

  input.addEventListener('change', () => {
    // 弾性スケールはON/OFFどちらでも再生。連続操作でも途切れないようreflowで強制的に再起動する
    box.classList.remove('is-pop');
    void box.offsetWidth; // reflow
    box.classList.add('is-pop');

    if (input.checked) {
      box.classList.remove('is-wave');
      void box.offsetWidth; // reflow
      box.classList.add('is-wave');
    }

    if (hint) {
      hint.textContent = input.checked ? 'もう一度クリックでOFFに戻せます' : 'クリックしてON/OFF';
    }

    window.setTimeout(() => {
      box.classList.remove('is-pop', 'is-wave');
    }, 560);
  });
})();

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

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

# 追加してほしい効果
弾むチェック(遊べるUI)
クリックでチェックマークが跳ねながら描かれ、ボックスが弾性スケールし背景色がふわっと波及するチェックボックス。同意フォームなどに使えます。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 弾むチェック: チェックマークが跳ねながら描かれ、ボックスが弾性スケールし波紋が広がる -->
<div class="stage">
  <label class="elastic-check">
    <input type="checkbox" class="elastic-check__input" />
    <span class="elastic-check__box" aria-hidden="true">
      <svg class="elastic-check__icon" viewBox="0 0 24 24">
        <rect class="elastic-check__rect" x="2" y="2" width="20" height="20" rx="6" />
        <path class="elastic-check__mark" d="M6 12.5l4 4 8-9" />
      </svg>
    </span>
    <span class="elastic-check__label">通知を受け取る</span>
  </label>
  <p class="hint">クリックしてON/OFF</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%, #151b2e 0%, #0d0e1a 72%);
  color: #f4f5fb;
}

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

.elastic-check {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 12px 20px;
  border-radius: 16px;
  cursor: pointer;
  background: rgba(255, 255, 255, .04);
  -webkit-tap-highlight-color: transparent;
}

/* ネイティブinputは視覚的に隠すがフォーカス・操作性は維持する */
.elastic-check__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.elastic-check__box {
  position: relative;
  width: 30px;
  height: 30px;
  flex: none;
  animation: check-idle 3.4s ease-in-out infinite;
}
.elastic-check__input:checked ~ .elastic-check__box { animation: none; }
.elastic-check__input:focus-visible ~ .elastic-check__box {
  outline: 2px solid #34d399;
  outline-offset: 3px;
  border-radius: 8px;
}

@keyframes check-idle {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(52, 211, 153, 0)); }
  50%      { filter: drop-shadow(0 0 5px rgba(52, 211, 153, .35)); }
}

.elastic-check__icon { width: 100%; height: 100%; overflow: visible; }

.elastic-check__rect {
  fill: transparent;
  stroke: rgba(255, 255, 255, .35);
  stroke-width: 1.6;
  transition: fill .35s ease, stroke .35s ease;
}
.elastic-check__input:checked ~ .elastic-check__box .elastic-check__rect {
  fill: #1c8f6b;
  stroke: #34d399;
}

.elastic-check__mark {
  fill: none;
  stroke: #f4f5fb;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 22;
  stroke-dashoffset: 22;
  transition: stroke-dashoffset .5s cubic-bezier(.34, 1.56, .64, 1);
}
.elastic-check__input:checked ~ .elastic-check__box .elastic-check__mark {
  stroke-dashoffset: 0;
}

/* 波紋(::after)。チェックが入るたびJSでクラスを再付与して背景色を波及させる */
.elastic-check__box::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 8px;
  background: #34d399;
  opacity: 0;
  transform: scale(.4);
  pointer-events: none;
  z-index: -1;
}
.elastic-check__box.is-wave::after {
  animation: wave-ripple .55s ease-out;
}
@keyframes wave-ripple {
  0%   { opacity: .5; transform: scale(.4); }
  100% { opacity: 0;  transform: scale(2.1); }
}

/* ボックス自体の弾性スケール(チェック・解除どちらでも再生) */
.elastic-check__box.is-pop { animation: elastic-pop .5s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes elastic-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.22); }
  70%  { transform: scale(.92); }
  100% { transform: scale(1); }
}

.elastic-check__label {
  font-size: 15px;
  font-weight: 600;
  color: #f4f5fb;
}

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

@media (prefers-reduced-motion: reduce) {
  .elastic-check__box { animation: none; }
  .elastic-check__mark { transition-duration: .15s; }
  .elastic-check__box.is-wave::after,
  .elastic-check__box.is-pop { animation: none; }
}

【JavaScript】
// 弾むチェック: state変化のたびにボックスを弾性スケールさせ、ONの時だけ波紋を広げる
(() => {
  const input = document.querySelector('.elastic-check__input');
  const box = document.querySelector('.elastic-check__box');
  const hint = document.querySelector('.hint');
  if (!input || !box) return; // null安全

  input.addEventListener('change', () => {
    // 弾性スケールはON/OFFどちらでも再生。連続操作でも途切れないようreflowで強制的に再起動する
    box.classList.remove('is-pop');
    void box.offsetWidth; // reflow
    box.classList.add('is-pop');

    if (input.checked) {
      box.classList.remove('is-wave');
      void box.offsetWidth; // reflow
      box.classList.add('is-wave');
    }

    if (hint) {
      hint.textContent = input.checked ? 'もう一度クリックでOFFに戻せます' : 'クリックしてON/OFF';
    }

    window.setTimeout(() => {
      box.classList.remove('is-pop', 'is-wave');
    }, 560);
  });
})();

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

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