tool / svg blobsruns in your browser

SVG blob generator

Make a random, organic blob shape: choose how many points it has, how far each may wander and a seed, and copy it as an SVG file, a path’s d, React or a CSS clip-path. Morph it into the next seed, or watch scrollDraw draw its outline.

Points
3–12
Randomness
0–0.7
Code outputs
6
Gallery seeds
6
p0 · Mp1p2p3c1c2centre360° ÷ 7r4 · seededlongest radiusM · 7 × C · Z
Fig. 1 — 7 spokes 51.4° apart, a seeded radius on each, one smooth curve through their ends. The piece p1 → p2 is a cubic Bézier with c1 = p1 + (p2 − p0) ÷ 6 and c2 = p2 − (p3 − p1) ÷ 6, so it leaves p1 parallel to the dashed line p0 → p2. Every point joins that way, p0 included, where the path closes.
01The tool

Make a blob

Every setting redraws the blob at once. Construction shows how it is made; Morph moves it toward the next seed and back, and the Path d tab then gives both paths. The gallery loads 6 seeds to start from.

points · 6
randomness · 0.40
size · 400 px
line · 4 px
seed
style
colour
preview

6 points · M · 6 × C · Zlength 901.9 px400 × 400

blob.svg11 lines
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
  <!-- seed 4127 · 6 points · randomness 0.4 · 400 × 400 -->
  <path fill="currentColor" d="M200,72.9
    C237.2,73.5 282.8,101.3 308.5,137.3
    C334.2,173.4 372.2,256.1 354.2,289
    C336.1,321.9 249,336 200,334.6
    C151,333.2 79.4,314.1 60.4,280.6
    C41.3,247.2 62.3,168.6 85.6,133.9
    C108.9,99.3 162.8,72.3 200,72.9
    Z"/>
</svg>

Draw it · the outline with scrollDraw, driven here by seek()

On a page the same progress comes from the scroll: it starts as the blob’s top enters the bottom of the window and finishes as its bottom leaves the top.

main.js10 lines
// <div id="blob">
//   <svg viewBox="0 0 400 400" width="400" height="400">
//     <path d="M200,72.9 C237.2,73.5 282.8,101.3 308.5,137.3 C334.2,173.4 372.2,256.1 354.2,289 C336.1,321.9 249,336 200,334.6 C151,333.2 79.4,314.1 60.4,280.6 C41.3,247.2 62.3,168.6 85.6,133.9 C108.9,99.3 162.8,72.3 200,72.9 Z" fill="none" stroke="currentColor" stroke-width="4"/>
//   </svg>
// </div>

import { scrollDraw } from 'svg-scroll-draw';

// Draws the outline as #blob scrolls through the window.
scrollDraw('#blob');
02The construction

How a blob is built

Spokes. The blob has as many spokes as points, 360° ÷ points apart, the first straight up and the rest clockwise. The path starts at the first, so a drawing of its outline starts at the top (fig. 1).

Radii. Each spoke gets its own length from the seed, between 1 − randomness and 1 of the longest possible. At randomness 0 every spoke is as long as the others: a rounded triangle at 3 points, close to a circle at 12.

The curve. A closed Catmull-Rom curve through the spokes’ ends, written as one cubic Bézier per point. Each piece, from one point to the next, gets a handle at each end, set off from its point by a sixth of the line between that point’s two neighbours: forward at the start, back at the end. So the curve leaves each point in the direction it arrived. Then the whole shape is scaled about the centre until its farthest point sits 5% of the size, plus half the line, inside the box.

blob-path.js22 lines
// Spokes at equal angles from the top, clockwise; a radius on each (px);
// a closed Catmull-Rom curve through their ends: one C per point, then Z.
function blobPath(radii, cx, cy) {
  const n = radii.length;
  const p = radii.map((r, i) => {
    const a = -Math.PI / 2 + (i / n) * 2 * Math.PI;
    return [cx + r * Math.cos(a), cy + r * Math.sin(a)];
  });
  const at = (i) => p[(i + n) % n];
  const f = (v) => +v.toFixed(1);
  let d = `M${f(p[0][0])},${f(p[0][1])}`;
  for (let i = 0; i < n; i++) {
    const [p0, p1, p2, p3] = [at(i - 1), at(i), at(i + 1), at(i + 2)];
    // Each handle: a sixth of the chord between the point's neighbours.
    const c1 = [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6];
    const c2 = [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6];
    d += ` C${f(c1[0])},${f(c1[1])} ${f(c2[0])},${f(c2[1])} ${f(p2[0])},${f(p2[1])}`;
  }
  return d + ' Z';
}

blobPath([150, 120, 165, 110, 140], 200, 200); // five points around 200, 200
Why 0.7
Randomness stops at 0.7. Past it, a short radius between two long ones can fold the curve back over itself. Tried against every pattern of neighbouring radii from 3 to 12 points, the curve always keeps turning the same way round the centre up to 0.7, and can turn back at 0.8.
03Seeds

The same seed, the same blob

The radii come from mulberry32, a seeded random number generator a few lines long. The same seed gives the same numbers in every browser, because it is all 32-bit integer arithmetic, so a blob can be made again from its seed, point count, randomness and size. The SVG, React and CSS outputs carry a comment that says them.

seed.js15 lines
// mulberry32: a 32-bit seed in, the same stream of numbers in [0, 1) out.
function mulberry32(seed) {
  let a = seed | 0;
  return () => {
    a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

// Seed 4127, 6 points, randomness 0.4:
// each radius between 0.6 and 1 of the longest.
const rand = mulberry32(4127);
const radii = Array.from({ length: 6 }, () => 1 - 0.4 * rand());

The seed gives the radii in order, so changing the number of points keeps the first radii and adds or drops some at the end. The spokes move to new angles, though, so the shape is a new one. Randomness scales the same numbers: at a lower setting the blob keeps its bumps where they were, only smaller.

04Morphing

Morphing one blob into another

Two blobs with the same number of points have the same spokes and the same commands: M, one C per point, Z. Only the numbers differ, so a morph can move each number toward its partner. The preview moves the radii instead, which keeps every point on its own spoke (fig. 2).

t 0 · 4127t 0.5t 1 · 4128each radiusa + (b − a) × tall threeM · 6 × C · Z
Fig. 2 — seeds 4127 and 4128, 6 points each, and the blob half way. Every point slides along its own spoke, so the path’s commands never change, only its numbers.

On scroll, morphTo does the number-for-number version: it moves a <path>’s d toward another as the drawing progresses, and in development warns when the two paths’ commands or number counts differ.

morph.js12 lines
// <div id="blob"><svg viewBox="0 0 400 400" width="400" height="400">
//   <path d="M200,96 C249.9,96 324.2,81.3 349.9,113.5 C375.6,145.6 379.1,245.7 354.2,289 C329.2,332.3 250.4,374 200,373.4 C149.6,372.9 76.4,329 51.5,285.7 C26.6,242.4 25.6,145.3 50.4,113.6 C75.1,82 150.1,96 200,96 Z"
//     fill="none" stroke="currentColor" stroke-width="4"/>
// </svg></div>

import { scrollDraw } from 'svg-scroll-draw';

// Seed 4128 draws and becomes seed 4127 as #blob scrolls through
// the window. 6 points each, so both are M, 6 × C, Z.
scrollDraw('#blob', {
  morphTo: 'M200,86.1 C233.5,86.8 270.9,110.4 296.6,144.2 C322.3,178 370.3,259.2 354.2,289 C338,318.8 248.3,325.1 200,323.3 C151.7,321.5 81.5,309 64.2,278.4 C46.8,247.9 73.3,172 95.9,139.9 C118.6,107.9 166.5,85.4 200,86.1 Z',
});
The dash
scrollDraw measures the line once, on the path it starts with (and again on a resize or refresh()), and the dash keeps that length while the shape changes. If the blob it becomes is longer, the end of its outline is still open when the scroll is done. Seed 4128 is the longer of these two, 1030.7 px against 854.8, so it is the one the path starts from. The generator’s readout gives each blob’s length.
056 questions

SVG blob questions

Q1

How do I make a random blob shape in SVG?

Put a few points around a centre at equal angles, give each its own random distance from the centre, and join them with one smooth closed curve. The generator above does exactly that: 3 to 12 points, a radius on each from a seeded random number generator, and a closed Catmull-Rom curve written as one cubic Bézier (C) per point and a Z. Copy the result as an SVG file, the bare d value, a React component or a CSS clip-path.

Q2

How do I make a smooth blob with no corner where the path closes?

Make the curve leave every point in the direction it arrived. In a Catmull-Rom curve the direction at each point is parallel to the line between its two neighbours, and the Bézier handles on either side of the point lie along that direction, each a sixth of that line long. To close it, wrap the neighbours round: the first point’s neighbour before is the last point, and the last point’s neighbour after is the first. The seam is then as smooth as every other point, and the path ends with Z after a C that already ends on the start.

Q3

How do I morph one SVG blob into another?

Give both blobs the same number of points. Then both paths have the same commands, M, one C per point and Z, so every number in one has a partner in the other, and anything that moves numbers can move between them: SMIL’s <animate attributeName="d">, a JavaScript animation library, or scrollDraw’s morphTo option as the page scrolls. The preview above moves the radii instead, so every point slides along its own spoke. Blobs with different point counts have different commands, and a number-for-number morph cannot pair them up.

Q4

How do I draw a blob’s outline on scroll?

Give the path a stroke and no fill, wrap the SVG in an element, and call scrollDraw('#blob') from svg-scroll-draw. It measures the outline with getTotalLength(), makes the stroke one dash that long and slides it into place as the element scrolls through the window, at a steady rate: from its top reaching the bottom of the window to its bottom reaching the top. The Draw it panel above writes the markup and the call for the blob you made, as JavaScript or React.

Q5

Can I use a blob as a CSS clip-path or a mask?

Yes. clip-path: path('…') takes the d value as it is, but counts in pixels of the element’s box and does not scale, so the CSS clip-path tab writes it for an element exactly the size you set. For a shape that scales with its element, use the SVG file as a mask image, or put the path in a <clipPath clipPathUnits="objectBoundingBox"> with every number divided by the size, so they run from 0 to 1.

Q6

Will the same seed always give the same blob?

Yes. The radii come from mulberry32, a small seeded generator made of 32-bit integer arithmetic, so the same seed gives the same numbers on every machine, and the same seed, point count, randomness and size give the same blob. Math.random() cannot be seeded, which is why it only picks a new seed when you press Shuffle. The SVG, React and CSS outputs carry a comment that names the seed and settings that made them.

Drawing on scroll with svg-scroll-draw 2.23.3: the docs, or the SVG path viewer to measure and check any path.