SOURCE

let maxFireworkSize = 30;
let minBandSize = 1;
let maxBandSize = 15;
let maxNumberOfFireworks = 60;
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
	maxNumberOfFireworks = 30;
}

let fireworks = [];
let rateOfChange = 0.25;
let inverseDensity = 15;
let bandRateOfChange = 0.5;

function degrees_to_radians(degrees) {
	var pi = Math.PI;
	return degrees * (pi / 180);
}

setInterval(() => {
	if (fireworks.length < maxNumberOfFireworks) {
		let width = windowWidth / 2;
		fireworks.push(
			new abstractFirework(
				random(0, windowWidth),
				random(0, windowHeight),
				random(10, maxFireworkSize),
				maxFireworkSize * 250
			)
		);
	}
}, 2000);

function setup() {
	createCanvas(windowWidth, windowHeight);
	fireworks.length = 0;
}

function windowResized() {
	waitress = millis() + 2000;
	if (fullscreen()) {
		resizeCanvas(displayWidth, displayHeight);
	} else {
		resizeCanvas(windowWidth, windowHeight);
	}
	showing = true;
	setup();
}

function draw() {
	background(0, 0, 0);
	_.forEach(fireworks, (f) => f.display());
	_.remove(fireworks, (f) => f.removed);
}

class abstractFirework {
	constructor(x, y, size, til) {
		this.x = x;
		this.y = y;
		this.size = size;
		this.til = til;
		this.removed = false;
		this.rings = [];
		this.rings.length = 0;
		this.bornAt = Date.now();

		for (let i = 0; i < this.size; i++) {
			this.rings.push(
				new Ring(
					i,
					i,
					random(minBandSize, maxBandSize),
					color(random(0, 256), random(0, 256), random(0, 256)),
					this.x,
					this.y,
					this.rings,
					this.bornAt,
					this.til
				)
			);
		}

		_.forEach(this.rings, (r) => r.init());

		setTimeout(() => {
			this.removed = true;
		}, til);
	}

	display() {
		_.forEach(this.rings, (r) => r.display());
	}
}

class Ring {
	constructor(
		id,
		layer,
		baseBandSize,
		ringDetailColor,
		x,
		y,
		rings,
		bornAt,
		til
	) {
		this.id = id;
		this.layer = layer;
		this.currentBandSize = 0;
		this.ringDetailColor = ringDetailColor;
		this.angle = 0;
		this.x = x;
		this.y = y;
		this.rings = rings;
		this.bornAt = bornAt;
		this.til = til;
		this.directionClockwise = this.layer % 2;

		this.newColorTarget = color(
			random(0, 256),
			random(0, 256),
			random(0, 256),
			random(0, 256)
		);

		this.maxRotations = 360 / inverseDensity;
		this.angleFind = 360 / this.maxRotations;
		this.tick = [];
		this.tickMax = [];
		this.tickMin = [];
		this.ticksTarget = [];

		this.tick.length = this.maxRotations;
		this.tickMax.length = this.maxRotations;
		this.tickMin.length = this.maxRotations;
		this.ticksTarget.length = this.maxRotations;
		this.rateOfTickChange = 0.5;

		this.base = 0;
		for (let i = 0; i < this.layer; i++) {
			this.base += this.rings[i].currentBandSize;
		}
	}

	radius() {
		return this.base + this.currentBandSize;
	}

	init() {
		for (let i = 0; i < this.maxRotations; i++) {
			this.tick[i] = 0;

			let r1 = this.newTargetTock();
			let r2 = this.newTargetTock();
			this.tickMax[i] = r2 > r1 ? r2 : r1;
			this.tickMin[i] = r2 > r1 ? r1 : r2;
			this.ticksTarget[i] = 0;
		}

		this.targetBandSize = this.newTargetBandSize();
	}

	display() {
		noFill();
		strokeWeight(1);
		this.updateColor();
		stroke(this.ringDetailColor);
		this.updateBand();
		this.drawTicTockBand();
	}

