Translation from: Lightning Miners Translation Team

Drawing Realistic Clouds with SVG and CSS

By Beau Jackson

Drawing Realistic Clouds with SVG and CSS

Translator: sichenguo

There is a story in Greek mythology about Zeus creating nephere, a cloud goddess, and like most Greek myths, this story is bizarre and R-rated. Here’s a short and restrained version.

What we do know is that Nephere was created by Zeus in the image of his own beautiful wife. A mortal meets Nephere, falls in love, and together they have a child, a half-human, half-horse baby, to be exact.

Weird, right? Thankfully, the process of creating a cloud in a browser is much simpler and less risky.

(Demo)

Recently, I found out that developer Yuan Chuan has implemented a realistic cloud generation from code. To me, the concept of a cloud in a browser has always been as mysterious as the other side of Greek mythology.

Let’s take a look at the ‘brush’ (click here) and see that the author achieved this amazing ‘cloud image’ by using box-shadow as a complement to the

element with two filters!

To create a realistic and delicate cloud image, use two filters, feTurbulence and feDisplacementMap. Not only can these two filters be powerful and complex, but they can also offer some exciting features (including Oscar-winning algorithms)! Of course, these features have daunting complexity under the browser ‘hood.’

While the physics of these SVG filters are beyond the scope of this article, there is plenty of documentation available on MDN and W3.org for learning about them. FeTurbulence and feDisplacement are also introduced.

For this article, we’ll focus on learning how to use these SVG filters to achieve amazing results. The algorithm behind the filter is beyond our scope, just as an artist can paint a beautiful landscape without understanding the molecular structure of paint.

All we need to do is pay close attention to a small number of SVG attributes that allow us to draw realistic cloud images in the browser. By learning these attributes, we can make certain filters as much as we want in the project.

Some necessary pre-requisite basics

Five notable properties of the CSS rule box-shadow:

box-shadow: <offsetX> <offsetY> <blurRadius> <spreadRadius> <color>;
Copy the code

Let’s increase these values (probably higher than normal developers would) so that the shadow appears in the lower right part of the view. !

Demo

#cloud-square {
    background: turquoise;
    box-shadow: 200px 200px 50px 0px # 000;
    width: 180px;
    height: 180px;
}

#cloud-circle {
    background: coral;
    border-radius: 50%;
    box-shadow: 200px 200px 50px 0px # 000;
    width: 180px;
    height: 180px;
}
Copy the code

You must have performed or seen shadow puppetry?

Double-M

In a similar way to changing the shape of the hand to change the shape of the shadow, the “source shape” in our HTML can be moved or morphed to synchronously affect the shape of the shadow rendered in the browser. Box-shadow replicates the rounded corner effect of the original graphic. The SVG filter applies to both elements and their shadow.

<svg width="0" height="0">
    <filter id="filter">
        <feTurbulence type="fractalNoise" baseFrequency=". 01" numOctaves="10" />
        <feDisplacementMap in="SourceGraphic" scale="10" />
    </filter>
</svg>
Copy the code

So far, the above SVG tags won’t render because we haven’t defined any visual styles (let alone zero width, height). Its sole purpose is to keep our feed filter SourceGraphic (that is, our

). Both our source

and its shadow are distorted independently by the filter.

