It's possible to do this somewhat efficiently beyond two balls with GLSL and lots of uniforms (or a UBO), since metaballs from the graphics perspective are really just distance fields.
If you want more than a few balls, you can do it in two passes: one to produce the distance field, and one to threshold it.
As an added benefit, it's straightforward to generalize these approaches to any two-dimensional continuous function.
I did something fairly similar to this here: https://codepen.io/thomcc/pen/vLzyPY (I need to look into why this isn't running at 60fps anymore on my laptop, it certainly used to...)
The big difference is that it prerenders a gradient for each ball (it uses html5 canvas for that, but doing it with webgl is completely doable, although a bit more work), which is used as a distance field.
> I need to look into why this isn't running at 60fps anymore on my laptop, it certainly used to...
Runs at 60fps for me on a Chromebook from 2014. I suspect you're looking at it on macOS, which has had very poor (arguably the poorest of any x86 platform) OpenGL drivers for the last four or five years.
Far from the worst: if the update rate is reasonably low, this approach makes it a lot simpler to handle high-resolution displays, event registration. It should also run on a few more browsers and devices.
It's possible to do this somewhat efficiently beyond two balls with GLSL and lots of uniforms (or a UBO), since metaballs from the graphics perspective are really just distance fields.
If you want more than a few balls, you can do it in two passes: one to produce the distance field, and one to threshold it.
As an added benefit, it's straightforward to generalize these approaches to any two-dimensional continuous function.