p5.jsでアイスクリームアニメ!
ツンデレ先生と学ぶコード作成の裏側
目次
登場人物紹介
- 👩🏫【GPT先生】…ちょっとツンデレ、でもコード初心者にめちゃくちゃ優しい女性エンジニア。
- 🧑💻【生徒】…AIにコードを書かせるのが好きな楽したがり。でも動くとテンション爆上がり。
ツンデレ先生とのコード作成会話ログ
🧑💻
生徒:
アイスクリームをテーマに、すごいアニメーションp5.jsで作って。HTMLとJS全部出して。
👩🏫
GPT先生:
ふっ、アンタ…いきなりハードル高いこと頼むじゃない。…べ、別に嬉しいわけじゃないけど、やるわよ!
まずアイスクリームって言ったら、ふんわりパステルカラーと、ドリップ、トッピングよね!p5.jsでそれを再現してあげる。
…あ、初心者でも動かせるように、HTMLとJSは分けて、しかもロードを早くする構成にするから覚悟しなさい。
🧑💻
生徒:
うおっ、ちゃんと考えてくれてる。面倒なことは全部任せた!
👩🏫
GPT先生:
まったく…しょうがないわね…!
とにかく、今回はCSSで背景を描画して軽くして、`noLoop()`でアニメを止めておく戦法よ。
じゃ、`index.html`からいくわよ!
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>アイスクリームアニメーション</title>
<link rel="preload" href="https://cdn.jsdelivr.net/npm/p5@1.9.2/lib/p5.min.js" as="script" crossorigin>
<script src="https://cdn.jsdelivr.net/npm/p5@1.9.2/lib/p5.min.js" defer crossorigin></script>
<script src="sketch.js" defer></script>
<style>
html, body { margin: 0; padding: 0; overflow: hidden; }
body {
background: linear-gradient(to bottom, #b6eaff 0%, #ffd5ed 100%);
}
canvas { display: block; }
</style>
</head>
<body></body>
</html>
🧑💻
生徒:
なんか簡単そうに見える!次は?
👩🏫
GPT先生:
フフ、あんたが理解しやすいようにしてるだけよ!
次は `sketch.js`! ちゃんと分けてあげてるんだから感謝しなさいよ?
sketch.js
/* Ice‑Cream‑Themed p5.js Sketch ── 軽量ロード版 ─────────────────── */
/* ========= グローバル定数・変数 ========= */
let coneH, scoopR; // 動的ジオメトリ
let scoops = [], drips = [], sprinkles = [];
const MAX_SCOOPS = 8;
const flavours = ["#FFF5D6","#FFB7CF","#C1F0C2","#CAA472","#B3C6FF","#FFE391"];
/* ========= ウィンドウ依存の寸法を一括計算 ========= */
function computeGeometry(){
const minDim = Math.min(windowWidth, windowHeight);
coneH = minDim * 0.28; // コーン高さ
scoopR = minDim * 0.12; // スクープ半径
}
/* ========= p5 基本フック ========= */
function setup(){
createCanvas(windowWidth, windowHeight);
angleMode(RADIANS);
computeGeometry();
noLoop(); // ★ 初期ロード時は描画停止
}
function windowResized(){
resizeCanvas(windowWidth, windowHeight);
computeGeometry();
}
/* ユーザー操作が来るまでは描画しない → タップで loop() 再開 */
function mousePressed(){
if (!isLooping()) loop(); // ★ 初回のみ描画ループを起動
if (scoops.length >= MAX_SCOOPS) return;
const margin = scoopR * 0.1; // コーン先端とスクープの “隙間”
const firstY = height - coneH - scoopR + margin;
const yBase = firstY - scoops.length * scoopR * 1.6;
const newScoop = new Scoop(createVector(width/2, yBase), scoopR);
scoops.push(newScoop);
burstSprinkles(newScoop);
}
function draw(){
/* 背景は CSS で塗っているのでここでは不要 */
drawCone();
for (let s of scoops){ s.update(); s.display(); }
for (let i = drips.length-1; i >= 0; i--){
drips[i].update(); drips[i].display();
if (drips[i].offscreen()) drips.splice(i,1);
}
for (let i = sprinkles.length-1; i >= 0; i--){
sprinkles[i].update(); sprinkles[i].display();
if (sprinkles[i].finished()) sprinkles.splice(i,1);
}
}
/* ========= 描画ユーティリティ ========= */
function drawCone(){
const coneW = coneH * 0.6, cx = width/2, cy = height - 10;
push();
translate(0, map(mouseY,0,height,-10,10)); // パララックス
fill(228,187,118);
stroke(205,164,101,120); strokeWeight(1);
beginShape();
vertex(cx-coneW/2, cy-coneH);
vertex(cx+coneW/2, cy-coneH);
vertex(cx, cy);
endShape(CLOSE);
const step = coneW/10;
for (let i=-coneH;i<0;i+=step) line(cx-coneW/2,cy+i,cx+coneW/2,cy+i);
rotate(-PI/6);
for (let i=-coneH;i height; }
}
class Sprinkle{
constructor(pos,col){
this.pos=pos.copy();
this.vel=p5.Vector.random2D().mult(random(1,3));
this.life=60; this.col=col; this.angle=random(TWO_PI);
}
update(){ this.pos.add(this.vel); this.vel.mult(0.95); this.life--; this.angle+=0.2; }
display(){ push(); translate(this.pos.x,this.pos.y); rotate(this.angle); fill(this.col); rectMode(CENTER); rect(0,0,4,1.5); pop(); }
finished(){ return this.life<=0; }
}
/* ========= ヘルパー関数 ========= */
function burstSprinkles(scoop){
for(let i=0;i<40;i++){
sprinkles.push(new Sprinkle(scoop.pos, color(random(flavours))));
}
}
function easeOutElastic(t){
const c4=(2*Math.PI)/3;
return t===0?0:t===1?1:Math.pow(2,-10*t)*Math.sin((t*10-0.75)*c4)+1;
}
🧑💻
生徒:
ふおお〜!背景ふわっと出てきて、クリックしたら動いた!最高じゃん!!
👩🏫
GPT先生:
べ、別に褒められて嬉しいわけじゃないけど…!
でも、これがロード高速化 + 初回静止 + ユーザー操作起動の三段構えよ!覚えておきなさい!
よくある質問(FAQ)
Q1. なぜ `noLoop()` を使うの?
ロード時に何も描画しないことで、CPU負荷を極限まで減らして、最初の表示速度を早く見せるためです。特にモバイル端末では効果絶大です。
Q2. CSSで背景を描くとどう違うの?
JavaScriptで背景を毎フレーム描くと描画負荷がかかりますが、CSSであれば1回だけブラウザに任せて描画されるので、CPUにもバッテリーにも優しいです。
Q3. 最初のクリックでしか動かないのはなぜ?
ユーザーの操作があってからアニメーションを開始することで、UX(体験)が良くなると同時に、デバイスによる無駄な処理を省けます。
Q4. 色や形を変えるにはどうすればいい?
flavours配列の色コードを好きなものに変更したり、`ellipse`を`rect`にしたりすれば、簡単にアートスタイルを変更できます。
Q5. 音を鳴らしたり、ボタンを追加することはできる?
もちろん可能です。p5.soundライブラリを追加すればクリック時に効果音を鳴らしたり、DOM要素を使ってボタンやスライダーを作ることもできます。
Q6. 複数のアイスを表示したい!
スクープのスタック上限`MAX_SCOOPS`を増やすだけでOK。さらに複数のコーンを配置したい場合は、`drawCone()`と`scoops`配列を分けて管理すると実装できます。
Q7. 画像として保存することはできる?
できます!p5.jsでは`saveCanvas()`を使えば、ワンクリックでキャンバスを画像として保存可能です。ボタンと連携させればすぐ使えます。
Q8. このコードはスマホでも動きますか?
はい。p5.jsはスマホ対応しており、今回のコードもタップによる操作で動くように設計されています。
Q9. Webアプリとして公開するには?
作成したHTMLとJSファイルをGitHub PagesやNetlify、Vercelにアップロードするだけで、誰でもアクセスできるWebアプリとして公開可能です。
コメント