多層サイン波アニメ

振幅と周波数の異なる正弦波を加算合成しネオン状に発光させる波形ビジュアル。音楽・オーディオ系のヘッダー背景に向いています。

#canvas#animation#math#wave

ライブデモ

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

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

HTML
<!-- Sakura:新曲MVプレイヤー風ヘッダー(多層サイン波) -->
<section class="sk-mv">
  <!-- 主役:ネオンの多層サイン波 -->
  <canvas class="sk-mv__wave" id="skWaves"></canvas>

  <!-- 前景UI:楽曲情報+プレイヤー -->
  <div class="sk-mv__inner">
    <span class="sk-mv__tag">NEW DIGITAL SINGLE</span>
    <h1 class="sk-mv__title">春風メロディー</h1>
    <p class="sk-mv__artist">Sakura — 7th Single</p>

    <div class="sk-mv__player">
      <button class="sk-mv__play" type="button" aria-label="再生">▶</button>
      <div class="sk-mv__bar"><span></span></div>
      <span class="sk-mv__time">1:24 / 4:02</span>
    </div>

    <div class="sk-mv__links">
      <a href="#">MVを見る</a>
      <a href="#">歌詞</a>
      <a href="#">配信リンク</a>
    </div>
  </div>
</section>
CSS
/* Sakura:新曲MVヘッダー(サイン波ビジュアル) */
:root {
  --pink: #ffd1e0;
  --pink-deep: #ff7fa8;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
  overflow: hidden;
}

