我們將保持程式碼短而簡單,因此我們的煙花看起來不會很花哨或不現實 - 實際上,它們將與您在二十年前在Geocities上看到的一樣。但它仍然是一個很好的編碼練習。
主程式[第5-31行]首先宣告變數,然後建立一個煙花物件的陣列。每個煙花都有自己的x和y座標,年齡(自爆炸以來經過的時間),階段(飛行/爆炸)和一系列火花。
每個火花依次具有自己的水平(vx)和垂直(vy)速度,重量決定了它的下降速度和顏色(由紅色,綠色和藍色成分決定)。
在[31]中,我們啟動了主要的動畫功能 - 爆炸[40-75]。此功能更新並繪製每個煙花的每個火花。
[44-62]是爆炸階段。在這個階段,每個火花都有10個矩形的蹤跡。我們根據煙花的座標,火花的速度,爆炸的“年齡”和矩形的索引來計算每個矩形[45-48]的位置。我沒有使用任何彈道公式,相反,我透過反覆試驗得出了這個古怪的[48]。
[49-52]計算每個矩形的顏色,考慮到衰減因子,它是從爆炸的“年齡”得出的 - 爆炸越老,顏色越深。
[53-56]繪製矩形
在[60-61]中,如果爆炸超過100幀,則有5%的機會重置煙花。
重置功能[33-37]將煙花放回螢幕底部的隨機位置,“飛行”階段再次開始。
在這個階段[64-71],煙花在螢幕上移動[64]並且繪製了極其基本的火花尾[65-69]。
在給定的框架中,煙花爆炸的可能性為0.001(相位變化)[71]。 一旦y座標達到200,它肯定會爆炸。
程式碼如下:
<html>
<body style='background-color:black'>
<canvas id='myCanvas' width='800' height='800'></canvas>
<script>
const max_fireworks = 5,
max_sparks = 50;
let canvas = document.getElementById('myCanvas');
let context = canvas.getContext('2d');
let fireworks = [];
for (let i = 0; i < max_fireworks; i++) {
let firework = {
sparks: []
};
for (let n = 0; n < max_sparks; n++) {
let spark = {
vx: Math.random() * 5 + .5,
vy: Math.random() * 5 + .5,
weight: Math.random() * .3 + .03,
red: Math.floor(Math.random() * 2),
green: Math.floor(Math.random() * 2),
blue: Math.floor(Math.random() * 2)
};
if (Math.random() > .5) spark.vx = -spark.vx;
if (Math.random() > .5) spark.vy = -spark.vy;
firework.sparks.push(spark);
}
fireworks.push(firework);
resetFirework(firework);
}
window.requestAnimationFrame(explode);
function resetFirework(firework) {
firework.x = Math.floor(Math.random() * canvas.width);
firework.y = canvas.height;
firework.age = 0;
firework.phase = 'fly';
}
function explode() {
context.clearRect(0, 0, canvas.width, canvas.height);
fireworks.forEach((firework,index) => {
if (firework.phase == 'explode') {
firework.sparks.forEach((spark) => {
for (let i = 0; i < 10; i++) {
let trailAge = firework.age + i;
let x = firework.x + spark.vx * trailAge;
let y = firework.y + spark.vy * trailAge + spark.weight * trailAge * spark.weight * trailAge;
let fade = i * 20 - firework.age * 2;
let r = Math.floor(spark.red * fade);
let g = Math.floor(spark.green * fade);
let b = Math.floor(spark.blue * fade);
context.beginPath();
context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',1)';
context.rect(x, y, 4, 4);
context.fill();
}
});
firework.age++;
if (firework.age > 100 && Math.random() < .05) {
resetFirework(firework);
}
} else {
firework.y = firework.y - 10;
for (let spark = 0; spark < 15; spark++) {
context.beginPath();
context.fillStyle = 'rgba(' + index * 50 + ',' + spark * 17 + ',0,1)';
context.rect(firework.x + Math.random() * spark - spark / 2, firework.y + spark * 4, 4, 4);
context.fill();
}
if (Math.random() < .001 || firework.y < 200) firework.phase = 'explode';
}
});
window.requestAnimationFrame(explode);
}
</script>
</body>
</html>