Save Expand icon

Ron Valstar

front-end developer

Experiment: Condens

This started out as plain water droplets. With rotation and corner-radius you can make elements look fairly natural.

If you've ever had a good look at water droplets on a window pane you must have noticed they all reflect the outside world inverted.
A normal CSS background cannot be rotated, but a CSS gradient can be! So that was the starting point.

The following linear gradient is rotated by degrees: linear-gradient(25deg,[color1],[color2]), the radial gradient is rotated by converting the degrees to position using sine and cosine.

// --deg is the rotation of the drop, --degSun the rotation of the sun
--rad: calc((var(--deg) + var(--degSun)) * var(--pi) / 180);
--distance: 50%;
--x: calc(var(--distance) * cos(var(--rad)));
--y: calc(var(--distance) * sin(var(--rad)));

A similar calculation is needed for the drop-shadow on the drop. It is not meant as a shadow but more as some extra light breaking that makes everything look a tad more realistic.

At first I thought I'd just add elements, position them, and that would be that.

A good grid

Positioning is tricky enough; we want them spaced out evenly, without showing a pattern, and without overlap.

So a grid like this:

But better with Pythagoras and square roots and stuff:

At this point a randomised offset maxed out to the grid size (minus half the drop size) would prevent any overlaps.

I could have used a radial calculation for the offset here, so all possible positions are within a circle from the original point. But simply randomising x and y looks good enough; so all possible positions are within a square from the original point.

Deterministic chaos

I was going to leave it at that because I thought animating it would either be too complex, or would look unrealistic. Besides, these doodles should contain as little JavaScript as possible, or even no JavaScript.
I'd like to do an initial setup, maybe a resize adjustment, but the rest should all be running off a custom CSS property --t that is increased to the maximum of 231 − 1 (the eighth Mersenne prime) over a period of one million seconds (or eleven days).
Nothing more than a ticking clock.

@property --t {
    syntax: "<number>";
    initial-value: 0;
    inherits: true;
}

@keyframes tick {
    from { --t: 0; }
    to { --t: 2147483647; }
}

html {
    animation: tick 1000000s linear infinite;
}

The animations should be deterministic: we must be able to calculate a state in one go. Normally gravitational computations are dependent on the previous state. For CSS it hard to store a state. So it is better to set positions without having to calculate all previous or intermediate states.

But CSS has functions we can use, about twenty of them are mathematical functions, among which are the generic trigonometric functions. We already used them to rotate the radial-gradient and the box-shadow.

There is one function that I rarely ever use, but matches the exact motion I need.

The tan function:

The beauty is that it is asymptotic; it becomes infinitely large near the dashed lines (half PI at PI intervals) but never quite reaches it. If we vertically offset our drops by this value we can make them move from top to bottom, while taking a little rest in the middle. And we can increase this rest by simply multiplying the tangent.

And if we also offset each drop tangent timing with a (pseudo) random number they will not drop all at once. The repeating nature of the tangent will cause a drop to start at the top once it drops out of sight.

Here is that offset in CSS:

--dripOffset: calc(10px*tan(0.00001*var(--t) + var(--seed)/var(--mod)*var(--pi)));

The 0.00001*var(--t) determines how often a drop wil fall. The 10px is the actual speed. And the rest is to ensure the drops don't fall all at once.
The tangent function has a period of PI so a random number between 0 and PI is chosen, with --seed as the random number and --mod as the prng modulus.

The same --dripOffset variable can also be used to set the height of the water trail.
In real life this trail is never straight of course. Plus water drops rolling down will always take other water drops with them. That will not happen here as it would require a lot more computation1.

To draw the trail it is easier to use a second element. Both the visual drop and the trail are drawn using the pseudo elemens :before and :after.
The size and position are set on the 'real' element.

The trail is drawn slightly thinner. Its colors are the sky and ground color as a radial gradient positioned left and right with the vertical center on the drop. This way the trail tapers off when higher (faster). The positions are static, the sun is used as a color but not animated, but it looks real enough.

That is really all there is to it. Here is a link to the full-screen experiment again, and here is the full source if you want to tinker with it.