perspectiveホバーグリッド
マウス位置に追従してカードが3D傾斜し、光沢が動くインタラクティブなグリッド。サービス紹介やメニューカードを印象的に見せます。
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- FlowDesk:マウス追従で3D傾斜する機能紹介カードグリッド -->
<section class="fd-features" aria-label="FlowDesk 主な機能">
<header class="fd-features__head">
<span class="fd-features__logo">▰ FlowDesk</span>
<h2 class="fd-features__title">チームの仕事を、ひとつに。</h2>
<p class="fd-features__sub">カードにマウスを乗せると傾きます</p>
</header>
<div class="fd-grid" id="fdGrid">
<article class="fd-card" tabindex="0">
<div class="fd-card__shine"></div>
<span class="fd-card__icon">📊</span>
<h3 class="fd-card__name">ダッシュボード</h3>
<p class="fd-card__desc">KPIをリアルタイムで一望。指標は自由に並べ替え。</p>
</article>
<article class="fd-card" tabindex="0">
<div class="fd-card__shine"></div>
<span class="fd-card__icon">⚡</span>
<h3 class="fd-card__name">自動ワークフロー</h3>
<p class="fd-card__desc">条件をつなぐだけで定型業務を完全自動化。</p>
</article>
<article class="fd-card" tabindex="0">
<div class="fd-card__shine"></div>
<span class="fd-card__icon">🔒</span>
<h3 class="fd-card__name">権限管理</h3>
<p class="fd-card__desc">役割ごとに細かくアクセスを制御。監査ログ完備。</p>
</article>
<article class="fd-card" tabindex="0">
<div class="fd-card__shine"></div>
<span class="fd-card__icon">🔗</span>
<h3 class="fd-card__name">連携</h3>
<p class="fd-card__desc">主要ツールと双方向に同期。APIも公開中。</p>
</article>
</div>
</section>
CSS
/* FlowDesk:紺×青のperspectiveホバーグリッド */
:root {
--navy: #0f1b34;
--blue: #4f7cff;
--white: #ffffff;
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 400px;
font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
background:
radial-gradient(circle at 80% 0%, #1b2c52 0%, var(--navy) 60%);
color: var(--white);
overflow: hidden;
}
.fd-features { padding: 18px 22px; height: 100%; }
.fd-features__head { margin-bottom: 14px; }
.fd-features__logo { font-size: 13px; font-weight: 800; letter-spacing: 0.1em; color: var(--blue); }
.fd-features__title { margin: 6px 0 2px; font-size: 21px; font-weight: 800; }
.fd-features__sub { margin: 0; font-size: 11px; color: rgba(255,255,255,.5); }
/* グリッド:各カードに個別の perspective を持たせる */
.fd-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.fd-card {
position: relative;
padding: 16px;
border-radius: 14px;
background: linear-gradient(155deg, rgba(79,124,255,.16), rgba(255,255,255,.05));
border: 1px solid rgba(79,124,255,.3);
box-shadow: 0 12px 26px rgba(0,0,0,.35);
outline: none;
/* JSが --rx / --ry を設定して傾ける */
transform: perspective(700px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
transform-style: preserve-3d;
transition: transform 0.15s ease, box-shadow 0.2s ease;
cursor: pointer;
}
.fd-card:hover, .fd-card:focus-visible {
box-shadow: 0 18px 36px rgba(79,124,255,.35);
border-color: rgba(79,124,255,.6);
}
/* マウス追従の光沢 */
.fd-card__shine {
position: absolute; inset: 0;
border-radius: 14px;
background: radial-gradient(160px circle at var(--mx, 50%) var(--my, 0%),
rgba(255,255,255,.35), transparent 60%);
opacity: 0;
transition: opacity 0.25s ease;
pointer-events: none;
}
.fd-card:hover .fd-card__shine { opacity: 1; }
/* 中身は少し手前へ浮かせて立体感 */
.fd-card__icon,
.fd-card__name,
.fd-card__desc { transform: translateZ(28px); }
.fd-card__icon { font-size: 26px; display: block; }
.fd-card__name { margin: 8px 0 4px; font-size: 15px; font-weight: 700; }
.fd-card__desc { margin: 0; font-size: 11.5px; line-height: 1.6; color: rgba(255,255,255,.7); }
@media (prefers-reduced-motion: reduce) {
.fd-card { transition: none; }
}
JavaScript
// FlowDesk perspectiveグリッド:マウス位置でカードを3D傾斜+光沢追従
(() => {
const grid = document.getElementById("fdGrid");
if (!grid) return; // null安全
const cards = Array.from(grid.querySelectorAll(".fd-card"));
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const MAX = 9; // 最大傾斜角(度)
cards.forEach((card) => {
const shine = card.querySelector(".fd-card__shine");
card.addEventListener("pointermove", (e) => {
if (reduce) return;
const r = card.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width; // 0〜1
const py = (e.clientY - r.top) / r.height; // 0〜1
// 中心からの距離で傾ける(上下は反転)
card.style.setProperty("--ry", `${(px - 0.5) * 2 * MAX}deg`);
card.style.setProperty("--rx", `${(0.5 - py) * 2 * MAX}deg`);
if (shine) {
shine.style.setProperty("--mx", `${px * 100}%`);
shine.style.setProperty("--my", `${py * 100}%`);
}
});
// 離れたら水平に戻す
card.addEventListener("pointerleave", () => {
card.style.setProperty("--rx", "0deg");
card.style.setProperty("--ry", "0deg");
});
});
})();
コード
HTML
<div class="tilt-grid" aria-label="perspectiveホバーグリッドのデモ">
<!-- 各カードはマウス位置で3D傾斜+光沢 -->
<article class="tilt-card" data-label="NEBULA">
<div class="tilt-card__inner"><span class="tilt-card__icon">✦</span><h3>NEBULA</h3><p>星雲</p></div>
</article>
<article class="tilt-card" data-label="PRISM">
<div class="tilt-card__inner"><span class="tilt-card__icon">◈</span><h3>PRISM</h3><p>分光</p></div>
</article>
<article class="tilt-card" data-label="ORBIT">
<div class="tilt-card__inner"><span class="tilt-card__icon">◉</span><h3>ORBIT</h3><p>軌道</p></div>
</article>
<article class="tilt-card" data-label="PULSE">
<div class="tilt-card__inner"><span class="tilt-card__icon">❋</span><h3>PULSE</h3><p>鼓動</p></div>
</article>
</div>
CSS
/* ===== perspectiveホバーグリッド ===== */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 360px;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(circle at 20% 0%, #2b2150 0%, transparent 55%),
radial-gradient(circle at 90% 100%, #15304f 0%, transparent 50%),
#070912;
overflow: hidden;
}
.tilt-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
padding: 16px;
}
@media (max-width: 560px) {
.tilt-grid { grid-template-columns: repeat(2, 1fr); }
}
.tilt-card {
width: 130px;
height: 160px;
perspective: 600px; /* 個別に視点を持たせる */
}
.tilt-card__inner {
position: relative;
width: 100%;
height: 100%;
border-radius: 16px;
display: grid;
place-content: center;
text-align: center;
color: #fff;
background: linear-gradient(155deg, rgba(255,255,255,.12), rgba(255,255,255,.03));
border: 1px solid rgba(255,255,255,.14);
box-shadow: 0 10px 30px rgba(0,0,0,.4);
/* JSが --rx / --ry を更新して傾ける */
transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
transform-style: preserve-3d;
transition: transform .25s ease, box-shadow .25s ease;
overflow: hidden;
}
.tilt-card:hover .tilt-card__inner {
box-shadow: 0 20px 50px rgba(0,0,0,.55);
}
/* マウス位置に追従する光沢(--mx/--my はJSが%で設定) */
.tilt-card__inner::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(
220px circle at var(--mx, 50%) var(--my, 50%),
rgba(255,255,255,.35),
transparent 60%
);
opacity: 0;
transition: opacity .25s ease;
}
.tilt-card:hover .tilt-card__inner::before { opacity: 1; }
.tilt-card__icon {
font-size: 34px;
transform: translateZ(34px); /* 浮き上がって見える */
filter: drop-shadow(0 6px 12px rgba(0,0,0,.4));
}
.tilt-card h3 {
margin: 12px 0 2px;
font-size: 15px; letter-spacing: .18em; font-weight: 700;
transform: translateZ(24px);
}
.tilt-card p {
margin: 0;
font-size: 11px; color: rgba(255,255,255,.6); letter-spacing: .2em;
transform: translateZ(18px);
}
/* 4枚に異なるアクセント色 */
.tilt-card:nth-child(1) .tilt-card__inner { border-color: rgba(167,139,250,.5); }
.tilt-card:nth-child(2) .tilt-card__inner { border-color: rgba(110,231,255,.5); }
.tilt-card:nth-child(3) .tilt-card__inner { border-color: rgba(251,191,114,.5); }
.tilt-card:nth-child(4) .tilt-card__inner { border-color: rgba(244,114,182,.5); }
@media (prefers-reduced-motion: reduce) {
.tilt-card__inner { transition: none; }
}
JavaScript
// perspectiveホバーグリッド: マウス位置でカードを3D傾斜+光沢追従
(() => {
const cards = Array.from(document.querySelectorAll('.tilt-card'));
if (cards.length === 0) return; // null安全
const MAX = 12; // 最大傾斜角(度)
cards.forEach((card) => {
const inner = card.querySelector('.tilt-card__inner');
if (!inner) return;
const onMove = (e) => {
const r = card.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width; // 0〜1
const py = (e.clientY - r.top) / r.height; // 0〜1
// 中心からのずれを角度に変換(Y軸は左右、X軸は上下で符号反転)
const ry = (px - 0.5) * 2 * MAX;
const rx = -(py - 0.5) * 2 * MAX;
inner.style.setProperty('--ry', ry.toFixed(2) + 'deg');
inner.style.setProperty('--rx', rx.toFixed(2) + 'deg');
// 光沢の中心
inner.style.setProperty('--mx', (px * 100).toFixed(1) + '%');
inner.style.setProperty('--my', (py * 100).toFixed(1) + '%');
};
const reset = () => {
inner.style.setProperty('--ry', '0deg');
inner.style.setProperty('--rx', '0deg');
};
card.addEventListener('pointermove', onMove);
card.addEventListener('pointerleave', reset);
});
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「perspectiveホバーグリッド」の効果を追加してください。
# 追加してほしい効果
perspectiveホバーグリッド(3D & パースペクティブ)
マウス位置に追従してカードが3D傾斜し、光沢が動くインタラクティブなグリッド。サービス紹介やメニューカードを印象的に見せます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<div class="tilt-grid" aria-label="perspectiveホバーグリッドのデモ">
<!-- 各カードはマウス位置で3D傾斜+光沢 -->
<article class="tilt-card" data-label="NEBULA">
<div class="tilt-card__inner"><span class="tilt-card__icon">✦</span><h3>NEBULA</h3><p>星雲</p></div>
</article>
<article class="tilt-card" data-label="PRISM">
<div class="tilt-card__inner"><span class="tilt-card__icon">◈</span><h3>PRISM</h3><p>分光</p></div>
</article>
<article class="tilt-card" data-label="ORBIT">
<div class="tilt-card__inner"><span class="tilt-card__icon">◉</span><h3>ORBIT</h3><p>軌道</p></div>
</article>
<article class="tilt-card" data-label="PULSE">
<div class="tilt-card__inner"><span class="tilt-card__icon">❋</span><h3>PULSE</h3><p>鼓動</p></div>
</article>
</div>
【CSS】
/* ===== perspectiveホバーグリッド ===== */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 360px;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(circle at 20% 0%, #2b2150 0%, transparent 55%),
radial-gradient(circle at 90% 100%, #15304f 0%, transparent 50%),
#070912;
overflow: hidden;
}
.tilt-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
padding: 16px;
}
@media (max-width: 560px) {
.tilt-grid { grid-template-columns: repeat(2, 1fr); }
}
.tilt-card {
width: 130px;
height: 160px;
perspective: 600px; /* 個別に視点を持たせる */
}
.tilt-card__inner {
position: relative;
width: 100%;
height: 100%;
border-radius: 16px;
display: grid;
place-content: center;
text-align: center;
color: #fff;
background: linear-gradient(155deg, rgba(255,255,255,.12), rgba(255,255,255,.03));
border: 1px solid rgba(255,255,255,.14);
box-shadow: 0 10px 30px rgba(0,0,0,.4);
/* JSが --rx / --ry を更新して傾ける */
transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
transform-style: preserve-3d;
transition: transform .25s ease, box-shadow .25s ease;
overflow: hidden;
}
.tilt-card:hover .tilt-card__inner {
box-shadow: 0 20px 50px rgba(0,0,0,.55);
}
/* マウス位置に追従する光沢(--mx/--my はJSが%で設定) */
.tilt-card__inner::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(
220px circle at var(--mx, 50%) var(--my, 50%),
rgba(255,255,255,.35),
transparent 60%
);
opacity: 0;
transition: opacity .25s ease;
}
.tilt-card:hover .tilt-card__inner::before { opacity: 1; }
.tilt-card__icon {
font-size: 34px;
transform: translateZ(34px); /* 浮き上がって見える */
filter: drop-shadow(0 6px 12px rgba(0,0,0,.4));
}
.tilt-card h3 {
margin: 12px 0 2px;
font-size: 15px; letter-spacing: .18em; font-weight: 700;
transform: translateZ(24px);
}
.tilt-card p {
margin: 0;
font-size: 11px; color: rgba(255,255,255,.6); letter-spacing: .2em;
transform: translateZ(18px);
}
/* 4枚に異なるアクセント色 */
.tilt-card:nth-child(1) .tilt-card__inner { border-color: rgba(167,139,250,.5); }
.tilt-card:nth-child(2) .tilt-card__inner { border-color: rgba(110,231,255,.5); }
.tilt-card:nth-child(3) .tilt-card__inner { border-color: rgba(251,191,114,.5); }
.tilt-card:nth-child(4) .tilt-card__inner { border-color: rgba(244,114,182,.5); }
@media (prefers-reduced-motion: reduce) {
.tilt-card__inner { transition: none; }
}
【JavaScript】
// perspectiveホバーグリッド: マウス位置でカードを3D傾斜+光沢追従
(() => {
const cards = Array.from(document.querySelectorAll('.tilt-card'));
if (cards.length === 0) return; // null安全
const MAX = 12; // 最大傾斜角(度)
cards.forEach((card) => {
const inner = card.querySelector('.tilt-card__inner');
if (!inner) return;
const onMove = (e) => {
const r = card.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width; // 0〜1
const py = (e.clientY - r.top) / r.height; // 0〜1
// 中心からのずれを角度に変換(Y軸は左右、X軸は上下で符号反転)
const ry = (px - 0.5) * 2 * MAX;
const rx = -(py - 0.5) * 2 * MAX;
inner.style.setProperty('--ry', ry.toFixed(2) + 'deg');
inner.style.setProperty('--rx', rx.toFixed(2) + 'deg');
// 光沢の中心
inner.style.setProperty('--mx', (px * 100).toFixed(1) + '%');
inner.style.setProperty('--my', (py * 100).toFixed(1) + '%');
};
const reset = () => {
inner.style.setProperty('--ry', '0deg');
inner.style.setProperty('--rx', '0deg');
};
card.addEventListener('pointermove', onMove);
card.addEventListener('pointerleave', reset);
});
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。