SVG Animation Project: I’ll guide you through my coding process of a rain machine built with HTML, CSS, and a little JS

Wooo look at those keyframes rain!

Over the course of a weekend, I built a cute little animated rain machine. I’ll demonstrate my coding process here. This project uses CSS keyframe animations applied to SVG files. If you have a basic grasp of CSS, using keyframe animations is a friendly entry point into playing with animation.

The thing I love most about this type of project is that I can sit and fiddle with it while having the TV on in the background, and each change feels pretty experimental and modular. You can work towards an idea without planning exactly how you’re going to do every step, and just kind of try things out as you go. I get a lot of instant gratification out of seeing the visuals come together bit by bit, and build on top of each previous change.

You may have seen SVG animation that uses a “line drawing” effect, where a line doodles itself across the page. I encourage you to read this great post on CSS Tricks for a thorough introduction to this effect. I used this line drawing technique to fire my lightning bolts. This uses some CSS properties that may be unfamiliar, like stroke-dasharray and stroke-dashoffset.

This project also uses animations that work with CSS properties you already know and love. The recognizable CSS properties of background-color and transform:translate(Y) are animated to create pops of light in the sky and falling raindrops.

The standard way a to create SVG drawings is through Adobe Illustrator. It’s not the only way, though! If (like me) you don’t have Illustrator, I’m going to demonstrate a free online tool for drawing SVGs. I found Method Draw to be a great way to draw basic shapes with a pen tool and use pre-made shape stamps. The shape stamps gave me happy flashbacks to junior high computer class, and there’s actually a pretty decent library of shapes built in.

To start, I knew I wanted to have a drawing of lightning with many branches. I was easily able to draw what I wanted, and save as SVG. I opened the SVG file in my code editor, copied and pasted the SVG code into the body of my HTML file.

Note: SVGs can be used either in a normal img tag, like

<img src=”images/kitty.svg” alt=”cute cat”>

or used inline, where your SVG code itself is used as an HTML element, like so:

