There are two things I love in this world: blobby shapes and putting googly eyes on things. This tutorial combines both of my great loves and hopefully provides a gentle introduction to generative art whilst doing so.
Here's what we'll be making:
It is a somewhat simple example of what you can achieve with a generative approach to design/art, but hopefully something you can expand on.
Prerequisites ℹ️
No prior generative art knowledge is required! This tutorial is ideal for folks who are already familiar with JavaScript / HTML / CSS and are looking to get started with generative art.
What... is generative art? 🤔
The simplest definition I can find of Generative art is on the Tate gallery website —
“Generative art is art made using a predetermined system that often includes an element of chance – is usually applied to computer based art”
I think this is perfect and worth keeping in mind as we progress through this tutorial, especially if you are new to this stuff.
Let's build! 🔨
For this tutorial we are going to use SVG to render our character, JavaScript to decide what to render, and a little sprinkling of CSS to make sure they sit nicely on the page.
I have also included a couple of external JS libraries to keep our code simple and clean.
-
https://svgjs.dev/docs/3.0/ (used add / remove / modify SVG elements such as
<circle>
) - https://www.npmjs.com/package/@georgedoescode/spline (used to draw smooth curves through multiple points)
I have set up a CodePen that you can fork here which has all this stuff pre-added. Once you have forked the pen or set up your environment, we are ready to start creating our characters!
Package installation
If you are creating your own environment from scratch rather than forking the CodePen, you can install the required packages with:
npm install @georgedoescode/spline @svgdotjs/svg.js
They can then be imported into your JavaScript like so:
import { spline } from "@georgedoescode/spline";
import { SVG } from "@svgdotjs/svg.js";
Note: if you plan on setting up your own environment, remember you will likely need a bundler such as Parcel or Webpack to handle these kinds of module imports.
A blank canvas 🖼️
If you started your project by forking the above CodePen, then you already have this CSS set up.
If not, feel free to add the following to your project to place the <svg />
nicely in the centre of the viewport. Or don’t! In the words of the Isley Brothers — it's your thing, do what you wanna do.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 100vh;
display: grid;
place-items: center;
}
svg {
width: 75vmin;
height: 75vmin;
}
The birth of a Blob 👶
There will be no birds, bees, storks etc here. Just a code editor of your choice and some ES6 class syntax 🤖
Straight off the bat, we need to define a few properties:
- width: the viewBox width of the SVG
- height: the viewBox height of the SVG
-
target: where the
<svg />
element should be added in the DOM -
svg: the
svg.js
instance we will use for rendering - x: the horizontal position of our character within the SVG viewBox
- y: the vertical position of our character within the SVG viewBox
With all this in mind, we should have a class constructor that looks something like this:
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
}
}
Once we have defined the class constructor, we can create a brand new Blob character by calling:
// viewBox w x h, target to append the <svg /> element to
const character = new BlobCharacter(200, 200, document.body);
You won’t see anything just yet, and that’s cool.
Note: the viewBox attribute
If you are new to SVG and wondering what viewBox is, it essentially defines a coordinate space that you can draw anything relative to. In our case 200 x 200px.
Once you have defined a viewBox, the SVG will draw everything relative to the space you have defined whilst scaling to any resolution. This makes creating responsive generative works nice and easy!
If you would like to read more about viewBox - here is a great article on CSS tricks
All creatures great and small 🐭 🐘
Now that we have all the “boilerplate” setup for our character, it’s time for some fun!
The first thing we need to think about is the overall size of our character. Should they be large or small? How do we define that?
As we will be basing our character's shape on a circle (more on this later) we can use the circle’s radius to define our character's size. Don’t worry, I’m no mathematician and this isn’t going to get too deep.
A number between 50 and 80 should do perfectly given our viewBox dimensions of 200x200. Here's an infinitely useful utility function that we can use to generate a random number within a range:
// 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);
}
Using this very handy utility function we can define a random size for our character like so:
class BlobCharacter {
constructor(width, height, target) {
...
// choose a random size / radius for our character
this.size = random(50, 80);
}
}
Perfect! We still won't see anything, but the code is looking good.
Drawing the body ✏️
I think a good next step is to draw our character’s body. This is probably the trickiest part of the tutorial, but don’t worry if it’s a little confusing!
Take your time, play around with the code, break it, put it back together.
I find that often in generative art I use a code snippet for a while before I truly understand it. I think this is fine, no one is going to look at your beautiful creations and see that you didn’t fully understand some maths. If it looks cool, it looks cool. We are making art here not publishing a research paper!
Anyway, onto the code...
To draw our character’s body, we are going to:
- Plot equidistant points around the circumference of a circle
- Add a little randomness to the
{x, y}
values of each point - Draw a smooth curve through all of the points
The code for this can be added to a drawBody()
function on our BlobCharacter class (all code is commented to outline its functionality in-context):
...
drawBody() {
// choose a random number of points
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
const x = this.x + Math.cos(i * angleStep) * (this.size * pull);
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: '#000'
})
.fill('transparent');
}
Once added to the class, it can be called like so:
character.drawBody();
Ok! Drumroll, please...
If you check out your browser / CodePen window you should now be seeing an awesome random blob shape. If you refresh your browser or re-run the code, you should hopefully see a new shape each time!
We are making generative art!
Note: the spline()
function
The spline()
function you see here is another incredibly useful utility. It simply draws a smooth curve through a set of { x, y }
points. The shapes it creates should always “close” perfectly leaving you with a very satisfying, natural end result. The technical name for it is a Catmull-Rom spline.
You can find the source code for the version I created here. Thanks to https://twitter.com/cassiecodes for introducing me to the magic of splines 🙌
Drawing the eyes 👀
OK, so we have an awesome organic blob shape. We could almost stop here. You see these blobs all over the web and they can be an incredibly versatile design asset. A quick dribbble search should show you a few examples!
We should add some googly eyes though. Everything looks better with googly eyes.
Let’s add a drawEye()
function to our BlobCharacter class:
// x position, y position, radius / size
drawEye(x, y, size) {
// create a new svg <group /> to add all the eye content to
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: '#000'
})
.fill('#fff');
// add the inner part of the eye (another svg <circle /> element) to our eye <group />
eye
.circle(size / 2)
.cx(0)
.cy(0)
.fill('#000')
}
This isn’t going to do too much right now. Let’s add another drawEyes()
function that will call drawEye()
with some values.
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 centre of the character
this.drawEye(this.x, this.y, eyeSize);
} else {
// draw 2 eyes, equidistant from the centre of the character
this.drawEye(this.x - maxWidth / 2, this.y, eyeSize);
this.drawEye(this.x + maxWidth / 2, this.y, eyeSize);
}
}
We can then call drawEyes()
in the same way as drawBody()
:
character.drawEyes()
If you check out your browser now you should have the awesome blob body from before, but with some fresh new googly eyes (or just one eye) attached. Nice!
Now is a great time to inspect the DOM and have a poke around the <svg />
element that contains all of the parts of our blob character. You should see something like this:
This is one of the great things about using SVG for generative art. It is super easy to debug/visualise as you have a visual DOM tree to explore.
Inspecting the <svg />
element should highlight what svg.js
has been doing for us all this time. It is simply simplifying the dynamic creation / updating of SVG DOM elements. This can get quite wordy without a library.
Time to bust out the crayons 🖍️
Our character is looking awesome. It’s got tons of character, but I think it would be cool to add some color. You could leave it black and white if you like, though. It’s got a kind of cool kawaii sketch vibe this way.
A simple approach to introducing some color here is to define a primaryColor
, a lightColor
to replace #fff
and a darkColor
to replace #000
.
The darkColor
and lightColor
values are both tinted with the baseColor
. This is something I do a lot and think it’s a nice trick to make your color palettes feel coherent. It can work great in a UI context too.
Let’s set the color values in a new setColors()
function:
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%)`;
}
I always use HSL for colors as it I find it intuitive and easy to modify in a generative context. As displayed in the above code snippet, I am choosing random H/S/L values and combining them using JavaScript template literal strings.
We can then call setColors()
in the BlobCharacter constructor:
class BlobCharacter {
constructor(width, height, target) {
...
this.setColors();
}
}
Once the colors are defined, we can apply them throughout the code:
-
this.primaryColor
in place oftransparent
for the body fill -
this.darkColor
for all occurrences of#000
-
this.lightColor
for all occurrences of#fff
Finally, we can set the base <svg />
background color to this.lightColor
to create a colorful backdrop for our character:
class BlobCharacter {
constructor(width, height, target) {
...
this.setColors();
this.svg.node.style.background = this.lightColor;
}
}
Your character should now look something like the following image. Remember, the colors and shape will be different each time!
Variety is the spice of life 🌶️
Our character design is finished! There is one last thing we could add though...
Right now we only see one example of a character. It would be good to demonstrate the generative aspect of the piece a little more. We can do this by periodically regenerating and rendering our character.
In order to do this, let's wrap all the drawing functionality into a single draw()
method on the BlobCharacter
class:
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();
}
We can then call this method rather than running drawBody()
and drawEyes()
directly after creating our character
instance. We can then continue to call this method every 1.5s to create a new character:
// 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);
Hopefully you will now see something like this...
That’s all folks! 👋
Hooray, we made it! Hopefully, you have got to the end of this tutorial and created a cool character, whilst learning some stuff about generative art in the process.
If you got stuck at any point, check out the final example code as a reference or leave a comment here. I'm always happy to help!
If you did enjoy this post, please follow me on Twitter @georgedoescode and / or on CodePen @georgedoescode.
You can also support my tutorials by buying me a coffee ☕
I’m always posting little generative experiments, and I plan on publishing an article every 2 weeks from here onwards.
Next steps ➡️
There are a bunch of places you could take this from this point: animation, different eye types, different body shapes, a custom blob generator etc, and I would love to see anything you end up creating!
If you do end up creating anything that you would like to share, add the hashtag #generativeBlobStuff
to your CodePens / tweets / whatever!
Thank you very much for reading!
Top comments (5)
I totally forgot to add this to the post, but if anyone wants to check out some more generative characters (and maybe get some inspiration for next steps) here is a site I made a couple of months back that I based these little creatures on!
polyfig.app/
These are so cute
Ah! Thanks Ben!
This is so cool!
Thank you!