シルクウェーブ

半透明のサイン波リボンを canvas で加算合成し、しなやかに流れる絹のような発光背景を描きます。高級感あるヒーローに。

#canvas#javascript#animation

ライブデモ

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

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

HTML
<!-- Sakura:流れる絹波を背に、新曲MVのプレミア告知 -->
<section class="sw-stage">
  <!-- ★主役:加算合成で流れるシルクウェーブ -->
  <canvas class="sw-canvas" id="swCanvas" aria-hidden="true"></canvas>

  <div class="sw-inner">
    <span class="sw-eyebrow">NEW MUSIC VIDEO</span>
    <h1 class="sw-title">「桜花リフレイン」</h1>
    <p class="sw-sub">5th Single ・ Music Video Premiere</p>

    <!-- MVプレイヤー風カード -->
    <div class="sw-player">
      <button class="sw-play" type="button" id="swPlay" aria-label="再生">▶</button>
      <div class="sw-player__meta">
        <b>桜花リフレイン</b>
        <span>3:42 ・ プレミア公開</span>
      </div>
    </div>

    <p class="sw-date">6.20 (土) 20:00 公開</p>
  </div>
</section>
CSS
/* Sakura:深い夜桜色に、絹のような発光ウェーブのMV告知 */
* { box-sizing: border-box; margin: 0; padding: 0; }

.sw-stage {
  position: relative;
  min-height: 400px;
  height: 400px;
  overflow: hidden;
  background: linear-gradient(160deg, #2a0f24 0%, #3d1535 55%, #4a1230 100%);
  color: #fff;
  font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
}

/* ★主役:シルクウェーブを加算合成で描く canvas */
.sw-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
}

.sw-inner {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 24px;
}
.sw-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.28em;
  color: #ffc6df;
}
.sw-title {
  margin-top: 10px;
  font-size: 36px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-shadow: 0 0 28px rgba(255,143,196,0.55);
}
.sw-sub { margin-top: 8px; font-size: 12px; color: rgba(255,255,255,0.82); letter-spacing: 0.06em; }

