Animate SVG with JavaScript

null

There's so much that can be achieved natively in the browser using CSS3 or the Web Animations API, in JavaScript. Simple animations and transitions are well suited to CSS3 – whereas more complex animations need to be accomplished using JavaScript. 

The problem with the Web Animation API is browser support and the current feature set. Being a fairly young specification, this will improve in the coming years. 

In order to combat this feature and browser support deficit, we can turn to animation libraries, such as GreenSock (GSAP). GSAP gives us the ability to create complex animations and multiple timelines, with the ability to animate almost any element or property/value pair – all achieved with a simple and intuitive syntax.

In this tutorial we're going to use the GSAP library to create a few animations. The main and most complex animation we'll be creating will be transitioning the scene from day to night, we'll see how easily we can chain together multiple transitions to create complicated animations. We'll also be creating a few simple animations we'll be running constantly.

Download the files for this tutorial.

01. Document setup

To start, we need to fork the GreenSock Pen in order to trial its premium plugins. For the tutorial we'll be using an SVG which has already been optimised and pasted into our HTML editor. However, if you're using your own SVG you'll need to make sure that all of the elements have unique IDs. 

02. Create the first timeline

GSAP offers two timeline types: TimelineLite and TimelineMax. The TimeLineMax version offers access to additional features, such as the ability to repeat animations, as well as playing them in reverse among others. Our first timeline will be the water, which we will repeat infinitely and yoyo back and forth.

var animation_water = new TimelineMax({
  repeat: -1, 
  yoyo: true
});

03. Create the first animation

In order to animate the water we have another path in our SVG, hidden with a '0' opacity. We'll utilise the morphSVG plugin to transform our original water path into the new water path. We'll move the water element '12px' down on the y-axis. The two numbers at the end of the property represent the delay and the start times respectively.

animation_water
  .to("#water", 2, {
  y: 12, 
  morphSVG:"#water-2", 
  ease:Linear.easeNone
  }, 0 , 0)
;

04. Reusable properties

Since we will be reusing a number of properties and values a number of times we're going to create variables for these properties.

var animation_ease = Linear.easeNone;

05. Console logging

The GSAP library offers us the ability to get a number of properties from any given timeline. We can log these in the console to make sure that everything is working as we expect it to.

console.log(
  'animation_water duration: ' 
  + animation_water.duration()
  .toFixed(2)
);

06. Cloud timeline and console log

For every element we wish to animate separately and constantly we need to create a new timeline. We also want to log that timeline in the console as we go.

var animation_cloud = new TimelineMax({
  repeat: -1, 
  yoyo: true
});
console.log( '\n' + 
  ...	
  animation_cloud.duration().toFixed(2) + ' \n'
);

07. Cloud animation

Now that we have our timeline ready, we can create our cloud animation. The section of animation which takes the new properties can handle multiple property/value pairs separated using commas. 

Our cloud animation only needs to be subtle, so we only need to change the values by a small amount.

animation_cloud
  .to("#cloud", 3, { x: -2, y: 1, scale: 0.95, rotation: 1, ease: animation_ease}, 0, 0)
;

08. Create the night time animation

Next, we'll start to create our day-to-night animation. We'll create a variable for the cycle time and the day. The 'yoyo' setting in GSAP also enables to us to delay the animation before repeating. 

var day_night_cycle_time = 15;
var animation_delay = day_night_cycle_time / 2;
var animation_toNight = new TimelineMax({
  repeat: -1,
  yoyo: true,
  repeatDelay: animation_delay
});

09. Animate the overlay layer

Inside our SVG we have an overlay layer made of a rectangle covering the entire image with the same background gradient as our night-time background. The overlay applies the 'multiply' blend mode in order to darken the entire image. Our animation simply transitions the opacity of this element. 

animation_toNight
  .to('#nighttime-overlay', 
  day_night_cycle_time, {
  opacity: 1, 
  ease: animation_ease
  }
  , 0 , 0)
;

10. Animate the gradient

GSAP offers other tweens on top of the common 'to' and 'from' types. The tween type we need in order to animate our gradient is the 'staggerTo' tween. We can also use the 'cycle' property in order to rotate the colour wheel around to our new values.

.staggerTo('#daytime-gradient stop', 
  day_night_cycle_time, {
  cycle:{
  stopColor: ['#060414','#416584']
  },
  ease: animation_ease,
}, 0 , 0)

11. Animate the sun