We add the following CSS rules to the target element using the ID selector (#cloud-circle) :

#cloud-circle {
    filter: url(#filter);
    box-shadow: 200px 200px 50px 0px #fff;
}
Copy the code

Look here!

Well, the addition of an SVG filter still doesn’t surprise us.

(Demo)

Don’t worry! This is just the beginning. It gets interesting.

testfeDisplacementMapScale properties

Some experiments using this property can yield remarkable results. For now, let’s keep its feTurbulence unchanged and just adjust the Scale property of the DisplacementMap.

As the scale increases (by 30 at a time), our source

begins to distort and its shadows more closely resemble the random shapes of the clouds in the sky.
<feDisplacementMap in="SourceGraphic" scale="180" />
Copy the code

Demo

Well, by changing the scale, I finally seem to be approaching the range of values for the correct cloud shape! Let’s change the color slightly to look more like a cloud ☁️.

body {
    background: linear-gradient(165deg, #527785 0%, #7fb4c7 100%);
}

#cloud-circle {
    width: 180px;
    height: 180px;
    background: # 000;
    border-radius: 50%;
    filter: url(#filter);
    box-shadow: 200px 200px 50px 0px #fff;
}
Copy the code

Now our graphics are getting closer and closer to the real cloud effect!

Adjust the value of box-shadow

The following set of images shows the effect of blur on the box-shadow effect. In the figure below, the array of blur increases in sequence.

To give our cloud some cumulus effect, we can increase the size of

slightly.
#cloud-circle {
    width: 500px;
    height: 275px;
    background: # 000;
    border-radius: 50%;
    filter: url(#filter);
    box-shadow: 200px 200px 60px 0px #fff;
}
Copy the code

div

Wait a minute! We have increased the size of the source element, but it is now affecting our “cloud (white shadow)”. It’s very simple. You can solve this occlusion problem by projecting it further away.

The hidden layer of the source element is well implemented through some CSS positioning. The element is the parent of the cloud div element and is statically positioned by default. Setting Sue to absolute positioning hides # cloud-circles that do not need to be shown.

#cloud-circle {
    width: 500px;
    height: 275px;
    background: # 000;
    border-radius: 50%;
    filter: url(#filter);
    box-shadow: 400px 400px 60px 0px #fff; /* Increase shadow offset */
    position: absolute; /* Take the parent out of the document flow */
    top: -320px; /* Move a little down */
    left: -320px; /* Move a little right */
}
Copy the code

Great. It looks like we’ve got our cloud map in shape.

codepen

The clouds spread out on the screen, well, that should be a good description of the clouds we’ve drawn, there’s always something missing, we could have done better!

Use layers to express depth

This is what we want:

Photo source: pcdazero

Judging by the depth, texture and richness of the clouds in this photo, one thing is clear: Zeus went to art school. At the very least, he must have read the General-purpose component Design Principle, which states the concept of generality:

[…]. Lighting deviation plays an important role in the interpretation of depth and nature, and can be manipulated by the designer in various ways…… Use contrast between light and dark areas to give the appearance depth of field.

This paragraph gives us a hint to optimize the cloud image to be more realistic. By stacking layers of different shapes, sizes and colors, we can render cloud images in the reference image to create contrast. All you need is to create multiple layers using filter.

<svg width="0" height="0">
    <! -- Back Layer -->
    <filter id="filter-back">
        <feTurbulence
            type="fractalNoise"
            baseFrequency="0.012"
            numOctaves="4"
        />
        <feDisplacementMap in="SourceGraphic" scale="170" />
    </filter>
    <! -- Middle Layer -->
    <filter id="filter-mid">
        <feTurbulence
            type="fractalNoise"
            baseFrequency="0.012"
            numOctaves="2"
        />
        <feDisplacementMap in="SourceGraphic" scale="150" />
    </filter>
    <! -- Front Layer -->
    <filter id="filter-front">
        <feTurbulence
            type="fractalNoise"
            baseFrequency="0.012"
            numOctaves="2"
        />
        <feDisplacementMap in="SourceGraphic" scale="100" />
    </filter>
</svg>
Copy the code

Plus the layers provide more angles to explore feTurbulence and more functionality. We chose to use a smoother type :fractalNoise, and set numOctaves to 6.

<feTurbulence type="fractalNoise" baseFrequency="n" numOctaves="6" />
Copy the code

For now, let’s focus on the baseFrequency attribute. Here’s what we found when we incremented the baseFrequency value:

Words like turbulence, noise, frequency and octave can seem strange or even confusing. But fear not! Comparing the effect of a filter to a sound wave is actually quite accurate. We can equate low frequency (baseFrequency= 0.001) with low noise and low frequency, and higher value (baseFrequency=0.1) with sharper pitch.

We can see that our corresponding baseFrequency values for cumulus effects are in the range of 0.005 to 0.01.

Use numOctaves to add details

Increasing the value of numOctaves can render images in extremely fine detail. However, this is computationally intensive, so it is important to note that more detailed rendering can also have performance issues, so keep this value within a range if not necessary.

The good news is that, in this case, the details needed for our cloud image do not require a high numOctaves value, as shown in the figure above, a numOctaves value of 4 or 5 will work fine.

rendering

codepen

Changing the seed property produces a variety of cloud images

You can dig a little deeper into the seed property. However, for our purposes, the effect of seed can affect shape.

The Perlin Noise function (mentioned above) uses this value as the initial value for its random number generator. If this property is not set, the seed defaults to zero. The difference with baseFrequency is that no matter what value seed is given, there is no performance penalty to worry about.

Different seed values produce different shapes

The GIF above shows the effect with different seed values. It is important to note that each cloud is a layered composite cloud. (Although I adjusted the properties of each layer, I kept the seed values evenly distributed.)

Brockenhexe

Here, looking closely at the reference image, I have superimposed three ‘cloud’ layers (with different opacities) onto a div. Through trial and error and tweaking the seed parameter values, the result is an image that resembles the cloud shape in the photo below.

codepen

The sky is the limit

Of course, it would be arrogant of us to think that the clouds we have created are more beautiful than the goddesses created by Zeus.

However, the more we learn about CSS and SVG filters, the more we are able to create visually stunning graphics like these:

Reflecting Mist

Alto-Cirrus Clouds

In this article we’ve immersed ourselves in the power and complexity of SVG, even though SVG filters can often seem complex and difficult to understand.

However, just like the example of A Single Div Project and Diana Smith’s Painting Techniques, interesting and creative methods can always produce stunning results.

I’m sure a lot of developers have the ability to create a cloud, but a tool that can easily create a cloud would be much more welcome. So I developed a gadget to do just that.