In this exercise, we will use CSS and jQuery to animate an SVG scene according to the storyboards provided for us by the designer. Feel free to fork the starter SVG file and get started:

This is meant for new(ish) front-end devs who have a base understanding of jQuery and CSS - including position/number-based pseduo selectors and keyframes.


Step 1: Sun rise

Sun rising above the mountains

First, temporarily hide all the shape elements in the foreground: the clouds, raindrops, and rainbow. We'll unhide them later when we animate their individual entrances.

  /* hide all paths inside of the <g id="FOREGROUND"> */
#FOREGROUND path {
  opacity: 0;
}

Next, we are going to use CSS @keyframes and the transform property and move the sun along the Y axis, so it hides under the mountain range.

  /* name the animation sequence "sunrise" */
@keyframes sunrise {

  /* beginning of the animation - sun behind mountains */
  0% {
    transform: translateY(150px);
  }

  /* end of animation - sun above mountains */
  100% {
    transform: translateY(0px);
  }
}

Now let's assign those keyframes to the sun using CSS animation properties.

  #SUN {
  /* the name used for the animation sequence */
  animation-name: sunrise;

  /* the duration in seconds */
  animation-duration: 8s;

  /* ensures the sun stays in the final position */
  animation-fill-mode: forwards;
}


Step 2: Clouds float in

Un-hide the cloud <path> elements and give them a subtle transparency.

  /* un-hide clouds */
#FOREGROUND #CLOUDS-LEFT path,
#FOREGROUND #CLOUDS-RIGHT path {
  opacity: 0.7;
}

Using transform again, move the clouds along the x-axis so they start off-screen. Target the cloud groups (e.g. <g id="CLOUDS-LEFT">) instead of each cloud <path>.

  #FOREGROUND #CLOUDS-LEFT {
  transform: translateX(-350px);
}
#FOREGROUND #CLOUDS-RIGHT {
  transform: translateX(350px);
}

Write keyframes to animate the cloud group entrances.

  @keyframes float-in-left {
  0% {
    transform: translateX(-350px);
  }
  100% {
    transform: translateX(0px);
  }
}

@keyframes float-in-right {
  0% {
    transform: translateX(350px);
  }
  100% {
    transform: translateX(0px);
  }
}

And then assign those animation sequences to each group accordingly.

  /* both cloud groups will have the same 
animation delay, duration and fill-mode */
#FOREGROUND #CLOUDS-LEFT,
#FOREGROUND #CLOUDS-RIGHT {
  animation-duration: 7s;
  animation-delay: 2s;
  animation-fill-mode: forwards;
}

#FOREGROUND #CLOUDS-LEFT {
  transform: translateX(-350px);
  animation-name: float-in-left;
}

#FOREGROUND #CLOUDS-RIGHT {
  transform: translateX(350px);
  animation-name: float-in-right;
}

Clouds floating in progress

Okay, not bad - but it would look a bit more natural if we staggered the movement of the individual clouds as well.

Let's make a new keyframe and name it "float":

  @keyframes float {
  0% {
    transform: translateY(8px);
  }

  50% {
    transform: translateY(0px);
  }

  100% {
    transform: translateY(8px);
  }
}

And assign it to the individual clouds, each with a different delay to stagger their movement:

  #FOREGROUND #CLOUDS-LEFT path:first-child,
#FOREGROUND #CLOUDS-RIGHT path:last-child{
 animation-delay: 0s;
}
#FOREGROUND #CLOUDS-LEFT path:nth-child(2),
#FOREGROUND #CLOUDS-RIGHT path:first-child{
 animation-delay: 0.25s;
}
#FOREGROUND #CLOUDS-LEFT path:last-child,
#FOREGROUND #CLOUDS-RIGHT path:nth-child(2){
 animation-delay: 0.5s;
}

Clouds floating naturally

That's better! Now, let's code the rainfall animation.


Step 3: Rainfall animation sequence

This is where it gets fun. We want to code this so when a user clicks on a cloud, the raindrops fall. If all clouds are raining, the rainbow will appear.

First, let's make a hover state for each cloud to give the user a cue that they have to click them. Let's add a transition too, so it's not jarring.

  #FOREGROUND #CLOUDS-LEFT path,
#FOREGROUND #CLOUDS-RIGHT path {
  transition: 1s all;
}

#FOREGROUND #CLOUDS-LEFT path:hover,
#FOREGROUND #CLOUDS-RIGHT path:hover {
  fill: rgba(58,113,140,0.7); /* dark blue rain cloud */
  cursor: pointer;
}

Hover state of clouds

Great! Now let's write the keyframes for the rainfall animation sequence.

  @keyframes rainfall {
  /* we want the raindrops to be 
  up "in the cloud" and be invisible
  to start */
  0% {
    transform: translateY(-70px);
    opacity: 0;
  }

  /* the raindrops will fall off 
  the bottom of the artboard */
  100% {
    transform: translateY(260px);
    opacity: 0.8;
  }
}

And let's assign those keyframes to the raindrop paths:

  #RAINDROPS-LEFT path,
#RAINDROPS-RIGHT path{
  animation-name: rainfall; 
  animation-duration: 2s;
  animation-iteration-count: infinite;
}

