3D回転キューブ

6面を3D空間に配置したキューブが自動回転し、ドラッグで手動回転もできます。ローディングやヒーロー演出のアクセントに最適です。

#css#javascript#3d#interaction

ライブデモ

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

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

HTML
<!-- Sakura:新譜MVを6面に貼った回転キューブのヒーロー演出 -->
<section class="sk-hero" aria-label="Sakura 新曲ヒーロー">
  <div class="sk-hero__copy">
    <span class="sk-hero__tag">NEW SINGLE</span>
    <h1 class="sk-hero__title">花びらラプソディ</h1>
    <p class="sk-hero__sub">Sakura 4th Single / 2026.06.07 配信開始</p>
    <span class="sk-hero__hint">キューブをドラッグして回せます</span>
  </div>

  <div class="sk-stage">
    <!-- 6面を3D空間に配置。自動回転+ドラッグ対応 -->
    <div class="sk-cube" id="skCube">
      <span class="sk-face sk-face--f">MV</span>
      <span class="sk-face sk-face--b">LIVE</span>
      <span class="sk-face sk-face--r">桜</span>
      <span class="sk-face sk-face--l">♪</span>
      <span class="sk-face sk-face--u">★</span>
      <span class="sk-face sk-face--d">2026</span>
    </div>
  </div>
