メッシュグラデーション

複数の radial-gradient を重ねて有機的な色面を作り、JSで各色溜まりを漂わせるメッシュ背景。SaaSやポートフォリオのヒーローに最適です。

#css#javascript#gradient

ライブデモ

使用例(お題: SaaS FlowDesk)

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

HTML
<!-- FlowDesk:メッシュグラデを背に、機能訴求+ダッシュボードのチラ見せ -->
<section class="fm-stage">
  <!-- ★主役:4つの色溜まりを漂わせるメッシュグラデーション -->
  <div class="fm-mesh" id="fmMesh" aria-hidden="true"></div>

  <div class="fm-grid">
    <div class="fm-copy">
      <span class="fm-tag">FEATURES</span>
      <h1 class="fm-title">ダッシュボードで<br>すべてが見渡せる</h1>
      <p class="fm-sub">プロジェクトの進捗、メンバーの稼働、今日のタスク。散らばった情報をひとつの画面に集約します。</p>
      <ul class="fm-list">
        <li>リアルタイム同期</li>
        <li>権限管理&監査ログ</li>
        <li>API/Webhook連携</li>
      </ul>
    </div>

    <!-- 製品の世界観:浮かぶダッシュボードカード -->
    <div class="fm-panel" aria-hidden="true">
      <div class="fm-panel__bar">
        <span class="fm-dot"></span><span class="fm-dot"></span><span class="fm-dot"></span>
        <small>FlowDesk ・ 概要</small>
      </div>
      <div class="fm-stat">
        <div><b>98.4%</b><span>稼働率</span></div>
        <div><b>+12</b><span>今週の完了</span></div>
      </div>
      <div class="fm-chart">
        <i style="--h:40%"></i><i style="--h:62%"></i><i style="--h:48%"></i>
        <i style="--h:78%"></i><i style="--h:66%"></i><i style="--h:90%"></i>
      </div>
    </div>
  </div>
</section>
CSS
/* FlowDesk:紺地のメッシュグラデを背景に、機能訴求とダッシュボードカード */
* { box-sizing: border-box; margin: 0; padding: 0; }

.fm-stage {
  position: relative;
  min-height: 400px;
  height: 400px;
  overflow: hidden;
  background: #0f1b34;
  font-family: "Segoe UI", "Hiragino Sans", system-ui, sans-serif;
  color: #fff;
}

/* ★主役:複数の radial-gradient を重ねたメッシュ(位置はJSで揺らす) */
.fm-mesh {
  position: absolute;
  inset: -20%;
  z-index: 0;
  --x1: 18%; --y1: 22%;
  --x2: 82%; --y2: 18%;
  --x3: 24%; --y3: 82%;
  --x4: 80%; --y4: 80%;
  background:
    radial-gradient(38% 38% at var(--x1) var(--y1), #4f7cff 0%, transparent 70%),
    radial-gradient(40% 40% at var(--x2) var(--y2), #7b5cff 0%, transparent 72%),
    radial-gradient(42% 42% at var(--x3) var(--y3), #21a9fd 0%, transparent 70%),
    radial-gradient(40% 40% at var(--x4) var(--y4), #2b3f7a 0%, transparent 72%);
  filter: blur(44px) saturate(130%);
}

.fm-grid {
  position: relative;
  z-index: 1;
  height: 100%;
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  align-items: center;
  gap: 22px;
  padding: 0 30px;
}

.fm-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.22em;
  color: #9bc0ff;
}
.fm-title {
  margin-top: 10px;
  font-size: 30px;
  font-weight: 800;
  line-height: 1.22;
  text-shadow: 0 4px 22px rgba(0,0,0,0.4);
}
.fm-sub {
  margin-top: 12px;
  font-size: 13px;
  line-height: 1.8;
  color: rgba(255,255,255,0.88);
  max-width: 340px;
}
.fm-list {
  margin-top: 14px;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.fm-list li {
  font-size: 12.5px;
  color: rgba(255,255,255,0.92);
  padding-left: 22px;
  position: relative;
}
.fm-list li::before {
  content: "✓";
  position: absolute;
  left: 0;
  color: #6ff0c8;
  font-weight: 800;
}

/* ダッシュボードカード(ガラス風) */
.fm-panel {
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.22);
  border-radius: 16px;
  padding: 14px;
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: 0 18px 50px rgba(0,0,0,0.4);
}
.fm-panel__bar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 12px;
}
.fm-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,0.5); }
.fm-panel__bar small { margin-left: 6px; font-size: 10px; color: rgba(255,255,255,0.7); }