Cool - they're all falling at the same time again, so let's stagger them with some delays:

  #RAINDROPS-LEFT .CLOUD-1 path:first-child,
#RAINDROPS-LEFT .CLOUD-2 path:nth-child(2),
#RAINDROPS-LEFT .CLOUD-3 path:last-child,
#RAINDROPS-RIGHT .CLOUD-4 path:first-child,
#RAINDROPS-RIGHT .CLOUD-5 path:nth-child(2),
#RAINDROPS-RIGHT .CLOUD-6 path:last-child{
  animation-delay: 0.5s;
}
#RAINDROPS-LEFT .CLOUD-1 path:nth-child(2),
#RAINDROPS-LEFT .CLOUD-2 path:last-child,
#RAINDROPS-LEFT .CLOUD-3 path:first-child,
#RAINDROPS-RIGHT .CLOUD-4 path:nth-child(2),
#RAINDROPS-RIGHT .CLOUD-5 path:last-child,
#RAINDROPS-RIGHT .CLOUD-6 path:first-child {
  animation-delay: 1s;
}
#RAINDROPS-LEFT .CLOUD-1 path:last-child,
#RAINDROPS-LEFT .CLOUD-2 path:first-child,
#RAINDROPS-LEFT .CLOUD-3 path:nth-child(2),
#RAINDROPS-RIGHT .CLOUD-4 path:last-child,
#RAINDROPS-RIGHT .CLOUD-5 path:first-child,
#RAINDROPS-RIGHT .CLOUD-6 path:nth-child(2) {
  animation-delay: 1.5s;
}

Challenge: how could you use JS to randomize these delays instead? Is there a more efficient way to write this?

Okay the rain is falling - but now we have a problem: it's raining before the clouds appear.

We don't want the raindrops to appear until the user clicks the clouds, so let's give them display: none.

  #RAINDROPS-LEFT path,
#RAINDROPS-RIGHT path{
  animation-name: rainfall; 
  animation-duration: 2s;
  animation-iteration-count: infinite;
  display: none; /* hide until click */
}


Step 4: Use jQuery to make it rain

Lucky for us, this SVG has some consistent ID and classes that make working in jQuery easy.

  $('#CLOUDS-LEFT path, #CLOUDS-RIGHT path').on('click',
function(){ 
  // add a class to the cloud on click
  $(this).addClass('raining');

  // create a variable to find the ID of the clicked cloud
  var cloudnumber = $(this).attr('id');

  // find that same variable as a class (rain drop group),
  // and fade them in
  $('.' + cloudnumber + ' path').fadeIn();
});

And let's quickly go back and style the cloud with a class of "raining" to look like the hover state:

  #FOREGROUND #CLOUDS-LEFT path:hover,
#FOREGROUND #CLOUDS-RIGHT path:hover,
/* add the class of raining */
.raining {
  fill: rgba(58,113,140,0.7);
  cursor: pointer;
}

Awesome! We're almost there.

Rain clouds on click


Step 5: A rainbow appears!

To animate the rainbow, we will use SVG line animation. To read up on how it works, check out this great article by Chris Coyier.

Write the keyframes for the rainbow animation using stroke-dashoffset:

  @keyframes rainbow {
  to {
    stroke-dashoffset: 1000;
    opacity: 0.9;
  }
}

Next, apply the rainbow animation sequence to the path elements inside the #RAINBOW group.

Add display: none; to those paths as well, so we can display them later with jQuery.

  #RAINBOW path {
  stroke-dasharray: 2000;
  stroke-dashoffset: 2000;
  animation-name: rainbow;
  animation-duration: 5s;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
  animation-delay: 1s; /* slight delay before growing */
  display: none; /* hide until all clouds are raining */
}

Alrighty, let's jump back into jQuery and write the last few lines of code to finish this off:

  $('#CLOUDS-LEFT path, #CLOUDS-RIGHT path').on('click',
function(){ 
  $(this).addClass('raining');
  var cloudnumber = $(this).attr('id');
  $('.' + cloudnumber + ' path').fadeIn();

  // HOW MANY ELEMENTS HAVE THE CLASS OF RAINING?  
  var rainingclouds = $('.raining').length;

  // IF THERE ARE 6 CLOUDS RAINING...
  if (rainingclouds === 6) {

    // A RAINBOW APPEARS!  
    $('#RAINBOW path').fadeIn();

    // THE CLOUDS TURN WHITE AGAIN
    $('#CLOUDS-LEFT path, #CLOUDS-RIGHT path').removeClass('raining');

  }

});

And voila!

Final rain and rainbow animation

View the final CodePen here.


Extensions

There's even more you can do here. I challenge you to:

  • Modify and cannibalize this SVG as much as you want
  • Animate the colour of the sky throughout the scene: orange sunrise, darker while raining, lighter when the rainbow appears
  • Randomize the cloud and raindrop positioning
  • Add typography ("Make it rain" or "After the rain comes a rainbow")
  • Add parallaxing effects to the clouds, mountains
  • Reverse the animation sequence as well so that this is on loop - fade out the rainbow, clouds, and have the sun set


HAVE FUN!
I would love to see what you come up with!

Of course, if you have any Q's, tweet me @natacoops


4,723 4 76