動く幾何学パターン
canvas で三角格子を描き、サイン波に沿って各点を明滅させる動的背景。テック系のLPやダッシュボードの背景に使えます。
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- FlowDesk:動く三角格子を背景にした稼働ステータス画面 -->
<section class="fg-stage">
<!-- ★主役:canvas で三角格子を明滅させる動的背景 -->
<canvas class="fg-canvas" id="fgCanvas" aria-hidden="true"></canvas>
<div class="fg-inner">
<header class="fg-head">
<span class="fg-badge"><i class="fg-pulse"></i>すべて正常稼働中</span>
<h1 class="fg-title">システムステータス</h1>
</header>
<div class="fg-grid">
<div class="fg-card">
<span class="fg-card__label">API応答</span>
<b class="fg-card__val">142<small>ms</small></b>
<span class="fg-card__ok">▲ 良好</span>
</div>
<div class="fg-card">
<span class="fg-card__label">稼働率(30日)</span>
<b class="fg-card__val">99.98<small>%</small></b>
<span class="fg-card__ok">▲ 安定</span>
</div>
<div class="fg-card">
<span class="fg-card__label">処理中ジョブ</span>
<b class="fg-card__val">3,402</b>
<span class="fg-card__ok">▲ 順調</span>
</div>
</div>
</div>
</section>
CSS
/* FlowDesk:紺地に動く三角格子。テック系ステータスダッシュボード */
* { box-sizing: border-box; margin: 0; padding: 0; }
.fg-stage {
position: relative;
min-height: 400px;
height: 400px;
overflow: hidden;
background: radial-gradient(120% 120% at 50% -10%, #16284f 0%, #0f1b34 60%, #0a1226 100%);
color: #fff;
font-family: "Segoe UI", "Hiragino Sans", system-ui, sans-serif;
}
/* ★主役:明滅する三角格子を描く canvas */
.fg-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.fg-inner {
position: relative;
z-index: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 34px;
}
.fg-badge {
display: inline-flex;
align-items: center;
gap: 8px;
align-self: flex-start;
font-size: 12px;
font-weight: 700;
padding: 6px 14px;
border-radius: 999px;
background: rgba(79,124,255,0.16);
border: 1px solid rgba(79,124,255,0.4);
color: #aec4ff;
}
.fg-pulse {
width: 8px; height: 8px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 0 0 rgba(74,222,128,0.6);
animation: fgPulse 1.8s ease-out infinite;
}
@keyframes fgPulse {
0% { box-shadow: 0 0 0 0 rgba(74,222,128,0.6); }
100% { box-shadow: 0 0 0 8px rgba(74,222,128,0); }
}
.fg-title {
margin-top: 14px;
font-size: 30px;
font-weight: 800;
letter-spacing: 0.01em;
text-shadow: 0 4px 22px rgba(0,0,0,0.5);
}
.fg-grid {
margin-top: 22px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.fg-card {
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.14);
border-radius: 14px;
padding: 16px;
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
}
.fg-card__label { display: block; font-size: 11px; color: rgba(255,255,255,0.65); }
.fg-card__val {
display: block;
margin: 8px 0 6px;
font-size: 28px;
font-weight: 800;
color: #fff;
}
.fg-card__val small { font-size: 13px; font-weight: 600; color: rgba(255,255,255,0.6); margin-left: 2px; }
.fg-card__ok { font-size: 11px; font-weight: 700; color: #6ff0a8; }
@media (prefers-reduced-motion: reduce) {
.fg-pulse { animation: none; }
}
JavaScript
// canvasに三角格子を描き、サイン波で各点を明滅させるテック背景
(() => {
const canvas = document.getElementById("fgCanvas");
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 gap = 46; // 格子の間隔
let cols = [], rows = 0;
// サイズ調整(HiDPI対応)
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);
rows = Math.ceil(h / gap) + 1;
cols = Math.ceil(w / gap) + 1;
};
resize();
window.addEventListener("resize", resize);
// 1フレーム描画
const draw = (t) => {
const s = t / 1000;
ctx.clearRect(0, 0, w, h);
// 三角格子のライン
ctx.lineWidth = 1;
for (let r = 0; r < rows; r++) {
const offset = (r % 2) * (gap / 2); // 行ごとに半マスずらし三角に
const y = r * gap;
for (let c = 0; c < cols; c++) {
const x = c * gap + offset;
// 明滅の明るさ(位置と時間で揺らす)
const bright = (Math.sin(s * 1.4 + (x + y) * 0.012) + 1) / 2;
const a = 0.06 + bright * 0.22;
// 右と右下へ線を引く
ctx.strokeStyle = `rgba(120,160,255,${a})`;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + gap, y);
ctx.moveTo(x, y);
ctx.lineTo(x + gap / 2, y + gap);
ctx.stroke();
// 交点のドット(強く明滅)
ctx.fillStyle = `rgba(160,190,255,${0.1 + bright * 0.5})`;
ctx.beginPath();
ctx.arc(x, y, 1.4, 0, Math.PI * 2);
ctx.fill();
}
}
};
let raf = 0;
const loop = (t) => { draw(t); raf = requestAnimationFrame(loop); };
if (reduce) {
draw(0); // 静止1枚
} else {
raf = requestAnimationFrame(loop);
document.addEventListener("visibilitychange", () => {
cancelAnimationFrame(raf);
if (!document.hidden) raf = requestAnimationFrame(loop);
});
}
})();
コード
HTML
<!-- 動く幾何学パターン: canvas で三角格子を波のように動かす -->
<div class="geo-stage">
<canvas id="geoCanvas" class="geo-canvas"></canvas>
<div class="geo-overlay">
<h1 class="geo-title">Geometric Motion</h1>
<p class="geo-sub">canvas で描く三角格子が、サイン波に沿って明滅。テック系LPの背景に。</p>
</div>
</div>
CSS
/* canvas を全面に敷き、その上にテキストを乗せる */
* { box-sizing: border-box; margin: 0; padding: 0; }
.geo-stage {
position: relative;
min-height: 360px;
overflow: hidden;
display: grid;
place-items: center;
background: #070b1c;
font-family: "Segoe UI", "Hiragino Sans", system-ui, sans-serif;
}
.geo-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
.geo-overlay {
position: relative;
z-index: 2;
text-align: center;
color: #fff;
padding: 0 24px;
max-width: 500px;
/* 中央を少し暗くして可読性を確保 */
}
.geo-overlay::before {
content: "";
position: absolute;
inset: -40px -60px;
background: radial-gradient(closest-side, rgba(7, 11, 28, 0.7), transparent);
z-index: -1;
}
.geo-title {
font-size: 42px;
font-weight: 800;
letter-spacing: 0.02em;
background: linear-gradient(90deg, #5ef2ff, #7b88ff, #c479ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.geo-sub {
margin-top: 14px;
font-size: 14px;
line-height: 1.85;
color: rgba(255, 255, 255, 0.82);
}
JavaScript
// 三角格子を canvas に描き、各頂点の明度をサイン波で動かす
(() => {
const canvas = document.getElementById("geoCanvas");
if (!canvas || !canvas.getContext) return; // null安全
const ctx = canvas.getContext("2d");
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let dpr = Math.min(window.devicePixelRatio || 1, 2);
let W = 0, H = 0;
const GAP = 46; // 格子間隔(px)
// リサイズ対応: 親要素サイズに合わせる
const resize = () => {
dpr = Math.min(window.devicePixelRatio || 1, 2); // モニタ移動にも追従
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);
// 1フレーム描画
const draw = (t) => {
const s = t / 1000;
ctx.clearRect(0, 0, W, H);
const cols = Math.ceil(W / GAP) + 2;
const rows = Math.ceil(H / GAP) + 2;
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
// 行ごとに半マスずらして三角格子に
const px = x * GAP + (y % 2 ? GAP / 2 : 0);
const py = y * GAP;
// 距離と時間で明滅
const d = (px + py) * 0.012;
const wave = (Math.sin(d - s * 1.4) + 1) / 2;
const radius = 1.4 + wave * 2.6;
const alpha = 0.12 + wave * 0.55;
// 色相を位置でずらす
const hue = 190 + (px * 0.05 + py * 0.08) % 90;
ctx.beginPath();
ctx.arc(px, py, radius, 0, Math.PI * 2);
ctx.fillStyle = `hsla(${hue}, 90%, 68%, ${alpha})`;
ctx.fill();
// 右隣へ細い線
if (x < cols - 1 && wave > 0.4) {
ctx.beginPath();
ctx.moveTo(px, py);
ctx.lineTo(px + GAP, py);
ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${alpha * 0.25})`;
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
};
let raf = 0;
if (reduce) {
draw(0); // 静止画として1回だけ
} 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 で三角格子を描き、サイン波に沿って各点を明滅させる動的背景。テック系のLPやダッシュボードの背景に使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 動く幾何学パターン: canvas で三角格子を波のように動かす -->
<div class="geo-stage">
<canvas id="geoCanvas" class="geo-canvas"></canvas>
<div class="geo-overlay">
<h1 class="geo-title">Geometric Motion</h1>
<p class="geo-sub">canvas で描く三角格子が、サイン波に沿って明滅。テック系LPの背景に。</p>
</div>
</div>
【CSS】
/* canvas を全面に敷き、その上にテキストを乗せる */
* { box-sizing: border-box; margin: 0; padding: 0; }
.geo-stage {
position: relative;
min-height: 360px;
overflow: hidden;
display: grid;
place-items: center;
background: #070b1c;
font-family: "Segoe UI", "Hiragino Sans", system-ui, sans-serif;
}
.geo-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
.geo-overlay {
position: relative;
z-index: 2;
text-align: center;
color: #fff;
padding: 0 24px;
max-width: 500px;
/* 中央を少し暗くして可読性を確保 */
}
.geo-overlay::before {
content: "";
position: absolute;
inset: -40px -60px;
background: radial-gradient(closest-side, rgba(7, 11, 28, 0.7), transparent);
z-index: -1;
}
.geo-title {
font-size: 42px;
font-weight: 800;
letter-spacing: 0.02em;
background: linear-gradient(90deg, #5ef2ff, #7b88ff, #c479ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.geo-sub {
margin-top: 14px;
font-size: 14px;
line-height: 1.85;
color: rgba(255, 255, 255, 0.82);
}
【JavaScript】
// 三角格子を canvas に描き、各頂点の明度をサイン波で動かす
(() => {
const canvas = document.getElementById("geoCanvas");
if (!canvas || !canvas.getContext) return; // null安全
const ctx = canvas.getContext("2d");
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let dpr = Math.min(window.devicePixelRatio || 1, 2);
let W = 0, H = 0;
const GAP = 46; // 格子間隔(px)
// リサイズ対応: 親要素サイズに合わせる
const resize = () => {
dpr = Math.min(window.devicePixelRatio || 1, 2); // モニタ移動にも追従
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);
// 1フレーム描画
const draw = (t) => {
const s = t / 1000;
ctx.clearRect(0, 0, W, H);
const cols = Math.ceil(W / GAP) + 2;
const rows = Math.ceil(H / GAP) + 2;
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
// 行ごとに半マスずらして三角格子に
const px = x * GAP + (y % 2 ? GAP / 2 : 0);
const py = y * GAP;
// 距離と時間で明滅
const d = (px + py) * 0.012;
const wave = (Math.sin(d - s * 1.4) + 1) / 2;
const radius = 1.4 + wave * 2.6;
const alpha = 0.12 + wave * 0.55;
// 色相を位置でずらす
const hue = 190 + (px * 0.05 + py * 0.08) % 90;
ctx.beginPath();
ctx.arc(px, py, radius, 0, Math.PI * 2);
ctx.fillStyle = `hsla(${hue}, 90%, 68%, ${alpha})`;
ctx.fill();
// 右隣へ細い線
if (x < cols - 1 && wave > 0.4) {
ctx.beginPath();
ctx.moveTo(px, py);
ctx.lineTo(px + GAP, py);
ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${alpha * 0.25})`;
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
};
let raf = 0;
if (reduce) {
draw(0); // 静止画として1回だけ
} 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。