We can keep adding animations to our 'toNight' animation. This time we'll add a new 'to' tween in order to set our sun. We'll set the display time to be a fraction of the cycle time in order to animate the sun before the moon. GSAP enables us to set almost any attribute. We'll use this in order to animate the 'cx' and 'cy' properties to below the hill on the right.

.to('#sun', day_night_cycle_time / 1.25, {
  scale: 0.9, 
  attr:{cx:"753", cy:"697"}, 
  ease:animation_ease}
, 0, 0)

12. Animate the moon

We'll use the same technique we used to animate the sun out of view (see Step 11 above) in order to animate the moon into view. We could achieve this using one tween, of course, but in order to create a faux arc we'll do this is in two parts. In both parts we're also going to apply a new value to the scale property. 

.to('#moon', day_night_cycle_time / 2, {
  scale: 0.9, 
  attr:{cx:"174.5", cy:"202.5"}, 
  ease:animation_ease}
, 0 , 0)

13. Animate the moon - part two

The second part of the moon animation waits for the first section to finish before it begins. Note: these two parts of the moon animation are chained together inside the animation code along with the other day-to-night properties that we're using.

animation_toNight
  ...
  .to('#moon', day_night_cycle_time / 2, {
  scale: 0.9,
   attr:{cx:"410.5", cy:"114.5"}, 
  ease:animation_ease}
  , day_night_cycle_time / 2, 0)
;

14. Animate the stars

The only part left of our day-to-night animation are the stars. We'll animate the stars into view by transitioning a number of properties. The first of them is to simply bring them into view by animating their opacity.

.to('#stars', day_night_cycle_time/2, 
  {opacity: 1}, 
  day_night_cycle_time/2, 
0)

15. Animate the stars - part two

Next we'll use the 'from' tween in order to move the stars up and rotate them from a negative angle as they animate into view. We're using some simple maths in order to calculate our animation time and delay, all based on our 'day_night_cycle_time' variable.

.from("#stars", 
  day_night_cycle_time - (day_night_cycle_time / 4), 
  {y: 150, rotation: -15, ease: animation_ease}, 
  day_night_cycle_time / 4, 
0)

16. Create the stars timeline and the console log

Now we've created our day-to-night animation we can create another constant animation to make our stars blink. We'll create the new timeline and then log the timeline duration in the console.

var animation_stars = new TimelineMax({
  repeat: -1, 
  yoyo: true
});

17. Animate the stars

Now we've created the timeline ready for animation, we need to create our blinking animation. The animation is really simple – all we want to do is reduce the opacity value. Thanks to the 'yoyo' property the opacity will animate on and off and so will make the stars look like they are blinking.

animation_stars
  .to("#stars", 0.5, 
  {opacity: 0.5, ease: animation_ease}
, 0, 0)
;

18. Delay the blinking

In the last step we're targeting the stars group in order to apply our blinking animation, however it would look much better if the stars were to blink one at a time instead of together at the same time. We achieve this by targeting each star separately and applying a different animation. 

animation_stars
  …
  .to("#star-two", 0.5, 
  {opacity: 0.5, ease: animation_ease}
  , 1.25, 0)
  .to("#star-three", 0.5, 
  {opacity: 0.5, ease: animation_ease}
  , .75, 0)
  … ;

19. Additional elements - snow!

That's it! Our day-to-night cycling animation is finished and it looks awesome, but we don't have to stop there. Since the image is in SVG we can easily add new elements to our landscape. Let's add some snow. We'll do this using two separate layers. Each layer has a collection of ellipses large enough to cover the landscape and then the same collection repeated above.

<g id="snow-bottom-layer" …>
  ...
  <ellipse …/>
</g>
<g id="snow-top-layer" …>
  ...
  <ellipse …/>
</g>

20. Create the snow timelines

We create two separate timelines for our snow in order to be able to animate them over different durations. We'll also log their durations to the console. 

var animation_snowTop = new TimelineMax({
  repeat: -1,
  repeatDelay: 0
});
var animation_snowBottom = new TimelineMax({
  repeat: -1,
  repeatDelay: 0
});

21. Animate the snow

In order to animate our snow layers we want to move the two layers along the vertical axis. By differing their durations we will get the appearance of the layers moving at different speeds. The animation works by moving the collection of ellipses along the vertical axis until the second collection is in place of the first. We then repeat the animation.

animation_snow
  .to("#snow-top-layer", 7, 
  {attr: {transform: "translate(24 -108)"}
  , ease: animation_ease}
, 0, 0)
;

Find the full collection of tutorial Pens here.

This article was originally published in issue 269 of creative web design magazine Web Designer. Buy issue 269 here or subscribe to Web Designer here.

Related articles: