DEV Community

Cover image for Generative blob characters using SVG!
George Francis
George Francis

Posted on • Updated on • Originally published at georgefrancis.dev

Generative blob characters using SVG!

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.

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
Enter fullscreen mode Exit fullscreen mode

They can then be imported into your JavaScript like so:

import { spline } from "@georgedoescode/spline";
import { SVG } from "@svgdotjs/svg.js";
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Plot equidistant points around the circumference of a circle
  2. Add a little randomness to the {x, y} values of each point
  3. 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');
}
Enter fullscreen mode Exit fullscreen mode

Once added to the class, it can be called like so:

character.drawBody();
Enter fullscreen mode Exit fullscreen mode

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!

An organic blob shape

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')
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

We can then call drawEyes() in the same way as drawBody():

character.drawEyes()
Enter fullscreen mode Exit fullscreen mode

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!

An organic blob shape with googly eyes added

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:

A screenshot of an SVG DOM node

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%)`;
  }
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

Once the colors are defined, we can apply them throughout the code:

  • this.primaryColor in place of transparent 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Your character should now look something like the following image. Remember, the colors and shape will be different each time!

A colorful blob character


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();
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Hopefully you will now see something like this...

A gif showing different blob character generations


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)

Collapse
 
georgedoescode profile image
George Francis

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/

Collapse
 
ben profile image
Ben Halpern

These are so cute

Collapse
 
georgedoescode profile image
George Francis

Ah! Thanks Ben!

Collapse
 
nas5w profile image
Nick Scialli (he/him)

This is so cool!

Collapse
 
georgedoescode profile image
George Francis

Thank you!