グラデーション球

頂点カラーで上下2色を補間し、継ぎ目のないグラデーションをまとった球体。ブランドカラーの象徴やヒーローのアクセントに使えます。

外部ライブラリ: https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js

#webgl#threejs#gradient#3d

ライブデモ

使用例(お題: カフェ MOON BREW)

この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- MOON BREW:グラデーション球をラテのシンボルにした新作告知ヒーロー -->
<section class="mb-latte" aria-label="MOON BREW 新作ラテ">
  <!-- 背景の3Dグラデ球(クリーム→琥珀のラテ表現) -->
  <canvas id="scene" class="mb-latte__canvas" aria-hidden="true"></canvas>
  <div class="mb-latte__fallback" id="mb-fallback" hidden></div>

  <header class="mb-bar">
    <span class="mb-logo">◐ MOON BREW</span>
    <span class="mb-bar__season">2026 SPRING</span>
  </header>

  <div class="mb-latte__body">
    <span class="mb-kicker">NEW SEASONAL LATTE</span>
    <h1 class="mb-title">ハニー<br>キャラメル<br>ラテ</h1>
    <p class="mb-lead">なめらかなスチームミルクに、<br>琥珀色のキャラメルをひとさじ。</p>
    <div class="mb-meta">
      <span class="mb-meta__item">HOT / ICE</span>
      <span class="mb-meta__dot">·</span>
      <span class="mb-meta__item">Tall ¥620</span>
    </div>
    <button class="mb-btn" type="button">注文する</button>
  </div>
</section>
CSS
/* MOON BREW:明るいクリーム地のラテ告知 */
:root {
  --cream: #f5ede1;
  --brown: #2b1d12;
  --amber: #c98a3b;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  font-family: "Hiragino Mincho ProN", "Yu Mincho", "Segoe UI", serif;
  background: var(--cream);
}

.mb-latte {
  position: relative;
  width: 100%;
  height: 400px;
  overflow: hidden;
  background:
    radial-gradient(110% 80% at 75% 45%, #fbf4e8 0%, #f5ede1 55%, #ecdfc9 100%);
  color: var(--brown);
}

/* 3D球は右側で大きく見せる */
.mb-latte__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.mb-latte__fallback {
  position: absolute;
  right: 8%;
  top: 50%;
  width: 240px;
  height: 240px;
  transform: translateY(-50%);
  border-radius: 50%;
  background: linear-gradient(160deg, #fbf4e8 0%, var(--amber) 100%);
  box-shadow: 0 24px 60px rgba(201, 138, 59, 0.4);
}

.mb-bar {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 26px;
}
.mb-logo { font-size: 17px; letter-spacing: 0.14em; font-weight: 700; }
.mb-bar__season {
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.24em;
  color: var(--amber);
  font-weight: 700;
}

.mb-latte__body {
  position: relative;
  z-index: 2;
  max-width: 360px;
  padding: 20px 26px;
}
.mb-kicker {
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.26em;
  color: var(--amber);
  font-weight: 700;
}
.mb-title {
  margin: 10px 0 14px;
  font-size: 38px;
  line-height: 1.18;
  font-weight: 700;
}
.mb-lead {
  margin: 0 0 18px;
  font-size: 13.5px;
  line-height: 1.85;
  color: #5a4636;
}
.mb-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 22px;
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.06em;
  color: #7a614a;
}
.mb-meta__dot { color: var(--amber); }

.mb-btn {
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: 14px;
  font-weight: 700;
  color: var(--cream);
  background: linear-gradient(135deg, #6b4a2c, var(--brown));
  border: none;
  padding: 12px 30px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 8px 20px rgba(43, 29, 18, 0.3);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.mb-btn:hover { transform: translateY(-2px); box-shadow: 0 12px 26px rgba(43, 29, 18, 0.4); }
.mb-btn:active { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .mb-btn { transition: none; }
}
JavaScript
// MOON BREW ヒーロー:頂点カラーで上下2色を補間したグラデ球(クリーム→琥珀のラテ表現)
(function () {
  "use strict";
  const canvas = document.getElementById("scene");
  const fallback = document.getElementById("mb-fallback");
  // Three.js未読込やcanvas不在なら安全にフォールバック表示
  if (!canvas || typeof THREE === "undefined") {
    if (fallback) fallback.hidden = false;
    return;
  }

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

  let renderer;
  try {
    renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
  } catch (e) {
    if (fallback) fallback.hidden = false;
    return;
  }
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
  camera.position.set(0, 0, 4.4);

  // 球の各頂点に上下2色を補間したカラーを焼き込む
  const geometry = new THREE.SphereGeometry(1.25, 64, 64);
  const top = new THREE.Color(0xfbf4e8);    // クリーム
  const bottom = new THREE.Color(0xc98a3b); // 琥珀
  const pos = geometry.attributes.position;
  const colors = [];
  for (let i = 0; i < pos.count; i++) {
    // y座標(-1..1)を0..1へ正規化して縦グラデにする
    const t = (pos.getY(i) / 1.25 + 1) * 0.5;
    const c = bottom.clone().lerp(top, t);
    colors.push(c.r, c.g, c.b);
  }
  geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));

  const material = new THREE.MeshBasicMaterial({ vertexColors: true });
  const sphere = new THREE.Mesh(geometry, material);
  sphere.position.x = 1.5; // 前景テキストを避けて右へ
  scene.add(sphere);

  function resize() {
    const w = canvas.clientWidth || 1;
    const h = canvas.clientHeight || 1;
    renderer.setSize(w, h, false);
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
  }
  resize();
  window.addEventListener("resize", resize);

  let raf = 0;
  let running = true;
  function animate() {
    if (!reduceMotion) sphere.rotation.y += 0.006;
    renderer.render(scene, camera);
    raf = requestAnimationFrame(animate);
  }
  animate();

  document.addEventListener("visibilitychange", () => {
    if (document.hidden) {
      if (running) { cancelAnimationFrame(raf); running = false; }
    } else if (!running) {
      running = true;
      raf = requestAnimationFrame(animate);
    }
  });

  window.addEventListener("beforeunload", () => cancelAnimationFrame(raf));
})();

コード

HTML
<!-- 頂点カラーで滑らかなグラデーションをまとった球 -->
<div class="stage">
  <canvas id="sphere" aria-label="グラデーションに包まれた球"></canvas>
  <div class="caption">
    <span class="badge">Vertex Color</span>
    <h2>Gradient Sphere</h2>
    <p>頂点カラーで作る継ぎ目のないグラデーション球</p>
  </div>
</div>
CSS
/* 配色変数 */
:root {
  --ink: #fff5f8;
  --accent: #ffafcc;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: "Segoe UI", system-ui, sans-serif;
  overflow: hidden;
}

.stage {
  position: relative;
  width: 100%;
  height: 360px;
  /* やわらかいパステルの背景 */
  background: radial-gradient(circle at 50% 35%, #2a1a3e 0%, #160d24 65%, #0b0715 100%);
}

#sphere {
  display: block;
  width: 100%;
  height: 100%;
}

.caption {
  position: absolute;
  left: 28px;
  bottom: 24px;
  color: var(--ink);
  text-shadow: 0 2px 14px rgba(0, 0, 0, .7);
  pointer-events: none;
}

.badge {
  display: inline-block;
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 175, 204, .18);
  border: 1px solid rgba(255, 175, 204, .45);
  color: var(--accent);
  margin-bottom: 10px;
}

