Animating A Blob
Dec 11, 2025Introduction
In my previous post I played around with a couple of ways to build a convincing “blob” with JavaScript. In this follow up post, I’ll show off a cool way to animate it. We’ll lean into some more Fourier stuff, and end up (I think) with a really cool effect.
Quick Recap
If you haven’t read the previous article, I highly recommend it because we’re going to build off of the math from it. But to review, we came up with a nice “blob function” that defines the blob’s radius as a function of angle:
Note that $k$ only goes up to some fixed $n$ that’s not “too big” which keeps the blob from getting too “pointy”. In the previous post $n = 10$.
In the previous post we also worked out some limits on what $a_k$ could be, and used that to calculate what a minimum value for $a_0$ would be.
Where We’re Going Next
In the previous post we basically just set $b_k$ randomly. Our “blob constraints” didn’t really tell us what $b_k$ needed to be so we just picked something. So could we pick something else?
Well, it might be better to first demonstrate what $b_k$ does. Consider the following blob with just a single $\sin$ term, and $b_1 = 0$ ($n = 1$):
Now consider what happens when we change $b_1$ to be 15º, i.e. $b_1 = \frac{pi}{12}$.
So, changing $b_k$ shifts the sine term, which results in the blob rotating. What if we animated that $b_1$ term?
Okay, that’s what we’re looking for. Let’s come up with a generalized approach.
How Do We Animate That
Let’s make $b_k$ a function of time:
where $f_k$ is the how fast $b_k$ changes and $\beta_k$ is the initial value of $b_k$.
Picking Values
Picking random values is straightforward: For $\beta_k$ we need a uniform random number between 0 and $2\pi$, that is $b_k$ starts as offset anywhere on the circle.
For $f_k$ we pick such that our sine shift doesn’t happen “too fast”. We can measure “too fast” by saying that $b_k$ loops back on itself every $T$ seconds or greater. The longer it takes, the slower its moving. We can formulate that like this (assuming $f_k$ non-negative):
Fleshing this out gives:
Then we simplify:
Lastly, we solve for $f_k$:
Similarly, for $f_k \le 0$ we get a similar result, and can simplify to:
How Does That Look?
Here’s some code for generating (and drawing) blobs. Hopefully it’s a little more organized, but I’m now using the criteria we just determined for finding $f_k$.
class AnimatedBlob {
constructor(r, n) {
this.a = [];
this.f = [];
this.beta = [];
const H_n = 2.92897; // 10th harmonic number. TODO: Calculate this based off n
const epsilon = r * n / H_n;
const delta = 1.0
this.a0 = epsilon / n * H_n + delta;
const T = 1.0; // max period for a blob to circle around
for (let k = 0; k < n; k++) {
// set a
const top = epsilon / (k + 1) / n;
const bottom = -top;
this.a.push(bottom + Math.random() * (top - bottom));
// set f
this.f.push(this.rand(-Math.PI * 2 / T, Math.PI * 2 / T));
// set beta, uniform random between 0 and 2pi
this.beta.push(this.rand(0, Math.PI * 2));
}
}
rand(min, max) {
return min + Math.random() * (max - min);
}
radius(theta, t) {
// theta is in radians
let radius = this.a0;
for (let k = 0; k < this.a.length; k++) {
radius += this.a[k] * Math.sin((k + 1) * theta + this.f[k] * t + this.beta[k]);
}
return radius;
}
draw(ctx, t) {
const radii = [];
for (let degrees = 0; degrees < 360; degrees++) {
radii.push(this.radius(degrees * Math.PI / 180, t));
}
drawBlob(ctx, 100, 100, radii);
}
}
const finalCtx = document.getElementById("final-result").getContext("2d");
const blob = new AnimatedBlob(75, 10);
let t = 0;
function drawAnimated() {
blob.draw(finalCtx, t);
t += 1 / 16;
requestAnimationFrame(drawAnimated);
}
A Better Criteria for $f_k$
If you regenerate a few blobs you’ll find that some almost feel “jittery”. I noodled on that a bit, and I think what’s happening is we’re getting a case where the following occurs:
- $k$ is on the “larger” side, so the $\sin$ term is higher frequency
- the random value for $f_k$ happens to be larger magnitude.
In this scenario, the $b_k$ term shifts quickly from 0 to $2\pi$, a full sweep over the period of the $\sin$ term. However, the $\sin$ term is actually $\sin{k \theta}$ so a sweep from 0 to $2\pi$ covers $k$ periods of the term. We can correct for this by making $b_k$ loop back on itself over the period of the $\sin$ term. That would look like this:
Going through the algebra again, we get the following criteria for $f_k$:
Here’s what that looks like
Conclusion
Well these are pretty cool, I guess? For funsies I put them up on my business website if you’re interested in seeing them in the wild!