そのWeb演出、Claude Codeで作れます。クリエイティブWeb表現のプロンプト集 #05 Living Patterns
株式会社グルーヴサインの米沢です。
AIコーディングでクリエイティブなWeb表現を実際に作り、そのプロンプトごと公開していく「PROMPT → WEB」の第5回です。
PROMPT → WEB #005
Living Patterns
INTRODUCTION
ここまでの4回には、共通していたことがあります。描く対象があった、ということです。粒子(#01)、文字(#02)、線(#03)、物体(#04)。形はこちらが用意し、それを動かしていました。
今回はその前提を外します。描く対象がありません。あるのは、画面を細かい格子に区切った2種類の物質の濃度だけです。それぞれのマスで「にじむ」「反応する」という単純なルールを毎フレーム計算します。すると、誰も設計していないのに模様が勝手に生まれ、育ち、増える。
これは反応拡散系(Reaction–Diffusion)と呼ばれるもので、提唱したのはアラン・チューリングです。計算機の父として知られる人が晩年に取り組んだのは、「なぜ生き物にはあの模様があるのか」という問いでした。貝殻の縞、熱帯魚の斑点、豹の模様。設計図に描かれているのではなく、単純な化学反応から自然に生じているというのが彼の答えでした。
今回の仕掛けは、その計算をブラウザで毎秒何百万回と回すことです。題材は理科室の標本プレートにしました。なぞると養分が落ちて模様が育ち、クリックすると同じ数式のまま別の生き物に変わります。
RESULT
まず現物です。下のボタンを押してみてください。
クリックすると、この画面の上で Living Patterns が再生されます(何度でも再生可能)。ESC または CLOSE で終了します。
温かいボーン色のプレートに、赤錆色の珊瑚のような模様が広がっていきます。操作は2つです。
- カーソルを動かす(スマホは画面をなぞる) ── なぞった跡に養分が落ち、そこから新しい模様が育ちはじめます。字を書けば、その字が数秒かけて生き物に侵食されていきます。
- クリックする(スマホはタップ) ── 別の標本に切り替わります。珊瑚 → 繊維 → 迷路 → 細胞。画面をリセットするのではなく、いま生きている模様がそのまま別の種類へ変態していきます。
左下の標本ラベルに、いま何を見ているのか(feed / kill / 拡散比・世代数)が出ています。ESC または CLOSE で終了します。
動画でもGIFでもありません。あなたの画面の中で、いま計算されている化学反応です。同じ模様は二度と出ません。
WHAT WE MADE
技術的に分解すると、次のようになります。
- 描画はCanvas 2D、主役は計算 ── 図形を1つも描いていません。全ピクセルの色は、そのマスの濃度から決まります。
- 2つの濃度場 ── 物質AとBの濃度を
Float32Arrayで2枚ずつ(現在と次のフレーム)持ち、毎回入れ替えます。 - 1フレームに6回の反応 ── 1回では変化が遅いので、1フレームあたり6回まとめて時間を進めています。
- 濃度 → 色の変換表 ── 濃度から色を計算するのではなく、256段階の参照表を先に作っておき、毎ピクセルは表を引くだけにしています。
- 計算解像度と画面解像度の分離 ── 計算は横420マス前後の低解像度で行い、それを画面いっぱいに拡大して表示します。4Kでも計算量は変わりません(後述)。
- 4つの標本 ── feed / kill / 拡散比という3つのつまみの組み合わせを4種類用意しました。同じ数式のまま、まったく違う生き物になります。
- 静止画フォールバック ── アニメーションを好まない設定の環境では、十分に育てた状態を1枚だけ描いて静止させます。
HOW IT WORKS
1. 式は、たった2行
物質AとBがあり、次の反応が起きるとします。
A + 2B → 3B Bは、Aを食べて自分を増やす(自己触媒)
B → 除去 Bは一定の割合で取り除かれる
A → 供給 Aは外から一定の割合で補充される
これを各マスの濃度 a, b について式にすると、コードはこれだけです。
const abb = a * b * b; // A + 2B → 3B
na = a + (DA * lapA - abb + feed * (1 - a)) * dt; // Aは減り、外から補充される
nb = b + (DB * lapB + abb - (feed + kill) * b) * dt; // Bは増え、一定割合で消える
feed(供給率)と kill(除去率)が、この世界の法則です。この2つの数字だけで、生まれる模様が決まります。
2. 「にじみ」を計算する
上の lapA / lapB はラプラシアンといって、要はにじみです。周囲のマスと比べて自分が濃ければ薄くなり、薄ければ濃くなる。そのにじみ具合を、周囲8マスの重み付き和で求めます。
const lapB =
b[u-1]*0.05 + b[u]*0.2 + b[u+1]*0.05 +
b[i-1]*0.2 - b[i] + b[i+1]*0.2 +
b[d-1]*0.05 + b[d]*0.2 + b[d+1]*0.05;
上下左右だけ(4マス)でも動きますが、その場合は模様が縦横の方向に偏ります。斜めを 0.05 で加えると、格子の存在を感じさせない自然な模様になります。画面が四角いことを模様に悟らせないための一手間です。
3. 同じ式のまま、別の生き物になる
面白いのはここからです。AとBのにじむ速さの比(拡散比)も効きます。Bのにじみが遅いほど、模様は細かくなります。
デモの「珊瑚」と「繊維」は、feed も kill もまったく同じ数字です。違うのは拡散比(0.50 と 0.30)だけ。それだけで、枝分かれする珊瑚は、目の細かいレース状の組織に変わります。生き物の模様の多様さが、設計図の量ではなくわずかな条件の差から来ていることが、画面の上でそのまま見えます。
切り替えのとき、パラメータは一瞬で飛ばさず、40フレームほどかけて目標値へ寄せています。こうすると画面がリセットされず、いまいる生き物がゆっくり別の生き物に変態していくように見えます。
4. 計算解像度を、画面から切り離す
素直に実装すると「1ピクセル=1マス」にしたくなりますが、これをやると4Kディスプレイで200万マスを毎フレーム6回計算することになり、即座に破綻します。
そこで計算用のCanvasは横420マス前後に固定し、それを表示用Canvasへ拡大して描いています。
sctx.putImageData(img, 0, 0); // 低解像度で計算結果を書く
vctx.imageSmoothingEnabled = true;
vctx.drawImage(sim, 1, 1, w-2, h-2, 0, 0, W, H); // 画面いっぱいに拡大
拡大時のなめらかな補間が、そのままインクのにじみのような質感になります。粗さが、味方になっている部分です。ついでに外周1マス(計算していない縁)も、この時に切り落としています。
5. 開幕で固まらせない「助走」
模様が見応えのある状態になるには1000回以上の反応が要りますが、それを起動時に一括で回すと1秒以上画面が固まります。かといって最初から毎フレーム6回だと、豊かになるまで待たされます。
そこで最初の42フレームだけ18回、その後は6回に落としています。起動直後は少し重くなりますが、模様が一気に育つ様子そのものが見どころになるので、演出としても都合が良い。性能上の妥協が、そのまま演出になった例です。
THE PROMPT
ここが本題です。上のデモを作るためにClaude Codeへ渡せる、汎用プロンプト全文を掲載します。右上の COPY ボタンでそのままコピーできます。色やラベルを置き換えれば、自分用になります。
あなたはCreative Codingと数値シミュレーションに精通したフロントエンドエンジニアです。
「Living Patterns」= 図形も文字も描かず、画面そのものを反応拡散系(Gray-Scott)として
1ピクセルずつ計算し、模様が自分から育っていくWeb表現を、Canvas 2D で実装してください。
(シミュレーションのライブラリは使わない。数式は自前で書く)
# 置き換える変数
- BG_COLOR = "#e9e3d5" // 温かいボーン色。理科室の標本プレートの地
- PRESETS = 4種類。それぞれ feed / kill / 拡散比 と配色を持つ:
珊瑚 CORAL f=0.0545 k=0.0620 拡散比0.50 … 赤錆 "#c2543a" / 芯 "#2a1a12"
繊維 LACE f=0.0545 k=0.0620 拡散比0.30 … 緑青 "#2f8073" / 芯 "#10201d"
迷路 MAZE f=0.0290 k=0.0570 拡散比0.50 … 藍 "#4d4a86" / 芯 "#16142a"
細胞 CELLS f=0.0390 k=0.0580 拡散比0.50 … 黄土 "#b8842a" / 芯 "#24190e"
- LABEL = "LIVING PATTERNS"
# シミュレーション(Gray-Scott 反応拡散)
- 物質A,Bの濃度を Float32Array で2枚ずつ持ち、毎ステップ入れ替える(ピンポン)
- 各マスの更新式:
abb = a*b*b
a' = a + (DA*lapA - abb + feed*(1-a)) * dt
b' = b + (DB*lapB + abb - (feed+kill)*b) * dt
DA=1.0 / DB=拡散比 / dt=1.0
- lap は9点ラプラシアン(上下左右0.2・斜め0.05・中心-1)。斜めを入れて方向の偏りを消す
- 外周1マスは計算せず、描画時に切り落とす(境界条件の分岐をループから追い出す)
- 初期状態: a=1, b=0 に、小さな円形の種を全体に散らす
- 1フレームに複数回ステップを進める(既定6回)。1回では変化が遅すぎる
# 描画
- 計算用の低解像度Canvasと、表示用の全画面Canvasを分ける
- 計算グリッドは横 clamp(innerWidth/3.2, 280, 440) マスに固定する。
【重要】画面のピクセル数に比例させない。4Kでも計算量を一定に保つため
- 濃度b → 色は、256段階の参照表(Uint32Array)を先に作り、毎ピクセルは表を引くだけにする
ランプは [地の色 → 淡い色 → 発色(縁)→ 芯の濃色] の4点を補間して作る
- ImageData の buffer を Uint32Array として直接書き、putImageData → drawImage で拡大する
- 拡大は imageSmoothingEnabled = true(補間のぼけを、インクのにじみとして使う)
- 隅に標本ラベル(明朝系)で「標本 NN / 名称 / f・k・拡散比・世代数」を小さく置く
- ネオン・rainbow・紫グラデ・glassmorphism・黒背景は使わない(生物図鑑の落ち着いた質感にする)
# インタラクション
- ポインタを動かすと、その軌跡に沿って円形にBを注入する(なぞると模様が育つ)
前フレームとの間を補間して打つ。速く動かしても線が途切れないように
- クリック/タップで次のプリセットへ。【重要】画面をリセットしないこと。
feed/kill/拡散比を40フレームほどかけて目標値へ補間し、配色も同時に送る。
いまの模様が別の模様へ「変態」して見えるようにする
- Bが尽きて模様が死滅した場合のみ、静かに種を蒔き直す(常時監視はしない。数十フレームに1回で十分)
# パラメータ選定の注意
- feed/kill には「模様が画面を埋める領域」と「まばらな点で終わる領域」がある。
有名な値でも画面が寂しくなるものは採用しない。候補を複数試し、
一定濃度を超えたマスの割合(被覆率)が40%以上あるものだけを選ぶこと
- 4つのプリセットは、色ではなく「構造」で違いが出るように選ぶ
(枝分かれ/細かい網目/迷路/細胞の粒 のように)
# 品質要件(production)
- 全画面オーバーレイ。CLOSE ボタンと ESC で終了。多重起動を防ぐ
- レスポンシブ: リサイズでグリッドを組み直す。devicePixelRatio は最大2でクランプ
- モバイル: 計算グリッドを狭く(200〜300)、1フレームのステップ数も減らす。タッチ操作に対応
- prefers-reduced-motion: アニメーションせず、十分に育てた状態を1枚だけ描いて静止させる
- 起動時に一括で大量ステップを回して固まらせない。最初の数十フレームだけステップ数を増やす
- 終了時に requestAnimationFrame を停止し、全リスナを解除し、CanvasをDOMから除去する
(何度開閉してもリークしないこと)
- Canvas 2D が使えない環境では静かに何もしない。本文の閲覧を妨げない
CUSTOMIZE
- 模様の種類 ──
feedとkill。この2つが世界の法則です。ただし後述のとおり、美しい値の範囲は意外と狭いので、数値をいじるより「良い値の候補」を複数試して選ぶほうが早道です。 - 模様の細かさ ── 拡散比。小さくするほど目が細かくなります。
feed/killを変えずにここだけ動かすと、同じ生き物の「解像度」が変わったように見えます。 - 育つ速さ ── 1フレームあたりのステップ数。増やせば早く育ちますが、その分だけ重くなります。
- 配色 ── 4点のランプ(地・淡色・発色・芯)を差し替えれば、標本プレートにも、藍染にも、金属の腐食にもなります。地の色を暗くすると、一気に顕微鏡写真の雰囲気になります。
- 筆の太さ ── なぞったときに注入する円の半径。太くすると絵の具、細くするとペンのような描き味です。
- 静かにする ── ステップを止めて1枚だけ描けば、生成された模様がそのまま壁紙やOGP画像になります。
PERFORMANCE
全ピクセルを毎フレーム何度も計算するので、今回は素直に重い題材です。実測しながら決めました。
- 1フレームの実測値 ── 420×236マス(約10万マス)を6回ステップ+着色で、1フレームあたり約7.5ms(Node上で同じ計算を実測)。60fpsの予算16.7msに収まっています。
- 計算量を画面から切り離す ── 前述のとおり、計算グリッドは横280〜440マスで頭打ちにしています。これをやらないと、大画面ほど破綻します。
- 色は計算せず、引く ── 濃度→色を毎ピクセル計算するとRGB3回分の補間が10万回走ります。256段階の参照表にすれば、毎ピクセルは配列アクセス1回です。
- 境界の分岐をループから追い出す ── 端のマスで「はみ出したら折り返す」判定を入れると、10万回のループ全部にその判定が乗ります。外周1マスを計算対象から外し、描画時に切り落とすほうが、速くて簡単です。
- モバイル ── 計算グリッドを200〜300マスに、ステップ数を5回に落とします。模様の細かさは変わりますが、動きの気持ちよさは保てます。
- 静止画フォールバック ──
prefers-reduced-motionでは1600回まとめて計算し、1枚だけ描いて終わります。アニメーションしなくても作品として成立する絵になるのが、この題材の良いところです。 - 後始末 ── 終了時にRAFを停止し、全リスナを解除してCanvasを除去。何度開閉してもリークしません。
WHAT AI DID / WHAT HUMAN DIRECTION MATTERS
Gray-Scottの式そのものは、教科書にもAIの頭の中にも正確に入っています。「Canvasで反応拡散を実装して」と言えば、動くものはすぐ出てきます。 ラプラシアンの重みも、ピンポンバッファも、参照表による高速化も、AIは淀みなく書きます。
問題はその後でした。最初に出てきた実装は、正しく動いているのに美しくありませんでした。
原因はパラメータです。反応拡散でよく紹介される有名な値(例えば「細胞分裂」と呼ばれる f=0.0367 / k=0.0649)を入れると、確かに教科書どおりの挙動になります。しかし画面いっぱいで見ると、まばらな点が寂しく散らばっているだけでした。理科的な正しさと、作品としての強さは別物だったわけです。
そこで、9種類の候補を同じ条件で3000ステップ回し、一定濃度を超えたマスの割合(被覆率)を数値で比べました。 結果、有名な値ほど疎(8〜15%)で、採用したのは40%を超えたものだけです。「なんとなく綺麗そう」ではなく計測で決めた、という点では#003・#004と同じやり方をしています。
人間の仕事として残ったのは、次のような判断でした。
- そもそも「描かない」表現を選ぶこと
- 美しくない有名パラメータを、実測して捨てること
- 4種類を色ではなく構造の違いで選ぶこと(枝分かれ/細かい網目/迷路/粒)
- 切り替えを「リセット」ではなく「変態」にすること
- 同じ feed / kill のまま拡散比だけを変えた2種類を並べるという、見せ方の設計
最後の1つは、技術的にはパラメータを1つ変えただけです。しかし「同じ法則から、こんなに違う生き物が生まれる」というこの表現の主題は、この並びがないと伝わりません。何を作るかはAIが実装できても、何を並べれば伝わるかは、まだ人が決めることです。
5回続けて、結論は同じところに落ち着いています。実装のコストは劇的に下がりました。下がった分だけ、選ぶことと捨てることに時間を使えるようになった、というのが実感です。
NEXT
粒子(WebGL)、文字(Canvas 2D)、線(SVG)、物理(自作エンジン)、そして今回の計算そのもの。「描く」から「育てる」へ、一歩進んだ回でした。
次回はまた別の仕掛け ── 画像処理(ハーフトーン)、音(Web Audio)、スクロール連動あたりを考えています。
今回のプロンプトも丸ごとコピーして使えます。色を自社のブランドカラーに置き換えて、Claude Codeに渡してみてください。しばらく画面を眺めてしまうはずです。誰も描いていない模様が育っていくのを見るのは、思いのほか飽きません。
レガシーPHPシステムの保守・引き継ぎのご相談を受け付けています。
初回のご相談・ヒアリングは無料です。