perspectiveホバーグリッド

マウス位置に追従してカードが3D傾斜し、光沢が動くインタラクティブなグリッド。サービス紹介やメニューカードを印象的に見せます。

#css#javascript#3d#hover

ライブデモ

使用例(お題: 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。