Something is brewing

The problem: you need a visual to pair the assuredly brief, but inevitable delay a page needs to load.

The tagline: improve the experience with SVG and CSS.

SVG syntax

The page in question offers a series of recipes, factual or otherwise, so it is only natural to conjure up SVG elements to draw a piece of kitchenware.

Pot

Just a handful of elements is enough to realize a convincing cauldron. First a rounded rectangle for the top.

<g fill="#2e2e2c">
	<rect x="-20" width="40" height="8" rx="4" />
</g>

Having the rx attribute exactly as half the smaller size, as half the height, means the ends are perfectly round. Past the rectangle, a <path> crafting the rotund container with an overblown arc.

<g fill="#2e2e2c">
	<!-- ... -->
	<path d="M -16 4 a 22 22 0 1 0 32 0" />
</g>

Finally, two rounded lines extending from the sides to support the piece.

<g fill="none" stroke="#2e2e2c" stroke-width="6" stroke-linecap="round">
	<path d="M -5 20 l -10 20" />
	<path d="M 5 20 l 10 20" />
</g>

With the same color in the fill and stroke attributes the elements coalesce in the solid recipient.

Flames

The <path> element is once more equipped for the heat source. A couple of bezier curves, pointing upwards, work to create a single candle-lit figure.

<path fill="#f04545" d="M 0 0 c -3.5 0 -4.5 -4.5 0 -6.5 4.5 1.5 3.5 6.5 0 6.5" />

To detail the visual you can add another, smaller shape right on top. Another set of curves shining in the heart of the larger whole. Here you can carefully manicure a d attribute with smaller values. Or, use the very same string and scale the result down.

<!-- ...path -->
<path
	transform="scale(0.5)"
	fill="#e8da38"
	d="M 0 0 c -3.5 0 -4.5 -4.5 0 -6.5 4.5 1.5 3.5 6.5 0 6.5"
/>

As you draw the shape from the bottom, the scaling operation has the second path reduce itself from the low origin.

Add a soft stroke around both shapes.

<g stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
	<!-- ...paths -->
</g>

And making sure to use the same color as the fill attribute, you eventually breathe life into the fire.

No matter how beautiful, a solitary flame is not enough for our cooking needs, though. You can actually give an id attribute to the shape and repeat it right afterwards, but for the sake of clarity, define a group in a element.

<defs>
	<g id="flame">
		<!-- ...paths -->
	</g>
</defs>

To show the single flame back refer to the identifier through the <use> element. To complete the set repeat the instructions with different horizontal offsets.

The order here matters. To have the outer shapes behind include the copies before the flames in the middle.

<use transform="translate(8 0)" href="#flame" />
<use transform="translate(-8 0)" href="#flame" />
<use transform="translate(4 0)" href="#flame" />
<use transform="translate(-4 0)" href="#flame" />
<use href="#flame" />

Beside the source order, SVG transformations help tremendously. Not only you are able to place the flames wherever needed. Through the rotate function you can spread the fire outwards.

<use transform="translate(8 0) rotate(28)" href="#flame" />
<use transform="translate(-8 0) rotate(-28)" href="#flame" />
<!-- ... -->

And with the now-familiar scale keyword you can even reduce the size as you move away from the center.

<use transform="translate(8 0) rotate(28) scale(0.8)" href="#flame" />
<!-- ... -->

How much to angle, how much to scale down each additional pair is a matter of taste, and one of the most entertaining parts of writing SVG in a code editor. Experiment with different values until you find a heart-warming sight.

Goo

Even with a limited number of SVG elements, it doesn’t take much to embellish the visual. Consider the stylized cauldron. Add a few colorful lines on the right end — perhaps through <path> elements with a noticeable, yellow-ish stroke — and the concoction becomes much more believable.

You can’t see inside, but there is no doubt that something is warming up, and the exquisite elixir is going to reach the surface and escape the frame. Especially with such kindling.

Bubbles

Let’s exaggerate further. Reaching the boiling point it is only natural to spot a few bubbles, and a perfect opportunity to rehearse the workflow introduced with the flames. One circle element works as a base.

<circle r="4" fill="#f6c159" />

A smaller, lighter circle slightly off center promises a light source from above.

<circle r="4" fill="#f6c159" />
<circle r="1.5" cx="1" cy="-1" fill="#f1daae" />

One definition — similarly to the flames we enjoy more than one copy.

<defs>
	<g id="bubble">
		<!-- ...circles -->
	</g>
</defs>

Multiple use elements.

<use transform="translate(10 -16)" href="#bubble" />
<use transform="translate(-2 -10) scale(0.8)" href="#bubble" />
<use transform="translate(-12 -20) scale(0.6)" href="#bubble" />