	updateColor() {
		let updateColor = (current, target, index) => {
			if (current.levels[index] == target.levels[index]) {
				return 0;
			} else if (current.levels[index] <= target.levels[index]) {
				return 1;
			} else {
				return -1;
			}
		};

		let targetReached = (current, target) => {
			let result = true;
			for (let i = 0; i < 3; i++) {
				if (current.levels[i] != target.levels[i]) {
					result = false;
				}
			}

			return result;
		};

		for (let i = 0; i < 3; i++) {
			this.ringDetailColor.levels[i] += updateColor(
				this.ringDetailColor,
				this.newColorTarget,
				i
			);

			this.ringDetailColor._array[3] = 1 - (Date.now() - this.bornAt) / this.til;
		}

		if (targetReached(this.ringDetailColor, this.newColorTarget)) {
			this.newColorTarget = color(
				random(0, 256),
				random(0, 256),
				random(0, 256),
				256
			);
		}
	}

	updateBand() {
		if (this.currentBandSize <= this.targetBandSize) {
			this.currentBandSize += bandRateOfChange;

			if (this.currentBandSize >= this.targetBandSize) {
				this.targetBandSize = this.newTargetBandSize();
			}
		} else {
			this.currentBandSize -= bandRateOfChange;

			if (this.currentBandSize <= this.targetBandSize) {
				this.targetBandSize = this.newTargetBandSize();
			}
		}
	}

	newTargetBandSize() {
		return random(this.layer * minBandSize, (this.layer + 1) * maxBandSize);
	}

	getMaxTick() {
		let m1 = _.max(this.tickMax, (t) => t);
		let m2 = _.max(this.tickMin, (t) => t);

		return m1 > m2 ? m1 : m2;
	}

	getMinTick() {
		let m1 = _.min(this.tickMax, (t) => t);
		let m2 = _.min(this.tickMin, (t) => t);

		return m1 > m2 ? m2 : m1;
	}

	drawTicTockBand() {
		if (this.directionClockwise) {
			this.angle = (this.angle + rateOfChange) % 360;
		} else {
			this.angle = (this.angle - rateOfChange) % 360;
		}

		for (let i = 0; i < this.maxRotations; i++) {
			this.drawTick((this.angle + i * this.angleFind) % 360, this.angleFind, i);
		}
	}

	drawTick(topAngle, angleFind, index) {
		let point1 = this.findPointByAngleAndCircle(topAngle, this.radius());

		if (this.ticksTarget[index] == 0) {
			this.tick[index] = this.tickMin[index];
			this.ticksTarget[index] = this.tickMax[index];
		}

		if (this.tick[index] >= this.tickMax[index]) {
			this.tickMin[index] = this.newTargetTock();
			this.ticksTarget[index] = this.tickMin[index];
		}

		if (this.tick[index] <= this.tickMin[index]) {
			this.tickMax[index] = this.newTargetTock();
			this.ticksTarget[index] = this.tickMax[index];
		}

		if (this.ticksTarget[index] > this.tick[index]) {
			this.tick[index] += this.rateOfTickChange;
		} else {
			this.tick[index] -= this.rateOfTickChange;
		}

		let point2 = this.findPointByAngleAndCircle(topAngle, this.tick[index]);

		line(point2.x, point2.y, point1.x, point1.y);
	}

	newTargetTock() {
		return random(
			this.currentBandSize + minBandSize,
			this.currentBandSize + maxBandSize
		);
	}

	findPointByAngleAndCircle(a, r) {
		let x = this.x + r * cos(degrees_to_radians(a));
		let y = this.y + r * sin(degrees_to_radians(a));

		return { x: x, y: y };
	}
}
html,
body {
	padding: 0px;
	margin: 0px;
	overflow: hidden;
}
console 命令行工具 X clear

                    
>
console