3Dティルトカード

マウス位置を回転角に変換し、CSSのperspectiveでカードが立体的に傾き光沢が走ります。商品カードやプロフィールカードの演出に最適です。

#js#css#3d#transform

ライブデモ

使用例(お題: アイドルグループ Sakura)

この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- Sakura: メンバー紹介。3Dティルトカードを主役に -->
<div class="sk">
  <header class="sk__head">
    <span class="sk__logo">🌸 Sakura</span>
    <span class="sk__sub">MEMBER</span>
  </header>

  <div class="sk__stage">
    <div class="tilt" tabindex="0">
      <div class="tilt__inner">
        <div class="tilt__photo">
          <img src="https://picsum.photos/300/300?random=41" alt="メンバーのプロフィール写真" loading="lazy">
          <span class="tilt__color">PINK</span>
        </div>
        <div class="tilt__info">
          <p class="tilt__role">センター / リーダー</p>
          <h1 class="tilt__name">星野 ひより</h1>
          <p class="tilt__msg">「今日も全力でお届けします!」</p>
          <div class="tilt__tags"><span>歌</span><span>ダンス</span><span>春生まれ</span></div>
        </div>
        <span class="tilt__glare" aria-hidden="true"></span>
      </div>
    </div>
    <p class="sk__hint">カードにカーソルを乗せると立体的に傾きます</p>
  </div>
</div>
CSS
/* Sakura アイドル テーマ: 桜ピンク/白/淡グレー */
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
  background:
    radial-gradient(circle at 80% 15%, #ffe3ee 0%, transparent 50%),
    radial-gradient(circle at 15% 85%, #ffd1e0 0%, transparent 45%),
    #fff7fb;
  color: #6b4a58;
}

.sk {
  height: 400px;
  display: flex;
  flex-direction: column;
  padding: 0 28px;
}
.sk__head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 18px 0;
}
.sk__logo {
  font-size: 18px;
  font-weight: 800;
  color: #e85a92;
}
.sk__sub {
  font-size: 12px;
  letter-spacing: .24em;
  color: #c79bb0;
}

.sk__stage {
  flex: 1;
  display: grid;
  place-content: center;
  gap: 16px;
  perspective: 900px;
}

/* 主役: 3Dティルトカード */
.tilt {
  --rx: 0deg;
  --ry: 0deg;
  --mx: 50%;
  --my: 50%;
  width: 320px;
  border-radius: 20px;
  outline: none;
  cursor: pointer;
}
.tilt__inner {
  position: relative;
  display: flex;
  gap: 14px;
  padding: 14px;
  border-radius: 20px;
  background: #ffffff;
  box-shadow: 0 22px 44px rgba(232,90,146,.28);
  transform: rotateX(var(--rx)) rotateY(var(--ry));
  transform-style: preserve-3d;
  transition: transform .2s ease;
  overflow: hidden;
}
.tilt__photo {
  position: relative;
  width: 110px;
  height: 130px;
  flex: none;
  border-radius: 14px;
  overflow: hidden;
}
.tilt__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.tilt__color {
  position: absolute;
  left: 8px;
  bottom: 8px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .1em;
  color: #fff;
  background: #e85a92;
  padding: 3px 8px;
  border-radius: 999px;
}
.tilt__info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
}
.tilt__role {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  color: #e85a92;
}
.tilt__name {
  margin: 2px 0;
  font-size: 22px;
  font-weight: 800;
  color: #4a3340;
}
.tilt__msg {
  margin: 0;
  font-size: 12px;
  color: #9a7d8a;
}
.tilt__tags {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}
.tilt__tags span {
  font-size: 10px;
  color: #b56685;
  background: #ffeaf2;
  padding: 3px 9px;
  border-radius: 999px;
}

/* マウス位置に追従する光沢 */
.tilt__glare {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(
    circle at var(--mx) var(--my),
    rgba(255,255,255,.55) 0%, rgba(255,255,255,0) 45%);
  mix-blend-mode: screen;
}

.sk__hint {
  margin: 0;
  text-align: center;
  font-size: 11px;
  color: #c79bb0;
}

