SOURCE

let artwork;
let colorArray;
let btnRegen;

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

function setup() {
	createCanvas(windowWidth, windowHeight);
	artwork = new Artwork();
	setupRegenButton();
	artwork.createNew(50, 10, 20);
}

function mousePressed(event) {
	btnRegen.onClick();
	return false;
}

function draw() {
	background(0, 0, 0);
	btnRegen.draw();
	artwork.display();
}

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

class Artwork {
	constructor() {
		this.rings = [];

		this.centerRadius;
		this.baseBandSize;

		this.rotationRate = 10;
		this.rotationIndex = 0;
	}

	createNew(numberOfRings, centerRadius, baseBandSize) {
		this.centerRadius = centerRadius;
		this.baseBandSize = baseBandSize;

		for (let i = 0; i < numberOfRings; i++) {
			this.rings.push(
				new Ring(
					i,
					i,
					this.baseBandSize,
					this.centerRadius,
					color(random(0, 256), random(0, 256), random(0, 256)),
					color(random(0, 256), random(0, 256), random(0, 256))
				)
			);
		}
	}

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

class Ring {
	constructor(
		id,
		layer,
		baseBandSize,
		centerRadius,
		ringTriangleColor,
		ringOuter
	) {
		this.id = id;
		this.layer = layer;
		this.baseBandSize = baseBandSize;
		this.centerRadius = centerRadius;
		this.ringTriangleColor = ringTriangleColor;
		this.ringOuter = ringOuter;
		this.innerRadius = this.centerRadius + this.layer * this.baseBandSize;
		this.outerRadius = this.innerRadius + this.baseBandSize;
		this.angle = 0;
		this.rateOfChange = 0.25;
		this.directionClockwise = this.layer % 2;

		this.inverseDensity = 3;
		this.maxRotations = 360 / this.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;
		for (let i = 0; i < this.maxRotations; i++) {
			this.tick[i] = 0;

			let r1 = random(this.innerRadius, this.outerRadius);
			let r2 = random(this.innerRadius, this.outerRadius);

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

	display() {
		noFill();
		strokeWeight(1);
		this.drawBands();
		this.drawTicTockBand();
	}

	drawBands() {
		stroke(this.ringOuter);
		ellipse(windowWidth / 2, windowHeight / 2, this.outerRadius * 2);
	}

	drawTicTockBand() {
		if (this.directionClockwise) {
			this.angle = (this.angle + this.rateOfChange) % 360;
		} else {
			this.angle = (this.angle - this.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.innerRadius);

		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.ticksTarget[index] = this.tickMin[index];
		}

		if (this.tick[index] <= this.tickMin[index]) {
			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(point1.x, point1.y, point2.x, point2.y);
	}

	findPointByAngleAndCircle(a, r) {
		let x = windowWidth / 2 + r * cos(degrees_to_radians(a));
		let y = windowHeight / 2 + r * sin(degrees_to_radians(a));

		return { x: x, y: y };
	}
}

function setupRegenButton() {
	btnRegen = new Button(this);
	btnRegen.textColor = color(0, 192, 0);
	btnRegen.color = color(64, 64, 64);
	btnRegen.y = 10;
	btnRegen.x = 10;
	btnRegen.width = 50;
	btnRegen.height = 30;
	btnRegen.text = "REGEN";
	btnRegen.rounding = 10;
	btnRegen.borderColor = color(256, 256, 0);
	btnRegen.borderWidth = 1;
	btnRegen.dropShadow = false;

	btnRegen.subscribe(() => setup());
}

class Drawable {
	constructor() {
		this.mouseEnterHandlers = [];
		this.mouseLeaveHandlers = [];
		this.x = 0;
		this.y = 0;
		this.height = 100;
		this.width = 100;
		this.borderWidth = 0;
		this.rounding = 0;
		this.dropShadow = false;
		this.dropShadowOffset = 5;
		this.color = undefined;
		this.borderColor = undefined;
		this.visible = true;
	}

	draw() {
		if (this.visible) {
			if (this.dropShadow) {
				fill(color(0, 0, 0, 128));
				rect(
					this.relativeX + this.dropShadowOffset,
					this.relativeY + this.dropShadowOffset,
					this.width,
					this.height,
					this.rounding
				);
			}

			fill(this.color);

			strokeWeight(this.borderWidth);
			if (this.borderColor) {
				stroke(this.borderColor);
			}

			rect(this.relativeX, this.relativeY, this.width, this.height, this.rounding);
			strokeWeight(0);

			this.onMouseEnter();
			this.onMouseLeave();
		}
	}

	get relativeX() {
		return this.container ? this.container.relativeX + this.x : this.x;
	}

	get relativeY() {
		return this.container ? this.container.relativeY + this.y : this.y;
	}

	mouseEnter() {
		if (
			mouseX > this.relativeX &&
			mouseX < this.relativeX + this.width &&
			mouseY > this.relativeY &&
			mouseY < this.relativeY + this.height
		) {
			return true;
		}
		return false;
	}

	subscribeMouseEnter(fn) {
		this.mouseEnterHandlers.push(fn);
	}

	unsubscribeMouseEnter(fn) {
		this.mouseEnterHandlers = this.handlers.filter(function (item) {
			if (item !== fn) {
				return item;
			}
		});
	}

	onMouseEnter() {
		this.mouseEnterHandlers.forEach((item) => {
			if (this.mouseEnter()) {
				item.call(this);
			}
		});
	}

	mouseLeave() {
		if (
			mouseX > this.relativeX &&
			mouseX < this.relativeX + this.width &&
			mouseY > this.relativeY &&
			mouseY < this.relativeY + this.height
		) {
			return false;
		}
		return true;
	}

	subscribeMouseLeave(fn) {
		this.mouseLeaveHandlers.push(fn);
	}

	unsubscribeMouseLeave(fn) {
		this.mouseLeaveHandlers = this.handlers.filter(function (item) {
			if (item !== fn) {
				return item;
			}
		});
	}

	onMouseLeave() {
		this.mouseLeaveHandlers.forEach((item) => {
			if (this.mouseLeave()) {
				item.call(this);
			}
		});
	}
}

class Clickable extends Drawable {
	constructor() {
		super();
		this.clickHandlers = [];
	}

	clicked() {
		if (
			mouseX > this.relativeX &&
			mouseX < this.relativeX + this.width &&
			mouseY > this.relativeY &&
			mouseY < this.relativeY + this.height
		) {
			return true;
		}
		return false;
	}

	subscribe(fn) {
		this.clickHandlers.push(fn);
	}

	unsubscribe(fn) {
		this.clickHandlers = this.handlers.filter(function (item) {
			if (item !== fn) {
				return item;
			}
		});
	}

	onClick() {
		this.clickHandlers.forEach((item) => {
			if (this.clicked() && this.visible) {
				item.call(this);
			}
		});
	}
}

class Button extends Clickable {
	constructor() {
		super();

		this.textSize = 8;

		this.text = "";
		this.textColor = undefined;
	}

	draw() {
		if (this.visible) {
			super.draw();
			if (this.textColor) {
				fill(this.textColor);
			}
			textSize(this.textSize);
			textAlign(CENTER, CENTER);

			text(this.text, this.relativeX, this.relativeY, this.width, this.height);
		}
	}
}
html,
body {
	padding: 0px;
	margin: 0px;
	overflow: hidden;
}
console 命令行工具 X clear

                    
>
console