canvas-generative について
クリエイティブコーディング向けの Canvas 2D 生成アートスキルです。ノイズ、パーティクル、フローフィールド、再帰構造の実装を案内し、鮮明な描画と効率的なアニメーションループにも配慮します。
主な機能
- 01DPR 対応のキャンバス設定
- 02ノイズと再現可能なパターン
- 03パーティクルプールとフローフィールド
- 04フラクタルと L-system
- 05オフスクリーン描画と軌跡効果
活用シーン
- 01高 DPI 画面で鮮明な生成アートを描画
- 02フローフィールドを使ったパーティクルシステムの作成
- 03L-system による再帰構造の構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ athevon/ genjutsu --skill canvas-generativeインストール後、Claude Code で/canvas-generativeを入力して読み込まれたか確認します。
詳細
バージョン依存です。 以下のすべての API 名、SDK の対応状況、ブラウザーのサポートに関する記述は、一次情報に基づき 2026-09-08 に検証済みです。何をいつ検証したかは
_jutsu/VERSIONS.mdに記載されています。この日付が古い場合は、バージョン番号をもとに作業する前に再検証してください。
Canvas Generative
Canvas 2D によるアルゴリズムアートとジェネレーティブアートです。 ここでは簡潔なルールを示します。詳しい解説とリファレンス実装は
references/にあります。
Canvas 2D のセットアップ
DPR に対応したサイズ設定
すべてのキャンバスは、Retina/HiDPI ディスプレイで鮮明に表示される必要があります。バッファーサイズを物理ピクセルサイズに設定し、CSS で縮小してください。
function setupCanvas(canvas, width, height) {
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
return ctx;
}
リサイズハンドラー
function handleResize(canvas, ctx, draw) {
const ro = new ResizeObserver(([entry]) => {
const { width, height } = entry.contentRect;
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
draw(); // re-render after resize
});
ro.observe(canvas.parentElement);
return () => ro.disconnect();
}
アニメーションループ(RAF)
let animId;
let prevTime = 0;
function loop(time) {
const dt = Math.min((time - prevTime) / 1000, 0.1); // cap delta to avoid spiral of death
prevTime = time;
update(dt);
render(ctx);
animId = requestAnimationFrame(loop);
}
// Start
animId = requestAnimationFrame(loop);
// Stop
cancelAnimationFrame(animId);
ノイズ
| 種類 | 特徴 | 最適な用途 |
|---|---|---|
| Perlin | 滑らかで、グリッドに沿った偏りがあり、低コスト | 地形、雲、穏やかな有機的テクスチャ |
| Simplex | グリッド状のアーティファクトがなく、勾配に優れ、やや高コスト | フローフィールド、有機的な動き、シームレスタイリング |
| Worley(セルラー) | 最も近い点までの距離を表し、セル状 | ボロノイパターン、コースティクス、ひび割れ、セル状テクスチャ |
使用ルール:
- 入力座標は必ずスケーリングしてください(
noiseScale係数で割ります)。ピクセル座標をそのまま使うと、見た目がノイズ状になります - 細部を表現するにはオクターブ(フラクタルブラウン運動)を使います。複数のノイズ呼び出しを、周波数を上げながら振幅を下げて合算します
- 再現性を確保するため、ノイズにシードを設定してください
// fBm pattern
function fbm(x, y, octaves = 4, lacunarity = 2, gain = 0.5) {
let value = 0, amplitude = 1, frequency = 1, maxAmp = 0;
for (let i = 0; i < octaves; i++) {
value += amplitude * noise2D(x * frequency, y * frequency);
maxAmp += amplitude;
amplitude *= gain;
frequency *= lacunarity;
}
return value / maxAmp; // normalize to [-1, 1]
}
パーティクルシステム
プールパターン(GC の負荷を回避)
固定サイズの配列を事前に確保してください。実行時に new や splice を使わないでください。
const POOL_SIZE = 10000;
const particles = new Array(POOL_SIZE);
let aliveCount = 0;
// Init pool
for (let i = 0; i < POOL_SIZE; i++) {
particles[i] = { x: 0, y: 0, vx: 0, vy: 0, life: 0, maxLife: 0, active: false };
}
function spawn(x, y) {
if (aliveCount >= POOL_SIZE) return;
const p = particles[aliveCount++];
p.x = x; p.y = y;
p.vx = (Math.random() - 0.5) * 2;
p.vy = (Math.random() - 0.5) * 2;
p.life = 0; p.maxLife = 60 + Math.random() * 60;
p.active = true;
}
function update() {
for (let i = aliveCount - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx; p.y += p.vy;
p.life++;
if (p.life >= p.maxLife) {
// Swap with last alive, shrink pool
particles[i] = particles[--aliveCount];
particles[aliveCount] = p;
p.active = false;
}
}
}
フローフィールド
パーティクルを誘導する角度ベクトルのグリッドです。ジェネレーティブアートの定番の手法です。
- グリッドを構築: キャンバスをセルに分割し、ノイズから各セルの角度を計算します
- 参照: パーティクルの位置をグリッドセルに対応させ、角度を取得します
- 誘導: 角度を速度として適用し、フレームをまたいで累積します
const cols = Math.ceil(width / cellSize);
const rows = Math.ceil(height / cellSize);
const field = new Float32Array(cols * rows);
// Fill with noise-based angles
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
field[y * cols + x] = noise2D(x * 0.05, y * 0.05) * Math.PI * 2;
}
}
// Particle follow
function followField(p) {
const col = Math.floor(p.x / cellSize);
const row = Math.floor(p.y / cellSize);
if (col >= 0 && col < cols && row >= 0 && row < rows) {
const angle = field[row * cols + col];
p.vx += Math.cos(angle) * force;
p.vy += Math.sin(angle) * force;
}
// Damping to prevent runaway velocity
p.vx *= 0.98;
p.vy *= 0.98;
}
フラクタルと L システム
L システムは、文字列の書き換えとタートルグラフィックスを組み合わせて、再帰構造を表現します。
| 構成要素 | 役割 |
|---|---|
| 公理 | 開始文字列(例:"F") |
| ルール | 生成ルール(例:"F" -> "F[+F]F[-F]F") |
| 角度 | +/- ごとのタートルの回転角度 |
| 反復回数 | ルールを適用する回数 |
function lsystem(axiom, rules, iterations) {
let current = axiom;
for (let i = 0; i < iterations; i++) {
current = current.split('').map(c => rules[c] || c).join('');
}
return current;
}
function drawLSystem(ctx, commands, len, angle) {
const stack = [];
for (const c of commands) {
switch (c) {
case 'F': ctx.lineTo(ctx._x += Math.cos(ctx._a) * len, ctx._y += Math.sin(ctx._a) * len); break;
case '+': ctx._a += angle; break;
case '-': ctx._a -= angle; break;
case '[': stack.push({ x: ctx._x, y: ctx._y, a: ctx._a }); break;
case ']': { const s = stack.pop(); ctx._x = s.x; ctx._y = s.y; ctx._a = s.a; ctx.moveTo(s.x, s.y); } break;
}
}
}
ダブルバッファーパターン
オフスクリーンキャンバスに描画し、表示用キャンバスに転送します。ちらつきをなくし、残像効果を実現できます。
const offscreen = document.createElement('canvas');
offscreen.width = canvas.width;
offscreen.height = canvas.height;
const offCtx = offscreen.getContext('2d');
function render() {
// Draw to offscreen
offCtx.fillStyle = 'rgba(0, 0, 0, 0.05)'; // trail fade
offCtx.fillRect(0, 0, offscreen.width, offscreen.height);
drawParticles(offCtx);
// Blit to screen
ctx.drawImage(offscreen, 0, 0);
}
禁止事項
1. 残像効果のために毎フレーム clearRect を呼び出さない
クリアすると残像が消えてしまいます。代わりに半透明の塗りつぶしを使ってください。
// BAD -- kills trails
ctx.clearRect(0, 0, w, h);
// GOOD -- fades previous frame
ctx.fillStyle = 'rgba(0, 0, 0, 0.02)';
ctx.fillRect(0, 0, w, h);
2. アニメーションループ内で getImageData を呼び出さない
getImageData は GPU からデータを読み戻すため、非常に低速です。絶対に必要な場合(例:一度だけ行う色のサンプリング)を除き、使用しないでください。
// BAD -- 60fps GPU readback
function loop() {
const data = ctx.getImageData(0, 0, w, h); // blocks rendering pipeline
processPixels(data);
requestAnimationFrame(loop);
}
// GOOD -- sample once, cache
const colorMap = ctx.getImageData(0, 0, w, h);
function getColor(x, y) {
const i = (y * w + x) * 4;
return [colorMap.data[i], colorMap.data[i+1], colorMap.data[i+2]];
}
3. 鮮明さを保つため、必ず DPR を考慮する
DPR によるスケーリングを行わないキャンバスは、Retina ディスプレイでぼやけて表示されます。上記のセットアップセクションを参照してください。
// BAD
canvas.width = 800;
canvas.height = 600;
// GOOD
const dpr = window.devicePixelRatio || 1;
canvas.width = 800 * dpr;
canvas.height = 600 * dpr;
canvas.style.width = '800px';
canvas.style.height = '600px';
ctx.scale(dpr, dpr);
4. 処理負荷の高いループ内でメモリを確保しない
update() や render() 内では、new、オブジェクトスプレッド、配列の作成を行わないでください。すべて事前に確保してください。
// BAD
function update() {
particles.forEach(p => {
const force = { x: Math.cos(a), y: Math.sin(a) }; // new object every frame per particle
p.vx += force.x;
});
}
// GOOD
let fx = 0, fy = 0; // reuse
function update() {
for (let i = 0; i < aliveCount; i++) {
fx = Math.cos(a); fy = Math.sin(a);
particles[i].vx += fx;
}
}
クイックリファレンス:サブスキルの読み込み
| 必要なもの | 読み込むもの |
|---|---|
| ノイズの実装、パーティクルプール、フローフィールド、L システム、アトラクター | references/algorithms.md |
| アニメーション遷移のタイミングとイージング | ../motion-principles/SKILL.md |
| 3D ジェネレーティブアート(シェーダー、GPU パーティクル) | ../threejs-r3f/SKILL.md |