@media (prefers-reduced-motion: reduce) {
  .tilt__inner { transform: none !important; transition: none; }
  .tilt__glare { display: none; }
}
JavaScript
// Sakuraメンバーカード: ポインタ位置を回転角へ変換して立体的に傾ける
(() => {
  const card = document.querySelector('.tilt');
  if (!card) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return;

  const MAX = 12; // 最大傾き(deg)

  const update = (clientX, clientY) => {
    const r = card.getBoundingClientRect();
    const px = (clientX - r.left) / r.width;  // 0..1
    const py = (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`);
    // 光沢の中心も同期
    card.style.setProperty('--mx', `${px * 100}%`);
    card.style.setProperty('--my', `${py * 100}%`);
  };

  card.addEventListener('pointermove', (e) => update(e.clientX, e.clientY));

  // 離脱時は水平に戻す
  const reset = () => {
    card.style.setProperty('--rx', '0deg');
    card.style.setProperty('--ry', '0deg');
    card.style.setProperty('--mx', '50%');
    card.style.setProperty('--my', '50%');
  };
  card.addEventListener('pointerleave', reset);
  card.addEventListener('blur', reset);
})();

コード

HTML
<!-- 3Dティルトカード: マウス位置で立体的に傾く -->
<div class="stage">
  <article class="tilt" tabindex="0">
    <div class="tilt__shine" aria-hidden="true"></div>
    <div class="tilt__content">
      <span class="tilt__badge">PREMIUM</span>
      <h2 class="tilt__title">Aurora Pass</h2>
      <p class="tilt__desc">傾けると光沢が走る、奥行きのあるカード。</p>
      <div class="tilt__foot">
        <span class="tilt__num">**** 4271</span>
        <span class="tilt__chip" aria-hidden="true"></span>
      </div>
    </div>
  </article>
</div>
CSS
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(circle at 50% 30%, #1b2735 0%, #090a0f 70%);
}

/* 親に遠近感を与える */
.stage {
  perspective: 900px;
}

/* カード本体: CSS変数で回転角を受け取る */
.tilt {
  --rx: 0deg;
  --ry: 0deg;
  --mx: 50%;
  --my: 50%;
  position: relative;
  width: 300px;
  height: 188px;
  border-radius: 18px;
  padding: 22px;
  cursor: pointer;
  transform: rotateX(var(--rx)) rotateY(var(--ry));
  transform-style: preserve-3d;
  transition: transform .25s ease;
  background: linear-gradient(135deg, #5b2bff 0%, #b829e3 50%, #ff5e9c 100%);
  box-shadow: 0 30px 60px rgba(91, 43, 255, .35);
  color: #fff;
  outline: none;
  overflow: hidden;
}

.tilt:focus-visible {
  box-shadow: 0 0 0 3px #fff, 0 30px 60px rgba(91, 43, 255, .45);
}

/* マウス追従のハイライト */
.tilt__shine {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    circle at var(--mx) var(--my),
    rgba(255, 255, 255, .55) 0%,
    rgba(255, 255, 255, 0) 45%
  );
  mix-blend-mode: soft-light;
  pointer-events: none;
}

/* 中身を手前に浮かせる */
.tilt__content {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  transform: translateZ(40px);
}

.tilt__badge {
  align-self: flex-start;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .18em;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .22);
  backdrop-filter: blur(4px);
}

.tilt__title {
  margin: 14px 0 4px;
  font-size: 26px;
  letter-spacing: .01em;
}

.tilt__desc {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(255, 255, 255, .85);
}

.tilt__foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.tilt__num {
  font-family: ui-monospace, "Courier New", monospace;
  letter-spacing: .12em;
  font-size: 15px;
}

/* ICチップ風の装飾 */
.tilt__chip {
  width: 38px;
  height: 27px;
  border-radius: 6px;
  background: linear-gradient(135deg, #ffe7a0, #d9a441);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .2);
}

@media (prefers-reduced-motion: reduce) {
  .tilt { transition: none; transform: none !important; }
  .tilt__shine { display: none; }
}
JavaScript
// 3Dティルトカード: カード内のポインタ位置を -0.5..0.5 に正規化して回転へ変換
(() => {
  const card = document.querySelector('.tilt');
  if (!card) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return;

  const MAX = 14; // 最大傾き(deg)

  const update = (clientX, clientY) => {
    const r = card.getBoundingClientRect();
    const px = (clientX - r.left) / r.width;  // 0..1
    const py = (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`);
    // 光沢の中心も同期
    card.style.setProperty('--mx', `${px * 100}%`);
    card.style.setProperty('--my', `${py * 100}%`);
  };

  card.addEventListener('pointermove', (e) => update(e.clientX, e.clientY));

  // 離脱時は水平に戻す
  const reset = () => {
    card.style.setProperty('--rx', '0deg');
    card.style.setProperty('--ry', '0deg');
    card.style.setProperty('--mx', '50%');
    card.style.setProperty('--my', '50%');
  };
  card.addEventListener('pointerleave', reset);
  card.addEventListener('blur', reset);
})();

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

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