.caption h2 {
  font-size: 22px;
  font-weight: 700;
}

.caption p {
  margin-top: 4px;
  font-size: 13px;
  opacity: .72;
}
JavaScript
// グラデーション球:頂点のY座標で2色を補間して着色
(function () {
  "use strict";
  const canvas = document.getElementById("sphere");
  if (!canvas || typeof THREE === "undefined") return;

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

  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
  camera.position.z = 4.4;

  // 高解像度の球を用意
  const geometry = new THREE.SphereGeometry(1.4, 96, 96);

  // 上下2色のグラデーションを頂点カラーとして焼き込む
  const top = new THREE.Color(0xff7eb3);
  const bottom = new THREE.Color(0x6a8dff);
  const pos = geometry.attributes.position;
  const colors = new Float32Array(pos.count * 3);
  const tmp = new THREE.Color();
  for (let i = 0; i < pos.count; i++) {
    // Y座標を0..1へ正規化し色を線形補間
    const t = (pos.getY(i) / 1.4) * 0.5 + 0.5;
    tmp.copy(bottom).lerp(top, t);
    colors[i * 3] = tmp.r;
    colors[i * 3 + 1] = tmp.g;
    colors[i * 3 + 2] = tmp.b;
  }
  geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));

  const material = new THREE.MeshStandardMaterial({
    vertexColors: true,
    metalness: 0.15,
    roughness: 0.35,
  });
  const sphere = new THREE.Mesh(geometry, material);
  scene.add(sphere);

  // 柔らかな環境光+ハイライト
  scene.add(new THREE.AmbientLight(0xffffff, 0.65));
  const key = new THREE.DirectionalLight(0xffffff, 0.9);
  key.position.set(2, 3, 4);
  scene.add(key);
  const fill = new THREE.PointLight(0x7ad4ff, 1.0, 20);
  fill.position.set(-3, -1, 2);
  scene.add(fill);

  function resize() {
    const w = canvas.clientWidth || 1;
    const h = canvas.clientHeight || 1;
    renderer.setSize(w, h, false);
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
  }
  resize();
  window.addEventListener("resize", resize);

  let raf = 0;
  function animate() {
    if (!reduceMotion) {
      sphere.rotation.y += 0.005;
      sphere.rotation.x = Math.sin(performance.now() * 0.0004) * 0.18;
    }
    renderer.render(scene, camera);
    raf = requestAnimationFrame(animate);
  }
  animate();

  window.addEventListener("beforeunload", () => cancelAnimationFrame(raf));
})();

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

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