<svg xmlns=”http://www.w3.org/2000/svg" xmlns:xlink=”http://www.w3.org/1999/xlink" version=”1.1" x=”0px” y=”0px” viewBox=”0 0 100 125" style=”enable-background:new 0 0 100 100;” xml:space=”preserve”>
<path id=”one” d=”M38.8,54.8c-0.8,0–1.5–0.7–1.5–1.5c0–2.7–2.2–5–5–5s-5,2.2–5,5c0,0.8–0.7,1.5–1.5,1.5s-1.5–0.7–1.5–1.5c0–4.4,3.6–8,8–8 s8,3.6,8,8C40.3,54.1,39.6,54.8,38.8,54.8z”/>
<path id=”two” d=”M74.1,54.8c-0.8,0–1.5–0.7–1.5–1.5c0–2.7–2.2–5–5–5s-5,2.2–5,5c0,0.8–0.7,1.5–1.5,1.5s-1.5–0.7–1.5–1.5c0–4.4,3.6–8,8–8 s8,3.6,8,8C75.6,54.1,75,54.8,74.1,54.8z”/>
<path id=”three” d=”M62.9,68.7c0.1–0.7–0.3–1.3–1–1.6c-0.8–0.3–1.6,0.1–1.9,0.9c-0.4,1–2,2.1–4.2,2.1c-2.4,0–4.1–1.2–4.3–2.4l2.6–2.6 c0.6–0.6,0.2–1.5–0.6–1.5h-7c-0.8,0–1.2,1–0.6,1.5l2.6,2.6c-0.1,1.2–1.8,2.4–4.3,2.4C42,70,40.4,69,40,68c-0.3–0.8–1.2–1.2–1.9–0.9 c-0.7,0.2–1,0.9–1,1.6c0,0.3,0,0.7,0.2,1c3.3,5.1,8.1,8.1,12.6,8.1s9.3–3,12.6–8.1C62.8,69.4,62.9,69,62.9,68.7z M44.7,73 c2.2–0.1,4.1–0.9,5.3–2.1c1.2,1.2,3.1,2,5.3,2.1c-1.7,1.2–3.6,1.8–5.3,1.8S46.4,74.2,44.7,73z”/>
<path id=”four” d=”M95.9,62h-2.6c0.9–3.3,1.5–6.8,1.5–10.3h0c0,0,0,0,0,0c0,0,0,0,0,0l-0.6–40.8c0–1.2–0.6–2.2–1.6–2.8c-1–0.6–2.2–0.7–3.2–0.2 L71.5,16c-6.5–3.3–13.9–5–21.4–5c-7.5,0–14.9,1.8–21.5,5L10.9,7.8C9.8,7.4,8.6,7.4,7.6,8.1c-1,0.6–1.6,1.7–1.6,2.8L5.5,51.7 c0,3.6,0.5,7,1.5,10.3H4.1c-0.8,0–1.5,0.7–1.5,1.5S3.3,65,4.1,65H8c0.9,2.5,2.1,4.9,3.6,7.1L6,74.4c-0.8,0.3–1.1,1.2–0.8,2 C5.4,77,6,77.3,6.6,77.3c0.2,0,0.4,0,0.6–0.1l6.1–2.5c1.4,1.8,2.9,3.6,4.6,5.2l-5.4,5.4c-0.6,0.6–0.6,1.5,0,2.1 c0.3,0.3,0.7,0.4,1.1,0.4s0.8–0.1,1.1–0.4l5.5–5.5c7.9,6.6,18.4,10.6,30,10.6c11.5,0,21.9–4,29.9–10.5l5.4,5.4 c0.3,0.3,0.7,0.4,1.1,0.4s0.8–0.1,1.1–0.4c0.6–0.6,0.6–1.5,0–2.1L82.2,80c1.7–1.6,3.2–3.3,4.6–5.2l6,2.5c0.2,0.1,0.4,0.1,0.6,0.1 c0.6,0,1.1–0.3,1.4–0.9c0.3–0.8,0–1.6–0.8–2l-5.4–2.2c1.5–2.3,2.7–4.7,3.7–7.2h3.6c0.8,0,1.5–0.7,1.5–1.5S96.7,62,95.9,62z M85.8,71.1L78.2,68c-0.8–0.3–1.6,0–2,0.8c-0.3,0.8,0,1.6,0.8,2l6.9,2.9c-1.2,1.5–2.5,2.9–3.9,4.2l-4.6–4.6c-0.6–0.6–1.5–0.6–2.1,0 c-0.6,0.6–0.6,1.5,0,2.1l4.5,4.5c-7.4,6–17.1,9.6–27.7,9.6c-10.7,0–20.5–3.7–27.8–9.7l4.4–4.4c0.6–0.6,0.6–1.5,0–2.1 c-0.6–0.6–1.5–0.6–2.1,0L20,77.8c-1.4–1.3–2.7–2.8–3.9–4.3l6.8–2.8c0.8–0.3,1.1–1.2,0.8–2c-0.3–0.8–1.2–1.1–2–0.8L14.3,71 c-1.2–1.9–2.3–3.9–3.2–6h10c0.8,0,1.5–0.7,1.5–1.5S22,62,21.2,62H10.1c-1–3.3–1.6–6.7–1.6–10.3L9,10.9c0–0.2,0.1–0.3,0.2–0.3 c0.1–0.1,0.2–0.1,0.4,0l25.6,11.8c0.8,0.3,1.6,0,2–0.7c0.3–0.8,0–1.6–0.7–2l-4.2–1.9c5.6–2.4,11.7–3.7,17.9–3.7 c6.2,0,12.3,1.3,17.9,3.7l-4.3,2c-0.8,0.3–1.1,1.2–0.7,2c0.3,0.8,1.2,1.1,2,0.7l25.6–11.8c0.2–0.1,0.3,0,0.4,0 c0.1,0.1,0.2,0.2,0.2,0.3l0.6,39.8h0l0,1c0,3.6–0.6,7–1.6,10.3H78.8c-0.8,0–1.5,0.7–1.5,1.5S78,65,78.8,65H89 C88.1,67.2,87.1,69.2,85.8,71.1z”/>
</svg>
  • Bonus: See my animated version of the above cute kitty SVG on CodePen

Part One: Lightning Animation

Okay, now to get started on the lightning. I’ve used inline SVGs instead of img tags for all the drawings in Rain Machine. This allows me to access the path elements inside the SVG, and manipulate each line individually.

To identify which lightning branch was which, I temporarily edited the stroke property inside each path to be a different colour (In SVGs, stroke and fill are properties used in place of color and background-color).

rainbow!

I could then figure out which line corresponded with each path, and follow the steps from the previously mentioned CSS Tricks post to draw the line from top to bottom.

In my JS file, I found the length of each individual line:

var path = document.querySelector(‘#one’);
var length = path.getTotalLength();
console.log(length);

And in CSS, set that length as the value of the stroke-dasharray for each of the seven paths:

#one{
stroke-dasharray: 571;
}

I then added the keyframes to draw the line from start to finish:

@keyframes animationone {
from{
stroke-dashoffset:571;
}
     to {
stroke-dashoffset: 0;
}
}

I called the line drawing animation when clicking on the lightning button:

in my JS file