Positioned and scaled to your heart’s content.

Spoon

One last instruction before we dive in the entertaining realm of cascading stylesheets. Add a line just behind the cauldron.

And the scene is set. After all, it would be irresponsible to leave the preparation unattended.

CSS properties

As in the kitchen, it is helpful to have a plan. The goal is to update the visual in a few ways, but always through the transform attribute. Or, to be technical, through the transform property.

.element {
	transform: scale(1.25);
}

Thanks to CSS specificity, the transformation takes precedence on whichever value is set in the markup.

Back to the plan. It would be most charming to have the flames grow in size and perhaps even flicker. On top of this animation we can then treat ourselves. Thicker gooey lines, bubbles actually floating, the spoon helpfully stirring the mixture/potion. You might be surprised how much is possible with just a few functions.

Flicker

To scale the flames you could update the <use> elements. Add a class on the instances.

<use class="flicker" href="#flames" />

Resize the copy with the appropriate function.

.flicker {
	transform: scale(0.7);
}

This works swimmingly for the central flame, but for the neighboring copies you would need to meddle with the SVG syntax to update the origin point. The point from where the animation takes place.

<g transform="translate(8 0) rotate(28) scale(0.8)">
	<use class="flicker" href="#flames" />
</g>

For a more detailed explanation, I’ll gladly refer you to a couple of articles devoted to SVG transformations and the concept of origin, but in a few words, you would need to apply a transformation beforehand so that you scale the flame from the bottom. Exactly as you first resized the <path> to draw the brighter body.

However, and at least for the current project, there is a better way. Add the class on the definition itself.

<defs>
	<g class="flicker" id="flame">
		<!-- ...paths -->
	</g>
</defs>

And accepting you are going to change every instance in the same manner, you can then move on to the actual animation.

Easing functions, durations. Even the structure of the keyframes is ground for exploration. For the possibly simplest change scale the flame down and restore the size to the original measure.

.flicker {
	transform: scale(0.7);
	animation: flicker 6s infinite;
}

@keyframes flicker {
	50% {
		transform: scale(1);
	}
}

A subtle effect with hopefully grander consequences.

Float

Growing with more impressive changes, we are going to move the bubbles with the translate function. Here we’d like to change the instances directly, so first place the circles with a prior group element.

<g transform="translate(10 - 16)">
	<use class="float" href="#bubble" />
</g>

The effect is then simple, but remarkable as you consider the source order. The moment you draw the shapes before the cauldron.

<svg>
	<!-- ...bubbles -->
	<!-- ...cauldron set -->
</svg>

A translation moves the circles down, but also behind. Out of sight.

.float {
	transform: translate(0px, 30px);
}

Animating the value back to the first coordinate works to then have the bubbles surface back up.

.float {
	transform: translate(0px, 30px);
	animation: float 10s infinite;
}

@keyframes float {
	to {
		transform: translate(0px, 0px);
	}
}

Here you can continue the exploration of CSS properties. Again different durations, specific timing functions, but also custom delays on the individual use elements.

<use class="float" style="animation-delay: 3s;" href="#bubble" />

Little tweaks which add variety and are bound to delight the eye.

As the elements reach the top the animation starts immediately and from the bottom. It’s only natural, however, to see the bubbles pop out of existence.

Stir

The wooden utensil patiently waits on the left side of the cauldron, but we finally want to move the spoon to and fro, from side to side.

A horizontal translation would be enough, but another function offers a different twist. Say you draw the line not as a diagonal, but a vertical segment. From the bottom.

<path d="M 0 0 v -30" />

If you then rotate the element, the line turns around the specific hinge. Much similarly to a windshield wiper.

Tram

You do need to position the path first, so that it is visible behind the cauldron, but the result promises to be worth the effort.

<g transform="translate(...)">
	<path class="stir" d="M 0 0 v -30" />
</g>

Target the element with your dearest selector and apply the rotation with a most daring animation.

.stir {
	transform: rotate(-30deg);
	animation: stir 6s infinite;
}
@keyframes stir {
	50% {
		transform: rotate(30deg);
	}
}

Translate, rotate. I mentioned the functions one at a time, but as a final playground you can try and mix them up. You could translate the spoon and tip it at the same time, perhaps with a smaller angle. Enough to stir character in the animation.

Wrapping up

A handful of SVG elements. A handful of CSS properties. Hopefully you are able to get past the verbose ramblings and see how much is possible even with a short shopping list.

You now have a sweet loading animation, and unlike most recipes, a considerable amount of freedom to achieve much more.

Starting with the gooey lines. I almost forgot to have them grow in size, so let me add the feature without much explanation. A one time change as the animation begins.

Once again a small update with a disproportionate result, completing a loader which oozes in flair.