3Dフリップカード

ホバーで反転しつつポインタ追従で傾く立体カード。preserve-3dとCSS変数で会員証やプロフィールカードに。

#css#javascript#3d#transform

ライブデモ

使用例(お題: カフェ MOON BREW)

この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- MOON BREW:カフェ会員証の3Dフリップカード -->
<section class="fc-stage">
  <p class="fc-lead">MOON BREW メンバーズ</p>

  <div class="fc-scene" id="fcScene">
    <div class="fc-card" id="fcCard">
      <!-- 表面:会員証 -->
      <div class="fc-face fc-front">
        <div class="fc-shine" aria-hidden="true"></div>
        <div class="fc-top">
          <span class="fc-cup">☕</span>
          <span class="fc-grade">GOLD</span>
        </div>
        <p class="fc-brand">MOON BREW</p>
        <p class="fc-member">会員証 · No.0427</p>
        <div class="fc-bottom">
          <span class="fc-name">YUKI SATO</span>
          <span class="fc-stamp">★ 8 / 10</span>
        </div>
      </div>
      <!-- 裏面:特典 -->
      <div class="fc-face fc-back">
        <div class="fc-stripe"></div>
        <p class="fc-back-h">あと2杯で1杯無料</p>
        <p class="fc-back-note">来店ごとにスタンプ1つ。<br>10個でドリンク1杯プレゼント。</p>
        <p class="fc-hint">ホバーで反転 / 動かして傾ける</p>
      </div>
    </div>
  </div>
