画面下のパラメータを調整すると
花火が変わります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>p5.js Fireworks</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
body {
margin: 0;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
#hanabi {
width: 100%;
height: 600px;
position: relative;
background: black;
}
.control-panel {
width: 100%;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
font-family: Arial, sans-serif;
position: fixed;
bottom: 0;
left: 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
.control-group {
margin: 5px 10px;
text-align: center;
}
label {
display: block;
font-size: 14px;
}
input {
width: 60px;
padding: 5px;
text-align: center;
}
</style>
</head>
<body>
<div id="hanabi"></div>
<div class="control-panel">
<div class="control-group">
<label>発射間隔</label>
<input type="number" id="fireworkInterval" value="100" min="10" max="500">
</div>
<div class="control-group">
<label>発射速度</label>
<input type="number" id="launchSpeed" value="20" min="5" max="50">
</div>
<div class="control-group">
<label>花火の大きさ</label>
<input type="number" id="fireworkSize" value="10" min="5" max="30">
</div>
<div class="control-group">
<label>花火の粒数</label>
<input type="number" id="particleCount" value="50" min="10" max="200">
</div>
<div class="control-group">
<label>爆発持続</label>
<input type="number" id="explosionDuration" value="30" min="10" max="100">
</div>
</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
<script>
let canvas;
let fireworkList = [];
let starField = [];
let fireworkInterval = 100;
let launchSpeed = 20;
let fireworkSize = 10;
let particleCount = 50;
let explosionDuration = 30;
function windowResized() {
let hanabiDiv = document.getElementById("hanabi");
resizeCanvas(hanabiDiv.clientWidth, hanabiDiv.clientHeight);
generateStars();
}
function setup() {
let hanabiDiv = document.getElementById("hanabi");
canvas = createCanvas(hanabiDiv.clientWidth, hanabiDiv.clientHeight);
canvas.parent("hanabi");
colorMode(RGB);
frameRate(60);
generateStars();
document.getElementById('fireworkInterval').addEventListener('input', (e) => {
fireworkInterval = parseInt(e.target.value);
});
document.getElementById('launchSpeed').addEventListener('input', (e) => {
launchSpeed = parseInt(e.target.value);
});
document.getElementById('fireworkSize').addEventListener('input', (e) => {
fireworkSize = parseInt(e.target.value);
});
document.getElementById('particleCount').addEventListener('input', (e) => {
particleCount = parseInt(e.target.value);
});
document.getElementById('explosionDuration').addEventListener('input', (e) => {
explosionDuration = parseInt(e.target.value);
});
}
function draw() {
background(0);
renderStars();
if (frameCount % fireworkInterval === 0) {
fireworkList.push(new Firework(random(width), height, 0, launchSpeed, 0.98));
}
for (let i = fireworkList.length - 1; i >= 0; i--) {
fireworkList[i].update();
if (fireworkList[i].hasExploded()) {
fireworkList.splice(i, 1);
}
}
}
class Firework {
constructor(x, y, vx, vy, gravity) {
this.frameCount = 0;
this.stage = 0;
this.explosionDelay = 0;
this.colorR = random(80, 235);
this.colorG = random(80, 235);
this.colorB = random(80, 235);
this.opacity = 255;
this.x = x;
this.y = y;
this.size = fireworkSize;
this.targetHeight = random(height / 6, height / 2);
this.launchHeight = height - this.targetHeight;
this.vx = vx;
this.vy = vy;
this.gravity = gravity;
this.trail = [];
this.explosionParticles = [];
}
hasExploded() {
return this.stage === 2;
}
update() {
if (this.stage === 0) {
this.launch();
} else if (this.stage === 1) {
this.explode();
}
}
launch() {
this.y -= this.vy;
this.vy *= 0.98;
this.renderFirework(this.x, this.y, this.size);
if (this.y < this.targetHeight) {
this.stage = 1;
this.createExplosion();
}
}
createExplosion() {
for (let i = 0; i < particleCount; i++) {
let angle = random(TWO_PI);
let speed = random(1, 5);
let vx = cos(angle) * speed;
let vy = sin(angle) * speed;
this.explosionParticles.push(new Firework(this.x, this.y, vx, vy, 0.98));
}
}
explode() {
for (let i = this.explosionParticles.length - 1; i >= 0; i--) {
let p = this.explosionParticles[i];
p.x += p.vx;
p.y += p.vy;
p.vx *= 0.98;
p.vy *= 0.98;
p.vy += 0.02;
p.size -= 0.05;
p.opacity -= 4;
this.renderFirework(p.x, p.y, p.size, p.opacity);
if (p.opacity <= 0 || p.size <= 0) {
this.explosionParticles.splice(i, 1);
}
}
if (this.explosionParticles.length === 0) {
this.stage = 2;
}
}
renderFirework(x, y, size, opacity = this.opacity) {
let c = color(this.colorR, this.colorG, this.colorB);
c.setAlpha(opacity);
fill(c);
ellipse(x, y, size, size);
}
}
function generateStars() {
starField = [];
for (let i = 0; i < 100; i++) {
starField.push([random(width), random(height / 2), random(1, 4)]);
}
}
function renderStars() {
for (let s of starField) {
fill(color(200, 200, 255, random(150, 200)));
ellipse(s[0], s[1], s[2]);
}
}
</script>
</html>
1. HTML の構造
基本構造
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>p5.js Fireworks</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style> ... </style>
</head>
<body>
<div id="hanabi"></div> <!-- 花火を表示するエリア -->
<div class="control-panel"> ... </div> <!-- パラメータを調整するフォーム -->
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
<script> ... </script>
</html>
#hanabi
→ 花火を表示するエリア.control-panel
→ ユーザーが調整できるパラメータ入力フォームp5.js
を<script>
で読み込み、JavaScript で花火を描画
2. CSS (デザインとレイアウト)
花火の表示エリア
#hanabi {
width: 100%;
height: 600px;
position: relative;
background: black;
}
width: 100%; height: 600px;
→ 幅100%、高さ600pxbackground: black;
→ 背景を黒に設定(夜空っぽく)
フォームの配置
.control-panel {
width: 100%;
background: rgba(0, 0, 0, 0.8); /* 半透明の黒 */
color: white;
padding: 10px;
font-family: Arial, sans-serif;
position: fixed;
bottom: 0;
left: 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
position: fixed; bottom: 0;
→ フォームを画面下部に固定width: 100%;
→ フォームが横幅いっぱいに広がるbackground: rgba(0, 0, 0, 0.8);
→ 背景を半透明の黒display: flex; flex-wrap: wrap;
→ フォームの入力欄を横並びにし、折り返し可能に
3. JavaScript(p5.js のスケッチ)
p5.js を使って、花火の描画とパラメータ調整機能を実装。
① 変数の定義
let canvas;
let fireworkList = [];
let starField = [];
let fireworkInterval = 100;
let launchSpeed = 20;
let fireworkSize = 10;
let particleCount = 50;
let explosionDuration = 30;
canvas
→ p5.js のキャンバスオブジェクトfireworkList
→ 現在描画されている花火のリストstarField
→ 背景の星のデータfireworkInterval
→ 花火を打ち上げる間隔(フレーム数)launchSpeed
→ 花火の打ち上げ速度fireworkSize
→ 花火の大きさparticleCount
→ 爆発時の粒の数explosionDuration
→ 爆発の持続時間
② p5.js の setup()
関数
function setup() {
let hanabiDiv = document.getElementById("hanabi");
canvas = createCanvas(hanabiDiv.clientWidth, hanabiDiv.clientHeight);
canvas.parent("hanabi");
colorMode(RGB);
frameRate(60);
generateStars();
createCanvas(hanabiDiv.clientWidth, hanabiDiv.clientHeight);
#hanabi
の幅と高さに合わせてキャンバスを作成
canvas.parent("hanabi");
- キャンバスを
#hanabi
に埋め込む
- キャンバスを
colorMode(RGB);
- 色を RGB モードに設定
frameRate(60);
- 60FPS でアニメーションを描画
generateStars();
- 背景の星をランダムに生成
③ フォームの入力値を取得
document.getElementById('fireworkInterval').addEventListener('input', (e) => {
fireworkInterval = parseInt(e.target.value);
});
各入力欄の値が変わるたびに fireworkInterval
などの値を更新。
④ draw()
関数
function draw() {
background(0);
renderStars();
if (frameCount % fireworkInterval === 0) {
fireworkList.push(new Firework(random(width), height, 0, launchSpeed, 0.98));
}
for (let i = fireworkList.length - 1; i >= 0; i--) {
fireworkList[i].update();
if (fireworkList[i].hasExploded()) {
fireworkList.splice(i, 1);
}
}
}
background(0);
→ 背景を毎フレーム黒で塗りつぶしrenderStars();
→ 星を描画frameCount % fireworkInterval === 0
で、一定間隔ごとに花火を打ち上げ- 打ち上げた花火は
fireworkList
に追加 - 爆発後の花火は
fireworkList
から削除
⑤ Firework
クラス(花火の動作)
class Firework {
constructor(x, y, vx, vy, gravity) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.gravity = gravity;
this.stage = 0;
this.explosionParticles = [];
}
hasExploded() {
return this.stage === 2;
}
update() {
if (this.stage === 0) {
this.launch();
} else if (this.stage === 1) {
this.explode();
}
}
launch() {
this.y -= this.vy;
this.vy *= 0.98;
this.renderFirework(this.x, this.y, fireworkSize);
if (this.y < height / 2) {
this.stage = 1;
this.createExplosion();
}
}
createExplosion() {
for (let i = 0; i < particleCount; i++) {
let angle = random(TWO_PI);
let speed = random(1, 5);
let vx = cos(angle) * speed;
let vy = sin(angle) * speed;
this.explosionParticles.push(new Firework(this.x, this.y, vx, vy, 0.98));
}
}
explode() {
for (let i = this.explosionParticles.length - 1; i >= 0; i--) {
let p = this.explosionParticles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.02;
p.size -= 0.05;
this.renderFirework(p.x, p.y, p.size);
if (p.size <= 0) {
this.explosionParticles.splice(i, 1);
}
}
if (this.explosionParticles.length === 0) {
this.stage = 2;
}
}
renderFirework(x, y, size) {
fill(255, 100, 0);
ellipse(x, y, size, size);
}
}
launch()
→ 花火を上昇explode()
→ 爆発時に粒を拡散createExplosion()
→ 爆発時の粒を生成
便利な時代になりましたね。