.sk-mv {
  position: relative;
  height: 400px;
  overflow: hidden;
  background:
    radial-gradient(600px 300px at 50% 120%, #ffe3ee, transparent),
    linear-gradient(175deg, #fff5f9, #ffd9e6);
}

/* 主役:波のキャンバス(下半分中心に発光) */
.sk-mv__wave {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 1;
}

.sk-mv__inner {
  position: relative;
  z-index: 2;
  padding: 34px 32px;
  max-width: 440px;
}
.sk-mv__tag {
  display: inline-block;
  font-size: 10px;
  letter-spacing: 0.3em;
  color: var(--pink-deep);
  font-weight: 800;
}
.sk-mv__title {
  margin: 12px 0 6px;
  font-size: 36px;
  line-height: 1.2;
  font-weight: 900;
  color: #5a2b3d;
}
.sk-mv__artist {
  margin: 0 0 26px;
  font-size: 13px;
  color: #93707e;
  font-weight: 700;
}

.sk-mv__player {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border-radius: 16px;
  background: rgba(255,255,255,0.8);
  box-shadow: 0 8px 22px rgba(196,120,150,0.2);
  max-width: 360px;
  margin-bottom: 18px;
}
.sk-mv__play {
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  border: none;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--pink-deep), #ff9cbb);
  color: #fff;
  font-size: 14px;
  cursor: pointer;
  box-shadow: 0 6px 14px rgba(255,127,168,0.4);
}
.sk-mv__bar {
  flex: 1;
  height: 6px;
  border-radius: 999px;
  background: #f3d5e0;
  overflow: hidden;
}
.sk-mv__bar span {
  display: block;
  width: 35%;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--pink-deep), #ff9cbb);
}
.sk-mv__time { font-size: 11px; color: #93707e; white-space: nowrap; }

.sk-mv__links { display: flex; gap: 18px; }
.sk-mv__links a {
  font-size: 12.5px;
  color: #7a5563;
  text-decoration: none;
  font-weight: 700;
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s ease;
}
.sk-mv__links a:hover { border-color: var(--pink-deep); }

@media (prefers-reduced-motion: reduce) {
  .sk-mv__links a { transition: none; }
}
JavaScript
// Sakura:多層サイン波をMVヘッダー背景に(桜色でネオン発光)
(() => {
  const canvas = document.getElementById('skWaves');
  if (!canvas) return; // null安全
  const ctx = canvas.getContext('2d');
  if (!ctx) return;

  let w = 0, h = 0, raf = 0, running = true, t = 0;
  const dpr = Math.min(window.devicePixelRatio || 1, 2);

  // 振幅・周波数・速度・色の異なる層
  const layers = [
    { amp: 26, freq: 0.012, speed: 0.030, color: 'rgba(255,127,168,0.55)', yr: 0.66 },
    { amp: 18, freq: 0.018, speed: 0.045, color: 'rgba(255,156,187,0.5)',  yr: 0.72 },
    { amp: 32, freq: 0.008, speed: 0.022, color: 'rgba(214,140,255,0.4)',  yr: 0.6  }
  ];

  function resize() {
    const r = canvas.getBoundingClientRect();
    w = r.width; h = r.height;
    canvas.width = Math.max(1, w * dpr);
    canvas.height = Math.max(1, h * dpr);
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  resize();

  function step() {
    ctx.clearRect(0, 0, w, h);
    ctx.globalCompositeOperation = 'lighter'; // 加算で波を発光

    for (const L of layers) {
      const baseY = h * L.yr;
      ctx.beginPath();
      ctx.moveTo(0, baseY);
      for (let x = 0; x <= w; x += 4) {
        // 2つのサインを合成してうねりを出す
        const y = baseY
          + Math.sin(x * L.freq + t * L.speed) * L.amp
          + Math.sin(x * L.freq * 0.5 - t * L.speed * 1.3) * L.amp * 0.4;
        ctx.lineTo(x, y);
      }
      ctx.lineWidth = 2.5;
      ctx.strokeStyle = L.color;
      ctx.shadowBlur = 14;
      ctx.shadowColor = L.color;
      ctx.stroke();
    }
    ctx.shadowBlur = 0;
    ctx.globalCompositeOperation = 'source-over';

    t += 1;
    raf = requestAnimationFrame(step);
  }

  function start() {
    if (running) return;
    running = true;
    raf = requestAnimationFrame(step);
  }
  function stop() {
    running = false;
    cancelAnimationFrame(raf);
  }

  window.addEventListener('resize', resize);
  document.addEventListener('visibilitychange', () => {
    document.hidden ? stop() : start();
  });

  running = false;
  start();
})();

コード

HTML
<!-- 正弦波アニメ -->
<div class="stage">
  <canvas id="sineCanvas"></canvas>
  <div class="title">SINE&nbsp;WAVES</div>
</div>
CSS
/* 正弦波アニメのステージ */
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
.stage {
  position: relative;
  width: 100%;
  height: 360px;
  overflow: hidden;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(1000px 600px at 50% 50%, #10243a, #050a14 80%);
}
#sineCanvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* 中央のタイトル(波の背後に薄く) */
.title {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: rgba(120, 200, 255, .12);
  font-size: clamp(28px, 9vw, 72px);
  font-weight: 800;
  letter-spacing: .12em;
  pointer-events: none;
  user-select: none;
}
JavaScript
// 多層の正弦波アニメデモ
(() => {
  const canvas = document.getElementById('sineCanvas');
  if (!canvas) return;
  const ctx = canvas.getContext('2d');
  const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  let w = 0, h = 0, t = 0;
  const dpr = Math.min(window.devicePixelRatio || 1, 2);

  // 各層の波パラメータ(振幅・周波数・速度・色)
  const layers = [
    { amp: 40, freq: 0.012, speed: 0.030, hue: 190, alpha: 0.9, off: 0 },
    { amp: 28, freq: 0.018, speed: 0.045, hue: 280, alpha: 0.7, off: 1.2 },
    { amp: 55, freq: 0.008, speed: 0.020, hue: 330, alpha: 0.55, off: 2.4 },
    { amp: 18, freq: 0.025, speed: 0.060, hue: 150, alpha: 0.5, off: 3.6 }
  ];

  function resize() {
    const r = canvas.getBoundingClientRect();
    w = r.width; h = r.height;
    canvas.width = w * dpr;
    canvas.height = h * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  resize();
  window.addEventListener('resize', resize);

  function drawWave(layer) {
    const midY = h / 2;
    ctx.beginPath();
    for (let x = 0; x <= w; x += 4) {
      // 2つの正弦を合成してうねりを出す
      const y = midY
        + Math.sin(x * layer.freq + t * layer.speed * 10 + layer.off) * layer.amp
        + Math.sin(x * layer.freq * 0.5 + t * layer.speed * 6) * (layer.amp * 0.4);
      x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
    }
    ctx.strokeStyle = `hsla(${layer.hue},85%,62%,${layer.alpha})`;
    ctx.lineWidth = 2;
    ctx.shadowBlur = 12;
    ctx.shadowColor = `hsla(${layer.hue},85%,62%,0.8)`;
    ctx.stroke();
    ctx.shadowBlur = 0;
  }

  function step() {
    ctx.clearRect(0, 0, w, h);
    ctx.globalCompositeOperation = 'lighter';
    for (const layer of layers) drawWave(layer);
    ctx.globalCompositeOperation = 'source-over';
    if (!reduced) t += 1;
    requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
})();

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

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

# 追加してほしい効果
多層サイン波アニメ(Canvas エフェクト)
振幅と周波数の異なる正弦波を加算合成しネオン状に発光させる波形ビジュアル。音楽・オーディオ系のヘッダー背景に向いています。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 正弦波アニメ -->
<div class="stage">
  <canvas id="sineCanvas"></canvas>
  <div class="title">SINE&nbsp;WAVES</div>
</div>

【CSS】
/* 正弦波アニメのステージ */
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
.stage {
  position: relative;
  width: 100%;
  height: 360px;
  overflow: hidden;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(1000px 600px at 50% 50%, #10243a, #050a14 80%);
}
#sineCanvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* 中央のタイトル(波の背後に薄く) */
.title {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: rgba(120, 200, 255, .12);
  font-size: clamp(28px, 9vw, 72px);
  font-weight: 800;
  letter-spacing: .12em;
  pointer-events: none;
  user-select: none;
}

【JavaScript】
// 多層の正弦波アニメデモ
(() => {
  const canvas = document.getElementById('sineCanvas');
  if (!canvas) return;
  const ctx = canvas.getContext('2d');
  const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  let w = 0, h = 0, t = 0;
  const dpr = Math.min(window.devicePixelRatio || 1, 2);

  // 各層の波パラメータ(振幅・周波数・速度・色)
  const layers = [
    { amp: 40, freq: 0.012, speed: 0.030, hue: 190, alpha: 0.9, off: 0 },
    { amp: 28, freq: 0.018, speed: 0.045, hue: 280, alpha: 0.7, off: 1.2 },
    { amp: 55, freq: 0.008, speed: 0.020, hue: 330, alpha: 0.55, off: 2.4 },
    { amp: 18, freq: 0.025, speed: 0.060, hue: 150, alpha: 0.5, off: 3.6 }
  ];

  function resize() {
    const r = canvas.getBoundingClientRect();
    w = r.width; h = r.height;
    canvas.width = w * dpr;
    canvas.height = h * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  resize();
  window.addEventListener('resize', resize);

  function drawWave(layer) {
    const midY = h / 2;
    ctx.beginPath();
    for (let x = 0; x <= w; x += 4) {
      // 2つの正弦を合成してうねりを出す
      const y = midY
        + Math.sin(x * layer.freq + t * layer.speed * 10 + layer.off) * layer.amp
        + Math.sin(x * layer.freq * 0.5 + t * layer.speed * 6) * (layer.amp * 0.4);
      x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
    }
    ctx.strokeStyle = `hsla(${layer.hue},85%,62%,${layer.alpha})`;
    ctx.lineWidth = 2;
    ctx.shadowBlur = 12;
    ctx.shadowColor = `hsla(${layer.hue},85%,62%,0.8)`;
    ctx.stroke();
    ctx.shadowBlur = 0;
  }

  function step() {
    ctx.clearRect(0, 0, w, h);
    ctx.globalCompositeOperation = 'lighter';
    for (const layer of layers) drawWave(layer);
    ctx.globalCompositeOperation = 'source-over';
    if (!reduced) t += 1;
    requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
})();

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

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