function runLightning(){
$(‘#one, #two, #three, #four, #five, #six, #seven’).addClass(‘bolt-active’);
}
function resetLightning(){
$(‘#one, #two, #three, #four, #five, #six, #seven’).removeClass(‘bolt-active’);
$(‘.lightning-button’).prop(‘disabled’, false);
}
$(‘.lightning-button’).on(‘click’, function(){
runLightning();
setTimeout(resetLightning, 1000);
$(this).prop(‘disabled’, true);
})

and in my CSS

#one.bolt-active{
animation:animationone .25s 0s 3 linear;
}
Which is shorthand for:

animationone = animation-name
.25s = animation-duration
0s = animation-delay
3 = animation-iteration-count
linear = animation-timing-function

Note: If these animation properties are unfamilliar, I recommend another great post about animation properties on CSS Tricks.

I wanted each bolt to behave a little bit differently in terms of timing and repetition, so I played with the animation-duration, animation-delay, and animation-iteration-count on each of the seven lines until I got the lightning strike effect looking how I wanted.

I also varied the stroke-width property within the SVG code for each of the seven paths, so they lines have varying thickness. Once the movement looked right, I reset the stroke property of each path to be a light yellow.

Next, so that the lightning is not visible when the page loads, I added opacity:0 to each line’s initial state, and opacity: 1 to the final state of the keyframe animation.

//this code represents one of the seven lightning lines. it would be repeated for each of the other paths, with each unique stroke-dasharray value.
#one{
stroke-dasharray: 571;
opacity: 0;
}
@keyframes animationone {
from{
stroke-dashoffset:571;
opacity: 0;
}
to {
stroke-dashoffset: 0;
opacity: 1;
}
}

To make the sky pop with light when lightning strikes, I added an animation to the background-color of the body, which is called alongside the lightning flash.

in my CSS

body.bolt-active {
animation:pop .4s .2s 2 cubic-bezier(0,.94,.73,1);
}
/*(When the body has a class of dusk, the background-color of the page is a sunset gradient instead of the default black, so I’ve added a lightning flash option for that colour scheme as well.)*/
body.dusk.bolt-active{
animation:popdusk .4s .2s 2 cubic-bezier(0,.94,.73,1);
}
@keyframes pop{
from{
background:#0f0f12;
}
to{
background: #211d23;
}
}
@keyframes popdusk{
to{
background: rgba(0,0,30,.6); /* fallback for old browsers */
background: -webkit-linear-gradient(bottom, #ffd89b, rgba(0,0,30,.6));
background: -o-linear-gradient(bottom, #ffd89b, rgba(0,0,30,.6));
background: linear-gradient(to top, #ffd89b, rgba(0,0,30,.6)); /* Chrome 10–25, Safari 5.1–6 */ /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
}

in my JS

function runLightning(){
$(‘#one, #two, #three, #four, #five, #six, #seven, body, body.dusk’).addClass(‘bolt-active’);
}
function resetLightning(){
$(‘#one, #two, #three, #four, #five, #six, #seven, body, body.dusk’).removeClass(‘bolt-active’);
$(‘.lightning-button’).prop(‘disabled’, false);
}

Part Two: Rain Animation

Phew. That was fun. Isn’t SVG animation satisfying? I love that I get to see instant results, and take baby steps along the way, making small changes until it looks the way I want. Kind of like painting!

Rain time.

I knew I wanted to have multiple layers of raindrops, positioned absolute on top of each other. Back in Method Draw I used the shape library to create three panels of raindrops, and same as before, I copied the SVG code from each drawing and pasted it into my HTML.

Background:

The layer furthest to the back has the smallest drops, and the most transparent white fill colour.

Middleground:

Middle layer has medium sized drops, slightly more opaque fill colour.

Foreground:

Top layer has the biggest drops, and drops are the most opaque.

Note: the black background-color is on the body of the page, rather than the images. The SVG images have a transparent background, so in this case you can think of each SVG element layer like an animation cell. Got those 3 SVGs positioned on top of each other, now to get the animating started.

If I were to animate the background, middleground and foreground at three different speeds, I think it would look like three flat images falling down the page. Kind of stiff, like sheets of paper. My goal was to add some variation to the speed of the individual raindrops within each panel, and hopefully make it look a little more natural, and create some visual depth.

Luckily, each raindrop is one path within the SVG, so I could target each path separately based on the id. The only CSS change in the rainfall animation is to use transform to make its position on the page animate from high to low. Again, I just played around with some numbers until I got the look I wanted.

in my CSS

@keyframes fall {
from {
transform:translateY(-200px);
}
to {
transform:translateY(400px);
}
}

The variation between raindrops comes in when calling this keyframe animation. I broke the drops into many different groups, and called the same animation at a bunch of different speeds.

Again, the initial opacity of the path is set to 0, and the final state is set to 1, so that I have the ability to switch the drops on and off. I also included the animation-iteration-count value as infinite, so the animation repeats and the rain keeps falling.

CSS

#svg_1,
#svg_2,
#svg_3,
#svg_4,
#svg_5,
#svg_6,
#svg_7,
#svg_8,
#svg_9,
#svg_10,
#svg_11,
#svg_12,
#svg_13,
#svg_14,
#svg_15,
#svg_16,
#svg_17,
#svg_18,
#svg_19,
#svg_20,
#svg_21{
opacity: 0;
}
/* The fastest animation*/
#svg_11.animated,
#svg_7.animated,
#svg_20.animated{
animation: fall 0.5s linear infinite;
opacity: 1;
}
#svg_9.animated,
#svg_16.animated,
#svg_18.animated,
#svg_13.animated{
animation: fall .8s linear infinite;
opacity: 1;
}
#svg_8.animated,
#svg_14.animated,
#svg_15.animated{
animation: fall 1.2s linear infinite;
opacity: 1;
}
#svg_6.animated,
#svg_21.animated,
#svg_2.animated,
#svg_10.animated{
animation: fall 1.6s linear infinite;
opacity: 1;
}
#svg_12.animated,
#svg_1.animated,
#svg_19.animated,
#svg_3.animated{
animation: fall 1.8s linear infinite;
opacity: 1;
}
#svg_4.animated,
#svg_17.animated,
#svg_5.animated{
animation: fall 2.1s linear infinite;
opacity: 1;
}
#rain-forward #svg_4.animated,
#rain-forward #svg_12.animated{
animation: fall .2s linear infinite;
opacity:1;
}
#rain-forward #svg_14.animated,
#rain-forward #svg_2.animated,
#rain-forward #svg_9.animated{
animation: fall .4s linear infinite;
opacity:1;
}
#rain-forward #svg_17.animated,
#rain-forward #svg_3.animated,
#rain-forward #svg_1.animated{
animation: fall .6s linear infinite;
opacity:1;
}
#rain-forward #svg_5.animated,
#rain-forward #svg_7.animated,
#rain-forward #svg_10.animated{
animation: fall 1s linear infinite;
opacity:1;
}
/* the slowest animations */
#rain-forward #svg_6.animated,
#rain-forward #svg_8.animated,
#rain-forward #svg_11.animated{
animation: fall 1.5s linear infinite;
opacity:1;
}