.fm-stat { display: flex; gap: 10px; margin-bottom: 14px; }
.fm-stat > div {
  flex: 1;
  background: rgba(255,255,255,0.08);
  border-radius: 10px;
  padding: 10px;
}
.fm-stat b { display: block; font-size: 20px; font-weight: 800; color: #fff; }
.fm-stat span { font-size: 10px; color: rgba(255,255,255,0.7); }

/* ミニ棒グラフ */
.fm-chart {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  height: 70px;
  padding: 0 2px;
}
.fm-chart i {
  flex: 1;
  height: var(--h);
  border-radius: 5px 5px 2px 2px;
  background: linear-gradient(to top, #4f7cff, #7fbfff);
}

@media (prefers-reduced-motion: reduce) {
  .fm-mesh { animation: none; }
}
JavaScript
// メッシュの各色溜まり(CSS変数)を時間で揺らし、有機的に漂わせる
(() => {
  const mesh = document.getElementById("fmMesh");
  if (!mesh) return; // null安全

  // モーション低減なら静止
  const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (reduce) return;

  // 4点の基準位置と位相
  const pts = [
    { kx: "--x1", ky: "--y1", bx: 18, by: 22, px: 0.0, py: 1.2 },
    { kx: "--x2", ky: "--y2", bx: 82, by: 18, px: 2.1, py: 3.3 },
    { kx: "--x3", ky: "--y3", bx: 24, by: 82, px: 4.0, py: 1.6 },
    { kx: "--x4", ky: "--y4", bx: 80, by: 80, px: 1.0, py: 5.0 },
  ];
  const amp = 8; // 揺れ幅(%)
  let raf = 0;

  const tick = (t) => {
    const s = t / 1000;
    for (const p of pts) {
      const x = p.bx + Math.sin(s * 0.32 + p.px) * amp;
      const y = p.by + Math.cos(s * 0.27 + p.py) * amp;
      mesh.style.setProperty(p.kx, x.toFixed(2) + "%");
      mesh.style.setProperty(p.ky, y.toFixed(2) + "%");
    }
    raf = requestAnimationFrame(tick);
  };
  raf = requestAnimationFrame(tick);

  // タブ非表示でループ停止
  document.addEventListener("visibilitychange", () => {
    cancelAnimationFrame(raf);
    if (!document.hidden) raf = requestAnimationFrame(tick);
  });
})();

コード

HTML
<!-- メッシュグラデーション: 複数の放射状グラデを重ねて有機的な色面を作る -->
<div class="mesh-stage">
  <div class="mesh-blobs" id="meshBlobs"></div>
  <div class="mesh-content">
    <h1 class="mesh-title">Mesh Gradient</h1>
    <p class="mesh-sub">複数の radial-gradient を重ね、柔らかな色の溜まりを表現。ヒーロー背景に最適です。</p>
  </div>
</div>
CSS
/* 複数の放射グラデを background に重ね、各点をゆっくり漂わせる */
* { box-sizing: border-box; margin: 0; padding: 0; }

.mesh-stage {
  position: relative;
  min-height: 360px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: #0e1020;
  font-family: "Segoe UI", "Hiragino Sans", system-ui, sans-serif;
}

/* メッシュ本体: 4つの色溜まりをカスタムプロパティで位置制御 */
.mesh-blobs {
  position: absolute;
  inset: -20%;
  --x1: 20%; --y1: 25%;
  --x2: 80%; --y2: 20%;
  --x3: 25%; --y3: 80%;
  --x4: 78%; --y4: 78%;
  background:
    radial-gradient(38% 38% at var(--x1) var(--y1), #ff6ec4 0%, transparent 70%),
    radial-gradient(40% 40% at var(--x2) var(--y2), #7873f5 0%, transparent 72%),
    radial-gradient(42% 42% at var(--x3) var(--y3), #21d4fd 0%, transparent 70%),
    radial-gradient(40% 40% at var(--x4) var(--y4), #f9d423 0%, transparent 72%);
  filter: blur(40px) saturate(135%);
  transition: --x1 0.6s linear; /* JS未対応ブラウザ向けの無害な保険 */
}

.mesh-content {
  position: relative;
  z-index: 2;
  text-align: center;
  color: #fff;
  padding: 0 24px;
  max-width: 520px;
}

.mesh-title {
  font-size: 44px;
  font-weight: 800;
  letter-spacing: 0.01em;
  text-shadow: 0 6px 30px rgba(0, 0, 0, 0.45);
}

.mesh-sub {
  margin-top: 14px;
  font-size: 14px;
  line-height: 1.85;
  color: rgba(255, 255, 255, 0.88);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
}

@media (prefers-reduced-motion: reduce) {
  .mesh-blobs { transition: none; }
}
JavaScript
// メッシュの各色溜まりの位置(CSS変数)を時間で揺らし、有機的な動きを作る
(() => {
  const blobs = document.getElementById("meshBlobs");
  if (!blobs) return; // null安全

  // モーション低減なら静止
  const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (reduce) return;

  // 4点それぞれの基準位置と揺れの位相を定義
  const pts = [
    { kx: "--x1", ky: "--y1", bx: 20, by: 25, px: 0.0, py: 1.1 },
    { kx: "--x2", ky: "--y2", bx: 80, by: 20, px: 2.0, py: 3.4 },
    { kx: "--x3", ky: "--y3", bx: 25, by: 80, px: 4.0, py: 1.7 },
    { kx: "--x4", ky: "--y4", bx: 78, by: 78, px: 1.0, py: 5.2 },
  ];

  const amp = 9; // 揺れ幅(%)
  let raf = 0;

  const tick = (t) => {
    const s = t / 1000;
    for (const p of pts) {
      const x = p.bx + Math.sin(s * 0.35 + p.px) * amp;
      const y = p.by + Math.cos(s * 0.28 + p.py) * amp;
      blobs.style.setProperty(p.kx, x.toFixed(2) + "%");
      blobs.style.setProperty(p.ky, y.toFixed(2) + "%");
    }
    raf = requestAnimationFrame(tick);
  };
  raf = requestAnimationFrame(tick);

  // タブ非表示時はループを止める
  document.addEventListener("visibilitychange", () => {
    cancelAnimationFrame(raf); // 二重ループ防止
    if (!document.hidden) raf = requestAnimationFrame(tick);
  });
})();

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

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

# 追加してほしい効果
メッシュグラデーション(背景 & グラデーション)
複数の radial-gradient を重ねて有機的な色面を作り、JSで各色溜まりを漂わせるメッシュ背景。SaaSやポートフォリオのヒーローに最適です。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- メッシュグラデーション: 複数の放射状グラデを重ねて有機的な色面を作る -->
<div class="mesh-stage">
  <div class="mesh-blobs" id="meshBlobs"></div>
  <div class="mesh-content">
    <h1 class="mesh-title">Mesh Gradient</h1>
    <p class="mesh-sub">複数の radial-gradient を重ね、柔らかな色の溜まりを表現。ヒーロー背景に最適です。</p>
  </div>
</div>

【CSS】
/* 複数の放射グラデを background に重ね、各点をゆっくり漂わせる */
* { box-sizing: border-box; margin: 0; padding: 0; }

.mesh-stage {
  position: relative;
  min-height: 360px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: #0e1020;
  font-family: "Segoe UI", "Hiragino Sans", system-ui, sans-serif;
}

/* メッシュ本体: 4つの色溜まりをカスタムプロパティで位置制御 */
.mesh-blobs {
  position: absolute;
  inset: -20%;
  --x1: 20%; --y1: 25%;
  --x2: 80%; --y2: 20%;
  --x3: 25%; --y3: 80%;
  --x4: 78%; --y4: 78%;
  background:
    radial-gradient(38% 38% at var(--x1) var(--y1), #ff6ec4 0%, transparent 70%),
    radial-gradient(40% 40% at var(--x2) var(--y2), #7873f5 0%, transparent 72%),
    radial-gradient(42% 42% at var(--x3) var(--y3), #21d4fd 0%, transparent 70%),
    radial-gradient(40% 40% at var(--x4) var(--y4), #f9d423 0%, transparent 72%);
  filter: blur(40px) saturate(135%);
  transition: --x1 0.6s linear; /* JS未対応ブラウザ向けの無害な保険 */
}

.mesh-content {
  position: relative;
  z-index: 2;
  text-align: center;
  color: #fff;
  padding: 0 24px;
  max-width: 520px;
}

.mesh-title {
  font-size: 44px;
  font-weight: 800;
  letter-spacing: 0.01em;
  text-shadow: 0 6px 30px rgba(0, 0, 0, 0.45);
}

.mesh-sub {
  margin-top: 14px;
  font-size: 14px;
  line-height: 1.85;
  color: rgba(255, 255, 255, 0.88);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
}

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

【JavaScript】
// メッシュの各色溜まりの位置(CSS変数)を時間で揺らし、有機的な動きを作る
(() => {
  const blobs = document.getElementById("meshBlobs");
  if (!blobs) return; // null安全

  // モーション低減なら静止
  const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  if (reduce) return;

  // 4点それぞれの基準位置と揺れの位相を定義
  const pts = [
    { kx: "--x1", ky: "--y1", bx: 20, by: 25, px: 0.0, py: 1.1 },
    { kx: "--x2", ky: "--y2", bx: 80, by: 20, px: 2.0, py: 3.4 },
    { kx: "--x3", ky: "--y3", bx: 25, by: 80, px: 4.0, py: 1.7 },
    { kx: "--x4", ky: "--y4", bx: 78, by: 78, px: 1.0, py: 5.2 },
  ];

  const amp = 9; // 揺れ幅(%)
  let raf = 0;

  const tick = (t) => {
    const s = t / 1000;
    for (const p of pts) {
      const x = p.bx + Math.sin(s * 0.35 + p.px) * amp;
      const y = p.by + Math.cos(s * 0.28 + p.py) * amp;
      blobs.style.setProperty(p.kx, x.toFixed(2) + "%");
      blobs.style.setProperty(p.ky, y.toFixed(2) + "%");
    }
    raf = requestAnimationFrame(tick);
  };
  raf = requestAnimationFrame(tick);

  // タブ非表示時はループを止める
  document.addEventListener("visibilitychange", () => {
    cancelAnimationFrame(raf); // 二重ループ防止
    if (!document.hidden) raf = requestAnimationFrame(tick);
  });
})();

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

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