3Dフリップカード
ホバーで反転しつつポインタ追従で傾く立体カード。preserve-3dとCSS変数で会員証やプロフィールカードに。
ライブデモ
使用例(お題: カフェ 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。