</section>
CSS
/* Sakura:桜ピンクの回転キューブ・ヒーロー */
:root {
  --pink: #ffd1e0;
  --pink-deep: #ff8fb3;
  --gray: #eef0f3;
  --size: 130px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
  background:
    radial-gradient(circle at 80% 20%, #fff 0%, var(--pink) 55%, #ffc0d8 100%);
  color: #7a3553;
  overflow: hidden;
}

.sk-hero { display: contents; }

/* 左:コピー */
.sk-hero__copy { padding: 0 12px 0 30px; }
.sk-hero__tag {
  display: inline-block;
  font-size: 11px; letter-spacing: 0.24em; font-weight: 800;
  color: #fff; background: linear-gradient(135deg, #ff9cc0, var(--pink-deep));
  padding: 5px 12px; border-radius: 999px;
  box-shadow: 0 6px 14px rgba(255,122,168,.4);
}
.sk-hero__title {
  margin: 14px 0 6px; font-size: 30px; font-weight: 900;
  letter-spacing: 0.02em; color: #c94d7d;
}
.sk-hero__sub { margin: 0 0 14px; font-size: 12.5px; color: #9a6076; line-height: 1.7; }
.sk-hero__hint { font-size: 11px; color: #b07089; }

/* 右:3Dステージ。perspective が立体の肝 */
.sk-stage {
  height: 100%;
  display: grid;
  place-items: center;
  perspective: 800px;
}

.sk-cube {
  position: relative;
  width: var(--size);
  height: var(--size);
  transform-style: preserve-3d; /* 6面を3D空間に */
  transform: rotateX(-18deg) rotateY(0deg);
  cursor: grab;
}
.sk-cube:active { cursor: grabbing; }

.sk-face {
  position: absolute;
  width: var(--size);
  height: var(--size);
  display: grid;
  place-items: center;
  font-size: 28px;
  font-weight: 900;
  letter-spacing: 0.05em;
  color: #fff;
  border-radius: 14px;
  border: 2px solid rgba(255,255,255,.7);
  background: linear-gradient(150deg, rgba(255,156,192,.92), rgba(255,138,177,.92));
  box-shadow: inset 0 0 24px rgba(255,255,255,.35);
  backface-visibility: visible;
}
/* 面ごとに濃淡を変えて立体感 */
.sk-face--f { transform: translateZ(65px); }
.sk-face--b { transform: rotateY(180deg) translateZ(65px); font-size: 20px; }
.sk-face--r { transform: rotateY(90deg) translateZ(65px); background: linear-gradient(150deg, #ffb0cf, #ff96bb); }
.sk-face--l { transform: rotateY(-90deg) translateZ(65px); background: linear-gradient(150deg, #ffb0cf, #ff96bb); }
.sk-face--u { transform: rotateX(90deg) translateZ(65px); background: linear-gradient(150deg, #ffc4da, #ffa6c5); }
.sk-face--d { transform: rotateX(-90deg) translateZ(65px); font-size: 18px; background: linear-gradient(150deg, #ffc4da, #ffa6c5); }

@media (prefers-reduced-motion: reduce) {
  .sk-cube { animation: none; }
}
JavaScript
// Sakura 回転キューブ:自動回転+ドラッグで手動回転
(() => {
  const cube = document.getElementById("skCube");
  if (!cube) return; // null安全

  let rotX = -18, rotY = 0;          // 現在の回転角(度)
  let auto = 0.4;                    // 自動回転速度
  let dragging = false, lastX = 0, lastY = 0;
  const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  // 角度を反映
  const apply = () => {
    cube.style.transform = `rotateX(${rotX}deg) rotateY(${rotY}deg)`;
  };

  const onDown = (e) => {
    dragging = true; auto = 0;
    lastX = e.clientX; lastY = e.clientY;
    cube.setPointerCapture?.(e.pointerId);
  };
  const onMove = (e) => {
    if (!dragging) return;
    rotY += (e.clientX - lastX) * 0.5;
    rotX -= (e.clientY - lastY) * 0.5;
    lastX = e.clientX; lastY = e.clientY;
    apply();
  };
  const onUp = () => {
    if (!dragging) return;
    dragging = false;
    // 手を離したら少し待って自動回転を再開
    setTimeout(() => { if (!dragging && !reduce) auto = 0.4; }, 1000);
  };

  cube.addEventListener("pointerdown", onDown);
  window.addEventListener("pointermove", onMove);
  window.addEventListener("pointerup", onUp);

  // 自動回転ループ
  const tick = () => {
    if (auto) { rotY += auto; apply(); }
    requestAnimationFrame(tick);
  };
  apply();
  if (!reduce) tick();
})();

コード

HTML
<div class="cube-wrap" aria-label="3D回転キューブのデモ">
  <div class="stage">
    <!-- 6面を持つキューブ。自動回転+ドラッグで手動回転 -->
    <div class="cube" id="cube">
      <div class="cube__face cube__face--front">前</div>
      <div class="cube__face cube__face--back">後</div>
      <div class="cube__face cube__face--right">右</div>
      <div class="cube__face cube__face--left">左</div>
      <div class="cube__face cube__face--top">上</div>
      <div class="cube__face cube__face--bottom">下</div>
    </div>
  </div>
  <p class="cube-hint">ドラッグで回転</p>
</div>
CSS
/* ===== 3D回転キューブ ===== */
:root {
  --size: 130px;            /* キューブの辺の長さ */
  --half: calc(var(--size) / 2);
  --hue: 265;
}

* { 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 50% 120%, hsl(var(--hue) 60% 22%), transparent 60%),
    linear-gradient(160deg, #0e0b1e 0%, #05040c 100%);
  overflow: hidden;
  user-select: none;
}

.cube-wrap { display: grid; place-items: center; gap: 18px; }

.stage {
  width: var(--size);
  height: var(--size);
  perspective: 720px; /* 視点までの距離 */
}

.cube {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  /* JSがCSS変数 --rx / --ry を更新して回転 */
  transform: rotateX(var(--rx, -22deg)) rotateY(var(--ry, 32deg));
  transition: transform .08s linear;
}
.cube.is-dragging { transition: none; cursor: grabbing; }

.cube__face {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 34px;
  font-weight: 800;
  color: #fff;
  border: 1px solid hsl(var(--hue) 90% 75% / .55);
  background: hsl(var(--hue) 70% 55% / .18);
  backdrop-filter: blur(2px);
  box-shadow: inset 0 0 30px hsl(var(--hue) 90% 70% / .25);
}
.cube__face::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(135deg, hsl(var(--hue) 90% 80% / .25), transparent 60%);
}

/* 各面を3D空間に配置 */
.cube__face--front  { transform: translateZ(var(--half)); }
.cube__face--back   { transform: rotateY(180deg) translateZ(var(--half)); }
.cube__face--right  { transform: rotateY(90deg)  translateZ(var(--half)); }
.cube__face--left   { transform: rotateY(-90deg) translateZ(var(--half)); }
.cube__face--top    { transform: rotateX(90deg)  translateZ(var(--half)); }
.cube__face--bottom { transform: rotateX(-90deg) translateZ(var(--half)); }

.cube-hint {
  margin: 0;
  font-size: 12px;
  letter-spacing: .2em;
  color: hsl(var(--hue) 50% 80% / .7);
}

@media (prefers-reduced-motion: reduce) {
  .cube { transition: none; }
}
JavaScript
// 3D回転キューブ: 自動回転 + ポインタードラッグで手動操作
(() => {
  const cube = document.getElementById('cube');
  if (!cube) return; // null安全

  let rx = -22;          // X軸回転(度)
  let ry = 32;           // Y軸回転(度)
  let autoSpin = true;   // 自動回転フラグ
  let dragging = false;
  let lastX = 0, lastY = 0;

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

  const apply = () => {
    cube.style.setProperty('--rx', rx.toFixed(2) + 'deg');
    cube.style.setProperty('--ry', ry.toFixed(2) + 'deg');
  };

  // 自動回転ループ(reduced-motion時は止める)
  const tick = () => {
    if (autoSpin && !reduce) {
      ry += 0.45;
      apply();
    }
    requestAnimationFrame(tick);
  };

  const onDown = (e) => {
    dragging = true;
    autoSpin = false;
    cube.classList.add('is-dragging');
    lastX = e.clientX;
    lastY = e.clientY;
    cube.setPointerCapture?.(e.pointerId);
  };
  const onMove = (e) => {
    if (!dragging) return;
    ry += (e.clientX - lastX) * 0.5;
    rx -= (e.clientY - lastY) * 0.5;
    rx = Math.max(-90, Math.min(90, rx)); // 上下の回転を制限
    lastX = e.clientX;
    lastY = e.clientY;
    apply();
  };
  const onUp = () => {
    if (!dragging) return;
    dragging = false;
    cube.classList.remove('is-dragging');
    // 少し待ってから自動回転を再開
    setTimeout(() => { if (!dragging) autoSpin = true; }, 1400);
  };

  cube.addEventListener('pointerdown', onDown);
  window.addEventListener('pointermove', onMove);
  window.addEventListener('pointerup', onUp);

  apply();
  requestAnimationFrame(tick);
})();

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

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

# 追加してほしい効果
3D回転キューブ(3D & パースペクティブ)
6面を3D空間に配置したキューブが自動回転し、ドラッグで手動回転もできます。ローディングやヒーロー演出のアクセントに最適です。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<div class="cube-wrap" aria-label="3D回転キューブのデモ">
  <div class="stage">
    <!-- 6面を持つキューブ。自動回転+ドラッグで手動回転 -->
    <div class="cube" id="cube">
      <div class="cube__face cube__face--front">前</div>
      <div class="cube__face cube__face--back">後</div>
      <div class="cube__face cube__face--right">右</div>
      <div class="cube__face cube__face--left">左</div>
      <div class="cube__face cube__face--top">上</div>
      <div class="cube__face cube__face--bottom">下</div>
    </div>
  </div>
  <p class="cube-hint">ドラッグで回転</p>
</div>

【CSS】
/* ===== 3D回転キューブ ===== */
:root {
  --size: 130px;            /* キューブの辺の長さ */
  --half: calc(var(--size) / 2);
  --hue: 265;
}

* { 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 50% 120%, hsl(var(--hue) 60% 22%), transparent 60%),
    linear-gradient(160deg, #0e0b1e 0%, #05040c 100%);
  overflow: hidden;
  user-select: none;
}

.cube-wrap { display: grid; place-items: center; gap: 18px; }

.stage {
  width: var(--size);
  height: var(--size);
  perspective: 720px; /* 視点までの距離 */
}

.cube {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  /* JSがCSS変数 --rx / --ry を更新して回転 */
  transform: rotateX(var(--rx, -22deg)) rotateY(var(--ry, 32deg));
  transition: transform .08s linear;
}
.cube.is-dragging { transition: none; cursor: grabbing; }

.cube__face {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 34px;
  font-weight: 800;
  color: #fff;
  border: 1px solid hsl(var(--hue) 90% 75% / .55);
  background: hsl(var(--hue) 70% 55% / .18);
  backdrop-filter: blur(2px);
  box-shadow: inset 0 0 30px hsl(var(--hue) 90% 70% / .25);
}
.cube__face::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(135deg, hsl(var(--hue) 90% 80% / .25), transparent 60%);
}

/* 各面を3D空間に配置 */
.cube__face--front  { transform: translateZ(var(--half)); }
.cube__face--back   { transform: rotateY(180deg) translateZ(var(--half)); }
.cube__face--right  { transform: rotateY(90deg)  translateZ(var(--half)); }
.cube__face--left   { transform: rotateY(-90deg) translateZ(var(--half)); }
.cube__face--top    { transform: rotateX(90deg)  translateZ(var(--half)); }
.cube__face--bottom { transform: rotateX(-90deg) translateZ(var(--half)); }

.cube-hint {
  margin: 0;
  font-size: 12px;
  letter-spacing: .2em;
  color: hsl(var(--hue) 50% 80% / .7);
}

@media (prefers-reduced-motion: reduce) {
  .cube { transition: none; }
}

【JavaScript】
// 3D回転キューブ: 自動回転 + ポインタードラッグで手動操作
(() => {
  const cube = document.getElementById('cube');
  if (!cube) return; // null安全

  let rx = -22;          // X軸回転(度)
  let ry = 32;           // Y軸回転(度)
  let autoSpin = true;   // 自動回転フラグ
  let dragging = false;
  let lastX = 0, lastY = 0;

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

  const apply = () => {
    cube.style.setProperty('--rx', rx.toFixed(2) + 'deg');
    cube.style.setProperty('--ry', ry.toFixed(2) + 'deg');
  };

  // 自動回転ループ(reduced-motion時は止める)
  const tick = () => {
    if (autoSpin && !reduce) {
      ry += 0.45;
      apply();
    }
    requestAnimationFrame(tick);
  };

  const onDown = (e) => {
    dragging = true;
    autoSpin = false;
    cube.classList.add('is-dragging');
    lastX = e.clientX;
    lastY = e.clientY;
    cube.setPointerCapture?.(e.pointerId);
  };
  const onMove = (e) => {
    if (!dragging) return;
    ry += (e.clientX - lastX) * 0.5;
    rx -= (e.clientY - lastY) * 0.5;
    rx = Math.max(-90, Math.min(90, rx)); // 上下の回転を制限
    lastX = e.clientX;
    lastY = e.clientY;
    apply();
  };
  const onUp = () => {
    if (!dragging) return;
    dragging = false;
    cube.classList.remove('is-dragging');
    // 少し待ってから自動回転を再開
    setTimeout(() => { if (!dragging) autoSpin = true; }, 1400);
  };

  cube.addEventListener('pointerdown', onDown);
  window.addEventListener('pointermove', onMove);
  window.addEventListener('pointerup', onUp);

  apply();
  requestAnimationFrame(tick);
})();

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

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