* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 100vh;
display: grid;
place-items: center;
}
svg {
width: 75vmin;
height: 75vmin;
}
import { spline } from "https://cdn.skypack.dev/@georgedoescode/[email protected]";
import { SVG } from "https://cdn.skypack.dev/@svgdotjs/svg.js";
// end result / complete code for the following tutorial
console.clear();
// choose a number within a range, integer (whole number) by default
function random(min, max, float = false) {
const val = Math.random() * (max - min) + min;
if (float) {
return val;
}
return Math.floor(val);
}
class BlobCharacter {
constructor(width, height, target) {
// viewBox width & height dimensions
this.width = width;
this.height = height;
// position of our character within the viewBox (the center)
this.x = this.width / 2;
this.y = this.height / 2;
// <svg /> element (svg.js instance) we are using to render
this.svg = SVG()
.addTo(target) // mount instance to our target
.viewbox(0, 0, this.width, this.height); // set the <svg /> viewBox attribute
// choose a random size / radius for our character
this.size = random(50, 80);
this.setColors();
this.svg.node.style.background = this.lightColor;
}
drawBody() {
// how many points do we want?
const numPoints = random(3, 12);
// step used to place each point at equal distances
const angleStep = (Math.PI * 2) / numPoints;
// keep track of our points
const points = [];
for (let i = 1; i <= numPoints; i++) {
// how much randomness should be added to each point
const pull = random(0.75, 1, true);
// x & y coordinates of the current point
// cos(theta) * radius + a little randomness thrown in
const x = this.x + Math.cos(i * angleStep) * (this.size * pull);
// sin(theta) * radius + a little randomness thrown in
const y = this.y + Math.sin(i * angleStep) * (this.size * pull);
// push the point to the points array
points.push({ x, y });
}
// generate a smooth continuous curve based on the points, using bezier curves. spline() will return an svg path-data string. The arguments are (points, tension, close). Play with tension and check out the effect!
const pathData = spline(points, 1, true);
// render the body in the form of an svg <path /> element!
this.svg
.path(pathData)
.stroke({
width: 2,
color: this.darkColor
})
.fill(this.primaryColor);
}
drawEyes() {
// ensure the width of two eyes never exceeds 50% of the characters body size
const maxWidth = this.size / 2;
// if a random number between 0 and 1 is greater than 0.75, the character is a cyclops!
const isCyclops = random(0, 1, true) > 0.75;
// the size of each (or only) eye.
const eyeSize = random(maxWidth / 2, maxWidth);
if (isCyclops) {
// draw just 1 eye, in the center of the character
this.drawEye(this.x, this.y, eyeSize);
} else {
// draw 2 eyes, equidistant from the center of the character
this.drawEye(this.x - maxWidth / 2, this.y, eyeSize);
this.drawEye(this.x + maxWidth / 2, this.y, eyeSize);
}
}
// x position, y position, radius / size
drawEye(x, y, size) {
// create a new svg <group /> to add all the eye content too
const eye = this.svg.group();
// <group /> elements do not have an x and y attribute, so we need to "transform" it to the right position
eye.transform({ translateX: x, translateY: y });
// add the outer ring of the eye (an svg <circle /> element) to our eye <group />
eye
.circle(size)
// cx / cy are the { x, y } values for the svg <circle /> element
.cx(0)
.cy(0)
.stroke({
width: 2,
color: this.darkColor
})
.fill(this.lightColor);
// add the inner part of the eye (another svg <circle /> element) to our eye <group />
eye
.circle(size / 2)
.cx(0)
.cy(0)
.fill(this.darkColor);
}
setColors() {
// random hue
const hue = random(0, 360);
// random saturation, keeping it quite high here as a stylistic preference
const saturation = random(75, 100);
// random lightness, keeping it quite high here as a stylistic preference
const lightness = random(75, 95);
// base color
this.primaryColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
// almost black, slightly tinted with the base color
this.darkColor = `hsl(${hue}, ${saturation}%, 2%)`;
// almost white, slightly tinted with the base color
this.lightColor = `hsl(${hue}, ${saturation}%, 98%)`;
}
draw() {
// clear the <svg /> element
this.svg.clear();
// generate new colors
this.setColors();
// set the svg background color
this.svg.node.style.background = this.lightColor;
// generate a new body shape and render it
this.drawBody();
// genearte new eye(s) and render them
this.drawEyes();
}
}
// viewBox w x h, target to append the <svg /> element to
const character = new BlobCharacter(200, 200, document.body);
// draw the initial character
character.draw();
setInterval(() => {
// every 1.5s after the initial render, draw a new character
character.draw();
}, 1500);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.