# 追加してほしい効果
グラデーション球(WebGL / Three.js)
頂点カラーで上下2色を補間し、継ぎ目のないグラデーションをまとった球体。ブランドカラーの象徴やヒーローのアクセントに使えます。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 頂点カラーで滑らかなグラデーションをまとった球 -->
<div class="stage">
  <canvas id="sphere" aria-label="グラデーションに包まれた球"></canvas>
  <div class="caption">
    <span class="badge">Vertex Color</span>
    <h2>Gradient Sphere</h2>
    <p>頂点カラーで作る継ぎ目のないグラデーション球</p>
  </div>
</div>

【CSS】
/* 配色変数 */
:root {
  --ink: #fff5f8;
  --accent: #ffafcc;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: "Segoe UI", system-ui, sans-serif;
  overflow: hidden;
}

.stage {
  position: relative;
  width: 100%;
  height: 360px;
  /* やわらかいパステルの背景 */
  background: radial-gradient(circle at 50% 35%, #2a1a3e 0%, #160d24 65%, #0b0715 100%);
}

#sphere {
  display: block;
  width: 100%;
  height: 100%;
}

.caption {
  position: absolute;
  left: 28px;
  bottom: 24px;
  color: var(--ink);
  text-shadow: 0 2px 14px rgba(0, 0, 0, .7);
  pointer-events: none;
}

.badge {
  display: inline-block;
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 175, 204, .18);
  border: 1px solid rgba(255, 175, 204, .45);
  color: var(--accent);
  margin-bottom: 10px;
}

.caption h2 {
  font-size: 22px;
  font-weight: 700;
}

.caption p {
  margin-top: 4px;
  font-size: 13px;
  opacity: .72;
}

【JavaScript】
// グラデーション球:頂点のY座標で2色を補間して着色
(function () {
  "use strict";
  const canvas = document.getElementById("sphere");
  if (!canvas || typeof THREE === "undefined") return;

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

  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
  camera.position.z = 4.4;

  // 高解像度の球を用意
  const geometry = new THREE.SphereGeometry(1.4, 96, 96);

  // 上下2色のグラデーションを頂点カラーとして焼き込む
  const top = new THREE.Color(0xff7eb3);
  const bottom = new THREE.Color(0x6a8dff);
  const pos = geometry.attributes.position;
  const colors = new Float32Array(pos.count * 3);
  const tmp = new THREE.Color();
  for (let i = 0; i < pos.count; i++) {
    // Y座標を0..1へ正規化し色を線形補間
    const t = (pos.getY(i) / 1.4) * 0.5 + 0.5;
    tmp.copy(bottom).lerp(top, t);
    colors[i * 3] = tmp.r;
    colors[i * 3 + 1] = tmp.g;
    colors[i * 3 + 2] = tmp.b;
  }
  geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));

  const material = new THREE.MeshStandardMaterial({
    vertexColors: true,
    metalness: 0.15,
    roughness: 0.35,
  });
  const sphere = new THREE.Mesh(geometry, material);
  scene.add(sphere);

  // 柔らかな環境光+ハイライト
  scene.add(new THREE.AmbientLight(0xffffff, 0.65));
  const key = new THREE.DirectionalLight(0xffffff, 0.9);
  key.position.set(2, 3, 4);
  scene.add(key);
  const fill = new THREE.PointLight(0x7ad4ff, 1.0, 20);
  fill.position.set(-3, -1, 2);
  scene.add(fill);

  function resize() {
    const w = canvas.clientWidth || 1;
    const h = canvas.clientHeight || 1;
    renderer.setSize(w, h, false);
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
  }
  resize();
  window.addEventListener("resize", resize);

  let raf = 0;
  function animate() {
    if (!reduceMotion) {
      sphere.rotation.y += 0.005;
      sphere.rotation.x = Math.sin(performance.now() * 0.0004) * 0.18;
    }
    renderer.render(scene, camera);
    raf = requestAnimationFrame(animate);
  }
  animate();

  window.addEventListener("beforeunload", () => cancelAnimationFrame(raf));
})();

# 外部ライブラリ
https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js

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