In my JS, when the “light rain” option is selected, I called the only the background and middleground animations. When the “heavy rain” option is selected, I called all three layers of rain to animate.

$(‘#light-rain-radio’).on(‘click’, function(){
$(‘#rain-back #svg_1, #rain-back #svg_2, #rain-back #svg_3, #rain-back #svg_4, #rain-back #svg_5, #rain-back #svg_6, #rain-back #svg_7, #rain-back #svg_8, #rain-back #svg_9, #rain-back #svg_10, #rain-back #svg_11, #rain-back #svg_12, #rain-back #svg_13, #rain-back #svg_14, #rain-back #svg_15, #rain-back #svg_16, #rain-back #svg_17, #rain-back #svg_18, #rain-back #svg_19, #rain-back #svg_20, #rain-back #svg_21, #rain-middle #svg_1, #rain-middle #svg_2, #rain-middle #svg_3, #rain-middle #svg_4, #rain-middle #svg_5, #rain-middle #svg_6, #rain-middle #svg_7, #rain-middle #svg_8, #rain-middle #svg_9, #rain-middle #svg_10, #rain-middle #svg_11, #rain-middle #svg_12, #rain-middle #svg_13, #rain-middle #svg_14, #rain-middle #svg_15, #rain-middle #svg_16, #rain-middle #svg_17’).addClass(‘animated’);
      $(‘#rain-forward #svg_1, #rain-forward #svg_2, #rain-forward #svg_3, #rain-forward #svg_4, #rain-forward #svg_5, #rain-forward #svg_6, #rain-forward #svg_7, #rain-forward #svg_8, #rain-forward #svg_9, #rain-forward #svg_10, #rain-forward #svg_11, #rain-forward #svg_12, #rain-forward #svg_13, #rain-forward #svg_14, #rain-forward #svg_15, #rain-forward #svg_16, #rain-forward #svg_17’).removeClass(‘animated’);

});
$(‘#heavy-rain-radio’).on(‘click’, function(){
$(‘#svg_1, #svg_2, #svg_3, #svg_4, #svg_5, #svg_6, #svg_7, #svg_8, #svg_9, #svg_10, #svg_11, #svg_12, #svg_13, #svg_14, #svg_15, #svg_16, #svg_17, #svg_18, #svg_19, #svg_20, #svg_21’).addClass(‘animated’);
});

Okay cool! That’s about it. Definitely lots more places you could take this kind of thing:

  • making the animation of the drops splash back up a little after hitting the bottom
  • making raindrops that stick to a window and roll down
  • animating clouds
  • adding sound and making the sound adjust along with the rain levels
Have fun! Share your own animation project on codepen!