</section>
CSS
/* MOON BREW:会員証の3Dフリップ+ティルト */
:root {
  --cream: #f5ede1;
  --brown: #2b1d12;
  --amber: #c98a3b;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  display: grid;
  place-items: center;
  font-family: "Hiragino Mincho ProN", "Yu Mincho", "Segoe UI", serif;
  background: radial-gradient(120% 120% at 50% 0%, #f7f1e6 0%, #ecdfca 70%, #e1cfae 100%);
  color: var(--cream);
  overflow: hidden;
}

.fc-stage { display: grid; place-items: center; gap: 18px; }
.fc-lead { margin: 0; font-size: 12px; letter-spacing: 0.16em; color: #8a6a3e; }

/* 3D空間 */
.fc-scene { perspective: 1000px; }

/* カード:ホバーで反転、JSで rotateX/Y を加算 */
.fc-card {
  position: relative;
  width: 300px;
  height: 188px;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(calc(var(--ry, 0deg) + var(--flip, 0deg)));
  transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.fc-scene:hover .fc-card { --flip: 180deg; }

.fc-face {
  position: absolute;
  inset: 0;
  border-radius: 18px;
  padding: 20px 22px;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  box-shadow: 0 24px 50px -18px rgba(43, 29, 18, 0.6);
  overflow: hidden;
}

/* 表面 */
.fc-front {
  background:
    radial-gradient(120% 120% at 0% 0%, #3a2817 0%, #2b1d12 60%),
    var(--brown);
  display: flex;
  flex-direction: column;
}
.fc-shine {
  position: absolute;
  top: -60%; left: -30%;
  width: 60%; height: 220%;
  background: linear-gradient(100deg, transparent, rgba(255, 233, 196, 0.35), transparent);
  transform: rotate(8deg);
  pointer-events: none;
}
.fc-top { display: flex; align-items: center; justify-content: space-between; }
.fc-cup { font-size: 18px; }
.fc-grade {
  font-size: 10px; letter-spacing: 0.18em; font-weight: 800;
  color: var(--brown);
  background: linear-gradient(135deg, #f0cd86, var(--amber));
  padding: 3px 10px; border-radius: 999px;
}
.fc-brand { margin: 16px 0 2px; font-size: 22px; font-weight: 800; letter-spacing: 0.06em; }
.fc-member { margin: 0; font-size: 11px; color: rgba(245, 237, 225, 0.6); }
.fc-bottom { margin-top: auto; display: flex; align-items: center; justify-content: space-between; }
.fc-name { font-size: 13px; letter-spacing: 0.1em; }
.fc-stamp { font-size: 12px; color: #f0cd86; font-weight: 700; }

/* 裏面 */
.fc-back {
  transform: rotateY(180deg);
  background:
    radial-gradient(120% 120% at 100% 100%, #44301f 0%, #2b1d12 60%),
    var(--brown);
  display: flex;
  flex-direction: column;
}
.fc-stripe { height: 30px; margin: -20px -22px 14px; background: #1c130a; }
.fc-back-h { margin: 0 0 8px; font-size: 16px; font-weight: 800; color: #f0cd86; }
.fc-back-note { margin: 0; font-size: 12px; line-height: 1.7; color: rgba(245, 237, 225, 0.75); }
.fc-hint { margin-top: auto; font-size: 10px; letter-spacing: 0.06em; color: rgba(245, 237, 225, 0.5); }

@media (prefers-reduced-motion: reduce) {
  .fc-card { transition: none; }
}
JavaScript
// MOON BREW:会員証カードをポインタ追従で傾ける(反転はCSSホバー)
(() => {
  const scene = document.getElementById("fcScene");
  const card = document.getElementById("fcCard");
  if (!scene || !card) return; // null安全

  const MAX = 8; // 最大傾き角(度)

  scene.addEventListener("pointermove", (e) => {
    const rect = card.getBoundingClientRect();
    // カード中心からの相対位置を -0.5〜0.5 に正規化
    const px = (e.clientX - rect.left) / rect.width - 0.5;
    const py = (e.clientY - rect.top) / rect.height - 0.5;
    card.style.setProperty("--rx", `${(-py * MAX).toFixed(2)}deg`);
    card.style.setProperty("--ry", `${(px * MAX).toFixed(2)}deg`);
  });

  // 離れたら水平に戻す
  scene.addEventListener("pointerleave", () => {
    card.style.setProperty("--rx", "0deg");
    card.style.setProperty("--ry", "0deg");
  });
})();

コード

HTML
<!-- 3Dフリップ+ティルト:ホバーで反転、ポインタ追従で傾く立体カード -->
<div class="flip-stage">
  <div class="flip-scene" id="flipScene">
    <div class="flip-card" id="flipCard">
      <!-- 表面 -->
      <div class="flip-face flip-front">
        <span class="flip-tag">MEMBER</span>
        <div class="flip-shine" aria-hidden="true"></div>
        <h3 class="flip-name">AURORA PASS</h3>
        <p class="flip-no">**** 4821</p>
      </div>
      <!-- 裏面 -->
      <div class="flip-face flip-back">
        <div class="flip-stripe"></div>
        <p class="flip-back-label">Member since 2024</p>
        <p class="flip-back-note">ホバーで反転 / 動かして傾ける</p>
      </div>
    </div>
  </div>
</div>
CSS
/* 3D空間にカードを置き、ホバーで反転+ポインタで傾ける */
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 360px;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Sans", "Yu Gothic UI", system-ui, sans-serif;
  background: radial-gradient(120% 120% at 50% 0%, #20243f 0%, #0b0c18 70%);
  color: #fff;
}
/* 遠近感の親 */
.flip-scene { perspective: 900px; }

/* 傾き(--rx/--ry はJS) と 反転を合成 */
.flip-card {
  position: relative;
  width: 280px; height: 176px;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(calc(var(--ry, 0deg) + var(--flip, 0deg)));
  transition: transform .15s ease-out;
  cursor: pointer;
}
/* ホバーで裏返す(--flip を180度に) */
.flip-scene:hover .flip-card { --flip: 180deg; }

.flip-face {
  position: absolute; inset: 0;
  border-radius: 16px;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  padding: 18px;
  overflow: hidden;
  box-shadow: 0 26px 50px -18px rgba(0,0,0,.7);
  display: grid;
  align-content: space-between;
}
.flip-front {
  background: linear-gradient(135deg, #7028e4 0%, #38a0f9 55%, #38f9d7 100%);
}
.flip-back {
  background: linear-gradient(135deg, #232744 0%, #3a3f6b 100%);
  transform: rotateY(180deg);
  align-content: start;
  gap: 14px;
}

.flip-tag {
  font-size: 10px; font-weight: 800; letter-spacing: .28em;
  opacity: .85;
}
.flip-name { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: .02em; }
.flip-no {
  margin: 0; font-size: 15px; letter-spacing: .22em;
  font-family: "Consolas", monospace; opacity: .92;
}

/* 表面の動く光沢 */
.flip-shine {
  position: absolute; inset: -40%;
  background: linear-gradient(115deg, transparent 40%, rgba(255,255,255,.5) 50%, transparent 60%);
  transform: translateX(-30%);
  animation: flipShine 4.5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes flipShine {
  0%, 60% { transform: translateX(-60%); }
  100% { transform: translateX(60%); }
}

.flip-stripe {
  height: 36px; margin: 4px -18px 0;
  background: #11131f;
}
.flip-back-label { margin: 0; font-size: 13px; font-weight: 700; color: #cfd4ff; }
.flip-back-note { margin: 0; font-size: 12px; color: #9aa0cf; }

@media (prefers-reduced-motion: reduce) {
  .flip-card { transition: none; }
  .flip-shine { animation: none; }
}
JavaScript
// 3Dティルト:ポインタ位置に応じて --rx/--ry を更新し立体的に傾ける
(() => {
  const scene = document.getElementById('flipScene');
  const card = document.getElementById('flipCard');
  if (!scene || !card) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const MAX = 12; // 最大傾き(deg)

  // ポインタ座標→カード中心からの比率→回転量
  const onMove = (e) => {
    const rect = card.getBoundingClientRect();
    const px = (e.clientX - rect.left) / rect.width;  // 0..1
    const py = (e.clientY - rect.top) / rect.height;  // 0..1
    const ry = (px - 0.5) * 2 * MAX;   // 左右で Y軸回転
    const rx = -(py - 0.5) * 2 * MAX;  // 上下で X軸回転
    card.style.setProperty('--ry', ry.toFixed(2) + 'deg');
    card.style.setProperty('--rx', rx.toFixed(2) + 'deg');
  };

  // 離れたら水平に戻す
  const reset = () => {
    card.style.setProperty('--ry', '0deg');
    card.style.setProperty('--rx', '0deg');
  };

  if (!reduce) {
    scene.addEventListener('pointermove', onMove);
    scene.addEventListener('pointerleave', reset);
  }
})();

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

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

# 追加してほしい効果
3Dフリップカード(アニメーション & トランジション)
ホバーで反転しつつポインタ追従で傾く立体カード。preserve-3dとCSS変数で会員証やプロフィールカードに。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 3Dフリップ+ティルト:ホバーで反転、ポインタ追従で傾く立体カード -->
<div class="flip-stage">
  <div class="flip-scene" id="flipScene">
    <div class="flip-card" id="flipCard">
      <!-- 表面 -->
      <div class="flip-face flip-front">
        <span class="flip-tag">MEMBER</span>
        <div class="flip-shine" aria-hidden="true"></div>
        <h3 class="flip-name">AURORA PASS</h3>
        <p class="flip-no">**** 4821</p>
      </div>
      <!-- 裏面 -->
      <div class="flip-face flip-back">
        <div class="flip-stripe"></div>
        <p class="flip-back-label">Member since 2024</p>
        <p class="flip-back-note">ホバーで反転 / 動かして傾ける</p>
      </div>
    </div>
  </div>
</div>

【CSS】
/* 3D空間にカードを置き、ホバーで反転+ポインタで傾ける */
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 360px;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Sans", "Yu Gothic UI", system-ui, sans-serif;
  background: radial-gradient(120% 120% at 50% 0%, #20243f 0%, #0b0c18 70%);
  color: #fff;
}
/* 遠近感の親 */
.flip-scene { perspective: 900px; }

/* 傾き(--rx/--ry はJS) と 反転を合成 */
.flip-card {
  position: relative;
  width: 280px; height: 176px;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(calc(var(--ry, 0deg) + var(--flip, 0deg)));
  transition: transform .15s ease-out;
  cursor: pointer;
}
/* ホバーで裏返す(--flip を180度に) */
.flip-scene:hover .flip-card { --flip: 180deg; }

.flip-face {
  position: absolute; inset: 0;
  border-radius: 16px;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  padding: 18px;
  overflow: hidden;
  box-shadow: 0 26px 50px -18px rgba(0,0,0,.7);
  display: grid;
  align-content: space-between;
}
.flip-front {
  background: linear-gradient(135deg, #7028e4 0%, #38a0f9 55%, #38f9d7 100%);
}
.flip-back {
  background: linear-gradient(135deg, #232744 0%, #3a3f6b 100%);
  transform: rotateY(180deg);
  align-content: start;
  gap: 14px;
}

.flip-tag {
  font-size: 10px; font-weight: 800; letter-spacing: .28em;
  opacity: .85;
}
.flip-name { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: .02em; }
.flip-no {
  margin: 0; font-size: 15px; letter-spacing: .22em;
  font-family: "Consolas", monospace; opacity: .92;
}

/* 表面の動く光沢 */
.flip-shine {
  position: absolute; inset: -40%;
  background: linear-gradient(115deg, transparent 40%, rgba(255,255,255,.5) 50%, transparent 60%);
  transform: translateX(-30%);
  animation: flipShine 4.5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes flipShine {
  0%, 60% { transform: translateX(-60%); }
  100% { transform: translateX(60%); }
}

.flip-stripe {
  height: 36px; margin: 4px -18px 0;
  background: #11131f;
}
.flip-back-label { margin: 0; font-size: 13px; font-weight: 700; color: #cfd4ff; }
.flip-back-note { margin: 0; font-size: 12px; color: #9aa0cf; }

@media (prefers-reduced-motion: reduce) {
  .flip-card { transition: none; }
  .flip-shine { animation: none; }
}

【JavaScript】
// 3Dティルト:ポインタ位置に応じて --rx/--ry を更新し立体的に傾ける
(() => {
  const scene = document.getElementById('flipScene');
  const card = document.getElementById('flipCard');
  if (!scene || !card) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const MAX = 12; // 最大傾き(deg)

  // ポインタ座標→カード中心からの比率→回転量
  const onMove = (e) => {
    const rect = card.getBoundingClientRect();
    const px = (e.clientX - rect.left) / rect.width;  // 0..1
    const py = (e.clientY - rect.top) / rect.height;  // 0..1
    const ry = (px - 0.5) * 2 * MAX;   // 左右で Y軸回転
    const rx = -(py - 0.5) * 2 * MAX;  // 上下で X軸回転
    card.style.setProperty('--ry', ry.toFixed(2) + 'deg');
    card.style.setProperty('--rx', rx.toFixed(2) + 'deg');
  };

  // 離れたら水平に戻す
  const reset = () => {
    card.style.setProperty('--ry', '0deg');
    card.style.setProperty('--rx', '0deg');
  };

  if (!reduce) {
    scene.addEventListener('pointermove', onMove);
    scene.addEventListener('pointerleave', reset);
  }
})();

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

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