/* MVプレイヤー風カード */
.sw-player {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px 10px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.25);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.sw-play {
  width: 42px; height: 42px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  font-size: 14px;
  color: #4a1230;
  background: linear-gradient(135deg, #ffd1e0, #ff9ec6);
  box-shadow: 0 6px 16px rgba(255,143,196,0.5);
  transition: transform 0.2s ease;
}
.sw-play:hover { transform: scale(1.08); }
.sw-play:active { transform: scale(0.95); }
.sw-player__meta { text-align: left; }
.sw-player__meta b { display: block; font-size: 13px; }
.sw-player__meta span { font-size: 10px; color: rgba(255,255,255,0.75); }

.sw-date { margin-top: 14px; font-size: 12px; font-weight: 700; color: #ffd1e0; }

@media (prefers-reduced-motion: reduce) {
  .sw-play { transition: none; }
}
JavaScript
// 半透明のサイン波リボンを加算合成し、流れる絹のような発光を描く
(() => {
  const canvas = document.getElementById("swCanvas");
  if (!canvas) return; // null安全
  const ctx = canvas.getContext("2d");
  if (!ctx) return;

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

  let w = 0, h = 0, dpr = 1;
  const resize = () => {
    dpr = Math.min(window.devicePixelRatio || 1, 2);
    w = canvas.clientWidth;
    h = canvas.clientHeight;
    canvas.width = Math.floor(w * dpr);
    canvas.height = Math.floor(h * dpr);
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  };
  resize();
  window.addEventListener("resize", resize);

  // 桜系の発光色リボン
  const ribbons = [
    { col: "rgba(255,143,196,0.18)", amp: 42, len: 0.011, sp: 0.6, yoff: 0.45, ph: 0 },
    { col: "rgba(179,156,255,0.16)", amp: 52, len: 0.008, sp: 0.4, yoff: 0.55, ph: 2 },
    { col: "rgba(255,209,224,0.14)", amp: 34, len: 0.013, sp: 0.8, yoff: 0.5,  ph: 4 },
    { col: "rgba(127,214,255,0.12)", amp: 60, len: 0.006, sp: 0.3, yoff: 0.62, ph: 1 },
  ];

  // 1本のリボンを塗る
  const drawRibbon = (r, t) => {
    ctx.beginPath();
    ctx.moveTo(0, h);
    for (let x = 0; x <= w; x += 8) {
      const y = h * r.yoff
        + Math.sin(x * r.len + t * r.sp + r.ph) * r.amp
        + Math.sin(x * r.len * 0.5 + t * r.sp * 0.7) * (r.amp * 0.4);
      ctx.lineTo(x, y);
    }
    ctx.lineTo(w, h);
    ctx.closePath();
    ctx.fillStyle = r.col;
    ctx.fill();
  };

  const render = (t) => {
    ctx.clearRect(0, 0, w, h);
    ctx.globalCompositeOperation = "lighter"; // 加算合成で発光感
    for (const r of ribbons) drawRibbon(r, t);
    ctx.globalCompositeOperation = "source-over";
  };

  if (reduce) {
    render(0); // 静止1枚
    return;
  }

  let raf = 0;
  const loop = (ms) => { render(ms / 1000); raf = requestAnimationFrame(loop); };
  raf = requestAnimationFrame(loop);

  document.addEventListener("visibilitychange", () => {
    cancelAnimationFrame(raf);
    if (!document.hidden) raf = requestAnimationFrame(loop);
  });

  // 再生ボタンの軽い反応
  const play = document.getElementById("swPlay");
  if (play) {
    play.addEventListener("click", () => {
      const on = play.textContent === "▶";
      play.textContent = on ? "❚❚" : "▶";
    });
  }
})();

コード

HTML
<!-- シルクウェーブ: canvasで重なるサイン波のリボンを描き、絹のような流れを表現 -->
<div class="silk-stage">
  <canvas id="silkCanvas" class="silk-canvas"></canvas>
  <div class="silk-content">
    <h1 class="silk-title">Silk Waves</h1>
    <p class="silk-sub">半透明のサイン波リボンを重ねて、しなやかに流れる絹のような背景を描きます。</p>
  </div>
</div>
CSS
/* canvasの波の上にテキストを乗せる */
* { box-sizing: border-box; margin: 0; padding: 0; }

.silk-stage {
  position: relative;
  min-height: 360px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: linear-gradient(160deg, #1a0b2e 0%, #2d0b4e 50%, #0b1b3a 100%);
  font-family: "Segoe UI", "Hiragino Sans", system-ui, sans-serif;
}

.silk-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

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

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

.silk-sub {
  margin-top: 14px;
  font-size: 14px;
  line-height: 1.85;
  color: rgba(255, 255, 255, 0.85);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
}
JavaScript
// 複数のサイン波リボンを半透明で重ね描きし、絹のような流れを作る
(() => {
  const canvas = document.getElementById("silkCanvas");
  if (!canvas || !canvas.getContext) return; // null安全
  const ctx = canvas.getContext("2d");

  const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  let W = 0, H = 0;

  const resize = () => {
    const r = canvas.getBoundingClientRect();
    W = Math.max(1, r.width);
    H = Math.max(1, r.height);
    canvas.width = Math.floor(W * dpr);
    canvas.height = Math.floor(H * dpr);
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  };
  resize();
  window.addEventListener("resize", resize);

  // リボンごとの色と動きのパラメータ
  const ribbons = [
    { hue: 290, amp: 38, len: 0.012, sp: 0.6, yo: 0.40, a: 0.16 },
    { hue: 210, amp: 50, len: 0.009, sp: 0.45, yo: 0.55, a: 0.16 },
    { hue: 330, amp: 30, len: 0.015, sp: 0.8, yo: 0.50, a: 0.14 },
    { hue: 180, amp: 44, len: 0.011, sp: 0.35, yo: 0.62, a: 0.12 },
  ];

  // 1本のリボンを塗りつぶす
  const drawRibbon = (rb, s) => {
    ctx.beginPath();
    ctx.moveTo(0, H);
    for (let x = 0; x <= W; x += 8) {
      const y = H * rb.yo
        + Math.sin(x * rb.len + s * rb.sp) * rb.amp
        + Math.sin(x * rb.len * 0.5 - s * rb.sp * 0.7) * rb.amp * 0.5;
      ctx.lineTo(x, y);
    }
    ctx.lineTo(W, H);
    ctx.closePath();

    const g = ctx.createLinearGradient(0, 0, W, 0);
    g.addColorStop(0, `hsla(${rb.hue}, 85%, 65%, 0)`);
    g.addColorStop(0.5, `hsla(${rb.hue}, 85%, 65%, ${rb.a})`);
    g.addColorStop(1, `hsla(${rb.hue + 30}, 85%, 60%, 0)`);
    ctx.fillStyle = g;
    ctx.fill();
  };

  const draw = (t) => {
    const s = t / 1000;
    ctx.clearRect(0, 0, W, H);
    ctx.globalCompositeOperation = "lighter"; // 加算合成で発光感
    for (const rb of ribbons) drawRibbon(rb, s);
    ctx.globalCompositeOperation = "source-over";
  };

  let raf = 0;
  if (reduce) {
    draw(0);
  } else {
    const loop = (t) => { draw(t); raf = requestAnimationFrame(loop); };
    raf = requestAnimationFrame(loop);
    document.addEventListener("visibilitychange", () => {
      cancelAnimationFrame(raf); // 二重ループ防止
      if (!document.hidden) raf = requestAnimationFrame(loop);
    });
  }
})();

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

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

# 追加してほしい効果
シルクウェーブ(背景 & グラデーション)
半透明のサイン波リボンを canvas で加算合成し、しなやかに流れる絹のような発光背景を描きます。高級感あるヒーローに。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- シルクウェーブ: canvasで重なるサイン波のリボンを描き、絹のような流れを表現 -->
<div class="silk-stage">
  <canvas id="silkCanvas" class="silk-canvas"></canvas>
  <div class="silk-content">
    <h1 class="silk-title">Silk Waves</h1>
    <p class="silk-sub">半透明のサイン波リボンを重ねて、しなやかに流れる絹のような背景を描きます。</p>
  </div>
</div>

【CSS】
/* canvasの波の上にテキストを乗せる */
* { box-sizing: border-box; margin: 0; padding: 0; }

.silk-stage {
  position: relative;
  min-height: 360px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: linear-gradient(160deg, #1a0b2e 0%, #2d0b4e 50%, #0b1b3a 100%);
  font-family: "Segoe UI", "Hiragino Sans", system-ui, sans-serif;
}

.silk-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

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

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

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

【JavaScript】
// 複数のサイン波リボンを半透明で重ね描きし、絹のような流れを作る
(() => {
  const canvas = document.getElementById("silkCanvas");
  if (!canvas || !canvas.getContext) return; // null安全
  const ctx = canvas.getContext("2d");

  const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  let W = 0, H = 0;

  const resize = () => {
    const r = canvas.getBoundingClientRect();
    W = Math.max(1, r.width);
    H = Math.max(1, r.height);
    canvas.width = Math.floor(W * dpr);
    canvas.height = Math.floor(H * dpr);
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  };
  resize();
  window.addEventListener("resize", resize);

  // リボンごとの色と動きのパラメータ
  const ribbons = [
    { hue: 290, amp: 38, len: 0.012, sp: 0.6, yo: 0.40, a: 0.16 },
    { hue: 210, amp: 50, len: 0.009, sp: 0.45, yo: 0.55, a: 0.16 },
    { hue: 330, amp: 30, len: 0.015, sp: 0.8, yo: 0.50, a: 0.14 },
    { hue: 180, amp: 44, len: 0.011, sp: 0.35, yo: 0.62, a: 0.12 },
  ];

  // 1本のリボンを塗りつぶす
  const drawRibbon = (rb, s) => {
    ctx.beginPath();
    ctx.moveTo(0, H);
    for (let x = 0; x <= W; x += 8) {
      const y = H * rb.yo
        + Math.sin(x * rb.len + s * rb.sp) * rb.amp
        + Math.sin(x * rb.len * 0.5 - s * rb.sp * 0.7) * rb.amp * 0.5;
      ctx.lineTo(x, y);
    }
    ctx.lineTo(W, H);
    ctx.closePath();

    const g = ctx.createLinearGradient(0, 0, W, 0);
    g.addColorStop(0, `hsla(${rb.hue}, 85%, 65%, 0)`);
    g.addColorStop(0.5, `hsla(${rb.hue}, 85%, 65%, ${rb.a})`);
    g.addColorStop(1, `hsla(${rb.hue + 30}, 85%, 60%, 0)`);
    ctx.fillStyle = g;
    ctx.fill();
  };

  const draw = (t) => {
    const s = t / 1000;
    ctx.clearRect(0, 0, W, H);
    ctx.globalCompositeOperation = "lighter"; // 加算合成で発光感
    for (const rb of ribbons) drawRibbon(rb, s);
    ctx.globalCompositeOperation = "source-over";
  };

  let raf = 0;
  if (reduce) {
    draw(0);
  } else {
    const loop = (t) => { draw(t); raf = requestAnimationFrame(loop); };
    raf = requestAnimationFrame(loop);
    document.addEventListener("visibilitychange", () => {
      cancelAnimationFrame(raf); // 二重ループ防止
      if (!document.hidden) raf = requestAnimationFrame(loop);
    });
  }
})();

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

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