# 追加してほしい効果
3Dティルトカード(マイクロインタラクション)
マウス位置を回転角に変換し、CSSのperspectiveでカードが立体的に傾き光沢が走ります。商品カードやプロフィールカードの演出に最適です。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 3Dティルトカード: マウス位置で立体的に傾く -->
<div class="stage">
  <article class="tilt" tabindex="0">
    <div class="tilt__shine" aria-hidden="true"></div>
    <div class="tilt__content">
      <span class="tilt__badge">PREMIUM</span>
      <h2 class="tilt__title">Aurora Pass</h2>
      <p class="tilt__desc">傾けると光沢が走る、奥行きのあるカード。</p>
      <div class="tilt__foot">
        <span class="tilt__num">**** 4271</span>
        <span class="tilt__chip" aria-hidden="true"></span>
      </div>
    </div>
  </article>
</div>

【CSS】
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(circle at 50% 30%, #1b2735 0%, #090a0f 70%);
}

/* 親に遠近感を与える */
.stage {
  perspective: 900px;
}

/* カード本体: CSS変数で回転角を受け取る */
.tilt {
  --rx: 0deg;
  --ry: 0deg;
  --mx: 50%;
  --my: 50%;
  position: relative;
  width: 300px;
  height: 188px;
  border-radius: 18px;
  padding: 22px;
  cursor: pointer;
  transform: rotateX(var(--rx)) rotateY(var(--ry));
  transform-style: preserve-3d;
  transition: transform .25s ease;
  background: linear-gradient(135deg, #5b2bff 0%, #b829e3 50%, #ff5e9c 100%);
  box-shadow: 0 30px 60px rgba(91, 43, 255, .35);
  color: #fff;
  outline: none;
  overflow: hidden;
}

.tilt:focus-visible {
  box-shadow: 0 0 0 3px #fff, 0 30px 60px rgba(91, 43, 255, .45);
}

/* マウス追従のハイライト */
.tilt__shine {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    circle at var(--mx) var(--my),
    rgba(255, 255, 255, .55) 0%,
    rgba(255, 255, 255, 0) 45%
  );
  mix-blend-mode: soft-light;
  pointer-events: none;
}

/* 中身を手前に浮かせる */
.tilt__content {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  transform: translateZ(40px);
}

.tilt__badge {
  align-self: flex-start;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .18em;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .22);
  backdrop-filter: blur(4px);
}

.tilt__title {
  margin: 14px 0 4px;
  font-size: 26px;
  letter-spacing: .01em;
}

.tilt__desc {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(255, 255, 255, .85);
}

.tilt__foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.tilt__num {
  font-family: ui-monospace, "Courier New", monospace;
  letter-spacing: .12em;
  font-size: 15px;
}

/* ICチップ風の装飾 */
.tilt__chip {
  width: 38px;
  height: 27px;
  border-radius: 6px;
  background: linear-gradient(135deg, #ffe7a0, #d9a441);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .2);
}

@media (prefers-reduced-motion: reduce) {
  .tilt { transition: none; transform: none !important; }
  .tilt__shine { display: none; }
}

【JavaScript】
// 3Dティルトカード: カード内のポインタ位置を -0.5..0.5 に正規化して回転へ変換
(() => {
  const card = document.querySelector('.tilt');
  if (!card) return; // null安全

  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if (reduce) return;

  const MAX = 14; // 最大傾き(deg)

  const update = (clientX, clientY) => {
    const r = card.getBoundingClientRect();
    const px = (clientX - r.left) / r.width;  // 0..1
    const py = (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`);
    // 光沢の中心も同期
    card.style.setProperty('--mx', `${px * 100}%`);
    card.style.setProperty('--my', `${py * 100}%`);
  };

  card.addEventListener('pointermove', (e) => update(e.clientX, e.clientY));

  // 離脱時は水平に戻す
  const reset = () => {
    card.style.setProperty('--rx', '0deg');
    card.style.setProperty('--ry', '0deg');
    card.style.setProperty('--mx', '50%');
    card.style.setProperty('--my', '50%');
  };
  card.addEventListener('pointerleave', reset);
  card.addEventListener('blur', reset);
})();

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

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