237 lines
4.9 KiB
JavaScript
237 lines
4.9 KiB
JavaScript
|
import { SVG } from "https://cdn.skypack.dev/@svgdotjs/svg.js";
|
||
|
|
||
|
function random(min, max, clamp = false) {
|
||
|
const value = Math.random() * (max - min) + min;
|
||
|
|
||
|
return clamp ? Math.round(value) : value;
|
||
|
}
|
||
|
|
||
|
class BauBudEyes {}
|
||
|
|
||
|
class BauBudPatternGenerator {
|
||
|
constructor(svg) {
|
||
|
this.svg = svg;
|
||
|
this.types = ["dots", "rects"];
|
||
|
}
|
||
|
|
||
|
generate(fill) {
|
||
|
this.type = this.types[random(0, this.types.length, true)];
|
||
|
|
||
|
return this.svg.pattern(4, 4, (add) => {
|
||
|
if (this.type === "rects") {
|
||
|
add.rect(2, 2).cx(0).cy(0).fill(fill);
|
||
|
} else {
|
||
|
add.circle(1).cx(2).cy(2).fill(fill);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class BauBudBody {
|
||
|
constructor(svg, x, y, width, height) {
|
||
|
this.svg = svg.group();
|
||
|
this.x = x;
|
||
|
this.y = y;
|
||
|
this.width = width;
|
||
|
this.height = height;
|
||
|
|
||
|
this.types = [
|
||
|
this.circle,
|
||
|
this.rect,
|
||
|
this.equilateralTriangle,
|
||
|
this.rightAngleTriangle,
|
||
|
this.semiCircle
|
||
|
];
|
||
|
|
||
|
this.patternGenerator = new BauBudPatternGenerator(this.svg);
|
||
|
}
|
||
|
|
||
|
circle() {
|
||
|
this.type = "circle";
|
||
|
|
||
|
return this.svg
|
||
|
.circle(Math.max(this.width, this.height))
|
||
|
.cx(this.x)
|
||
|
.cy(this.height);
|
||
|
}
|
||
|
|
||
|
rect() {
|
||
|
this.type = "rect";
|
||
|
|
||
|
return this.svg.rect(this.width, this.height).cx(this.x).cy(this.y);
|
||
|
}
|
||
|
|
||
|
equilateralTriangle() {
|
||
|
this.type = "equilateralTriangle";
|
||
|
|
||
|
return this.svg
|
||
|
.path(
|
||
|
`
|
||
|
M ${this.x} ${this.y - this.height / 2}
|
||
|
L ${this.x + this.width / 2} ${this.y + this.height / 2}
|
||
|
L ${this.x - this.width / 2} ${this.y + this.height / 2}
|
||
|
Z
|
||
|
`
|
||
|
)
|
||
|
.rotate(this.randomRotation(180));
|
||
|
}
|
||
|
|
||
|
rightAngleTriangle() {
|
||
|
this.type = "rightAngleTriangle";
|
||
|
|
||
|
return this.svg
|
||
|
.path(
|
||
|
`
|
||
|
M ${this.x - this.width / 2} ${this.y - this.height / 2}
|
||
|
L ${this.x + this.width / 2} ${this.y + this.height / 2}
|
||
|
L ${this.x - this.width / 2} ${this.y + this.height / 2}
|
||
|
Z
|
||
|
`
|
||
|
)
|
||
|
.rotate(this.randomRotation(90));
|
||
|
}
|
||
|
|
||
|
semiCircle() {
|
||
|
this.type = "semiCircle";
|
||
|
|
||
|
const rad = Math.max(this.width, this.height) / 2;
|
||
|
|
||
|
return this.svg
|
||
|
.path(
|
||
|
`
|
||
|
M ${this.x - rad} ${this.y + rad / 2}
|
||
|
A ${rad} ${rad} 0 0 1 ${this.x + this.width / 2} ${this.y + rad / 2}
|
||
|
Z
|
||
|
`
|
||
|
)
|
||
|
.rotate(this.randomRotation(180));
|
||
|
}
|
||
|
|
||
|
randomRotation(step = 90) {
|
||
|
const n = 360 / step;
|
||
|
return random(0, n, true) * step;
|
||
|
}
|
||
|
|
||
|
generate() {
|
||
|
this.svg.clear();
|
||
|
|
||
|
this.fill = ["red", "yellow", "blue"][random(0, 2, true)];
|
||
|
|
||
|
const pattern = this.patternGenerator.generate("#000");
|
||
|
|
||
|
this.base = this.types[random(0, this.types.length - 1, true)]
|
||
|
.call(this)
|
||
|
.fill("none")
|
||
|
.stroke("none");
|
||
|
|
||
|
let translateVal = random(5, 15);
|
||
|
if (random(0, 1) > 0.5) translateVal *= -1;
|
||
|
|
||
|
this.fill = this.base
|
||
|
.clone()
|
||
|
.addTo(this.svg)
|
||
|
.fill(this.fill)
|
||
|
.translate(translateVal, -translateVal);
|
||
|
|
||
|
this.pattern = this.base
|
||
|
.clone()
|
||
|
.addTo(this.svg)
|
||
|
.fill(pattern)
|
||
|
.translate(-translateVal * 2, translateVal * 2)
|
||
|
.scale(random(0.625, 0.825));
|
||
|
|
||
|
this.outline = this.base
|
||
|
.clone()
|
||
|
.addTo(this.svg)
|
||
|
.stroke("black")
|
||
|
.fill("transparent");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class BauBud {
|
||
|
constructor(target) {
|
||
|
this.svg = SVG().viewbox(0, 0, 200, 200).addTo(target);
|
||
|
|
||
|
this.body = new BauBudBody(this.svg, 100, 100, 100, 100);
|
||
|
this.eyes = new BauBudEyes(this.svg, 100, 100);
|
||
|
}
|
||
|
|
||
|
generate() {
|
||
|
this.body.generate();
|
||
|
|
||
|
const eyeWidth = random(5, 20);
|
||
|
const innerWidth = 8;
|
||
|
|
||
|
this.svg
|
||
|
.ellipse(16)
|
||
|
.stroke("#000")
|
||
|
.fill("#fff")
|
||
|
.cx(100 - eyeWidth)
|
||
|
.cy(100);
|
||
|
this.svg
|
||
|
.ellipse(16)
|
||
|
.stroke("#000")
|
||
|
.fill("#fff")
|
||
|
.cx(100 + eyeWidth)
|
||
|
.cy(100);
|
||
|
|
||
|
const choice = random(0, 5, true);
|
||
|
|
||
|
let leftEyeInnerX = 100 - eyeWidth;
|
||
|
let rightEyeInnerX = 100 + eyeWidth;
|
||
|
|
||
|
let eyeY = 100;
|
||
|
|
||
|
switch (choice) {
|
||
|
case 1:
|
||
|
leftEyeInnerX -= 4;
|
||
|
rightEyeInnerX -= 4;
|
||
|
break;
|
||
|
case 2:
|
||
|
leftEyeInnerX += 4;
|
||
|
rightEyeInnerX += 4;
|
||
|
break;
|
||
|
case 3:
|
||
|
eyeY += 4;
|
||
|
break;
|
||
|
case 4:
|
||
|
eyeY -= 4;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
leftEyeInnerX = leftEyeInnerX;
|
||
|
rightEyeInnerX = rightEyeInnerX;
|
||
|
}
|
||
|
|
||
|
this.svg
|
||
|
.ellipse(innerWidth)
|
||
|
.stroke("#000")
|
||
|
.fill("#000")
|
||
|
.cx(leftEyeInnerX)
|
||
|
.cy(eyeY);
|
||
|
this.svg
|
||
|
.ellipse(innerWidth)
|
||
|
.stroke("#000")
|
||
|
.fill("#000")
|
||
|
.cx(rightEyeInnerX)
|
||
|
.cy(eyeY);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (let i = 0; i < 100; i++) {
|
||
|
const bauBud = new BauBud(document.querySelector(".buds"));
|
||
|
bauBud.generate();
|
||
|
}
|
||
|
|
||
|
const buds = document.querySelector(".buds");
|
||
|
|
||
|
setInterval(() => {
|
||
|
const y = window.pageYOffset;
|
||
|
const windowHeight = window.innerHeight;
|
||
|
if (y + 200 + windowHeight >= buds.scrollHeight) {
|
||
|
for (let i = 0; i < 100; i++) {
|
||
|
const bauBud = new BauBud(buds);
|
||
|
bauBud.generate();
|
||
|
}
|
||
|
}
|
||
|
}, 100);
|