How to create a favorite animation with anime-js.

12,039 reads

@ ainalem Mikael Ainalem CTO of Norban

Evan Zodl’s Star Fractalized (3 levels) — Jorge Jaramillo — flickr

This is a follow-up on my first article on how to create a checkmark animation with anime-js. This one will go a bit deeper into how to build more advanced animations with anime-js. The previous article is more of an introductory level tutorial.

Making simple animations is great. In general, keeping things simple is a great mantra. Less is more. Yet, in certain situations you do sometimes want to go a bit further. There are these moments where you actually do want to polish certain transitions. Deformations, multidimensional animations and morphing are techniques that we see more of nowadays. A couple of years ago, looking back from 2017, these were not built with web technologies. They were more likely be in a video, some kind of pre-animated graphics or something not web (flash and the alike). Now, we’ve seen a shift away from these plugin-based/pre-rendered animations. Except though for the animated GIF which has had some kind of renaissance lately. We are also seeing more and more elaborative interactive animations. These animations are, due to the advances in modern browsers, now implemented in HTML5. Using web technologies generally gives better control over these interactions. Furthermore, it reduces the footprint for mobile devices. Reducing page load times is vital in these days. Especially now that mobile usage has surpassed desktop usage.

In this post I’ll implement a more elaborative animation using a more advanced layout. The check-mark animation in the previous post is more one-dimensional. There the animation steps occurred in sequence. Here more things are happening at the same time. This one is for marking tweets as a favourite: Twitter-Fav by Brian W from Dribble. This is a beautiful animation designed by a designer with an eye for visual details. To recreate it I’ll use a screen-capturing tool to record it off the screen. This will allow me to dissect and set the timing of the different parts. As in the previous article, let’s start out with the end result. Then let’s break them down into steps discussed one by one. Here’s the result:

Twitter-fav

Using a screen-capture tool is a great way to break down the anatomy of complex animations. It lets you stop and pause, seek to certain positions and reduce the frame rate. Doing so helps you to get a deeper understanding of how to build animations in general. If you’re in to details then make sure you use a screen recorder showing milliseconds on playback. With milliseconds you’ll be able to recreate the timing with more fine grained precision.

To recreate the whole thing I’m using a combination of HTML, CSS, SVG and Javascript. Noteworthy here is that CSS and SVG are overlapping technologies. Some things that you can create in either one of these. I tend to, as a rule of thumb, use CSS for simpler shapes. I find the CSS syntax is generally easier to work with. SVG is, in some other cases, better suited. Some things are hard to do right now in CSS like e.g. Bezier curves. All in all, it depends on the context when to use one over the other. To me, there is no clear rule anno 2017 when to use which. Sometimes I use one, sometimes the other and sometimes I use them combined.

When recreating animations it is generally good to break it down into smaller parts. Creating these parts one by one makes it easier to keep focus. This will also make the process seem less overwhelming. Actually, a bit like the composition of this article. To me this animation is best divided up into the following parts:

Downscaling the grey inactive star

Upscaling a halo

Splashing sprinkles

Finally upscaling the active star.

The following geometric shapes will then in turn create the above steps:

Bezier curves

Circles

Circles

The same Bezier curves used to draw the inactive star in a different color

Out of these four it’s only the star that stands out with a somewhat complex shape. Here, once again, a vector (SVG) editor comes in handy. Below is what it looks like in a editor such as Inkscape. Note the use of smooth nodes (the handles) to create the rounded edges.

The above looks like the below in code (omitting the outer svg node). The ‘c’ found early on in the d attribute string tells us that is a Bezier curve.

<path class=”favorite__inactive”

d=”M50.214 10.067c6.4.204 10.753 25.648 10.753 25.648s26.256–1.803 27.13 2.857c.874 4.66–20.04 16.642–20.04 16.642s9.537 24.303 5.523 26.817c-4.015 2.515–23.545–14.023–23.545–14.023S29.333 84.493 25.633 81.785c-3.7–2.71 6.657–26.472 6.657–26.472S11.234 43.94 12.383 39.108c1.15–4.832 26.55–3.393 26.55–3.393s4.88–25.853 11.28–25.648z”

fill=”#dbdedd” />

Let’s now add the first animation step, downscaling the star. Once again let’s use a anime-js timeline as it gives good control over the different parts of the animation. After adding the SVG to our HTML document the following Javascript code will do the trick:

timeline

.add({

targets: '.favorite__inactive',

scale: {

value: [1, 0],

duration: 400,

delay: 1000,

easing: 'easeInQuad'

}

})

Downscaling the star

This step should be pretty straightforward by now. Once again, the class name of the node creates a relation between the DOM node and its animation. The star scales from 1 (visible) to 0 (completely gone) in a second. Note the easing. It’s set to “in” to make star to speed up when downscaling.

Next up is creating the halo which is trickier. To create the halo let’s use two plain circles. One inner and one outer. The outer circle is opaque to set the halo color. The inner circle needs to be transparent to display other elements inside the halo. Note: The halo animation starts before the downscaling of the star ends. Thus, it’s important that elements inside the halo are visible. A SVG mask is a convenient way to create transparent parts in SVGs. SVG masks are more in detail discussed here: Clipping and masking. In essence you create a binary (black and white) masking image that you combine with other SVG content. The mask will then punch a hole through the opaque object it’s applied to. Other objects below and inside that particular object will now be visible. Let’s have a look at what this looks like in code:

<defs>

<mask id="favorite__halo-mask">

<rect width="100%" height="100%" fill="white"/>

<circle class="favorite__halo-inner" cx="50" cy="50" r="0" fill="black"></circle>

</mask>

</defs>

<circle class="favorite__halo-outer" cx="50" cy="50" r="48" fill="#feb53c" mask="url(#favorite__halo-mask)"></circle>

The first thing to note here is the last element. This is the outer circle, favorite__halo-outer. It’s as a regular SVG circle element with proportions, colors and a mask attribute. The masking syntax here is a bit weird as it refers to the mask through a url and its id. Anyway, it points to the mask element, the favorite__halo-mask. Which, in turn, contains the inner circle node, another SVG circle. This in total gives us the two circles referred to above. In essence we now have everything we need to create this part of the animation. Also worth mentioning is the white rect inside the mask. This rect marks the area which should stay intact. I.e. everything outside of the inner circle. Failing to include this node will make the mask not to work.

As a starting point the inner circle is completely gone (radius r = 0px). Here, once again let’s apply the concept of offset and delay when upscaling the circles. This is where the upscaling of the inner circle is a bit delayed compared to when the outer circle starts. Doing so will create the effect where the halo is getting thinner and thinner. In code:

timeline

// ...

.add({

targets: '.favorite__halo-outer',

scale: {

value: [0, 1],

duration: 400,

delay: 1400,

easing: 'easeOutQuad'

},

offset: 0

})

.add({

targets: '.favorite__halo-inner',

r: {

value: [0, 49],

duration: 300,

delay: 1500,

easing: 'easeOutQuad'

},

offset: 0

})

Note the difference in delays above. This gives us the following animation:

Halo animation (Slow motion)

Moving on to the sprinkles, the splash, the extra sugar on the top.

Disney’s Goofy

Splashes are no new concept when it comes to cartoons and animations. They are often used to exaggerate motion and to reinforce actions surrounding them. This animation is no different. Here the splash emphasizes and underlines the user action. The UI gives positive reinforcement to the user, creating good UX. This moment of charm will encourage the user to mark more tweets as favourites. Generally this will lead to an increase in user engagement.

There are five splashes in total and they’re positioned by even angles. They are in essence the same animation running in five different places at the same time. Reusing the same animation here is the quickest way to recreate the sprinkles part. All we need to do is to create one of the sprinkles. Repeat that animation 5 times. And, finally, spread out the 5 duplicated animations by even angles. In HTML:

<div class="favorite__sprinkle"> // 1st sprinkle

<div class="favorite__sprinkle-circle"></div>

</div>

<div class="favorite__sprinkle"> // 2nd sprinkle

<div class="favorite__sprinkle-circle"></div>

</div>

...

<div class="favorite__sprinkle"> // 5th sprinkle

<div class="favorite__sprinkle-circle"></div>

</div>

360 degrees divided by 5 equals 72 degrees, which is the value to rotate the nodes with. Also, a shift will make sure to place the third star at 6 o’clock (180deg). With some easy math we can calculate this shift to be 36 degrees (72 / 2). So let’s spread out the nodes in intervals of 72 degrees, by rotating the nodes, starting at 36 degrees. In CSS this means applying the transform rule and its rotate value. Values rotate(36), rotate(108), rotate(180), … will layout the nodes. In code this looks like:

.favorite__sprinkle {

...

transform: rotate(36deg);

}

.favorite__sprinkle:nth-child(2) {

transform: rotate(108deg);

}

...

.favorite__sprinkle:nth-child(5) {

transform: rotate(324deg);

}

The animation itself consists of 3 parts. They are: A crossfade (opacity), stretching the circle and moving the circle. To stretch the node I’m using a one dimensional scale. I noticed when writing this article that I’ve been a bit sloppy. I used the CSS height rule to stretch the sprinkles. One should use scale rather than width/height to change elements proportions. Not doing so will generally be slower. This since translate/scale/rotate are hardware (GPU) accelerated. As with all optimizations, apply them where needed and follow the guidelines. Below is what the whole sequence looks like:

.add({

targets: '.favorite__sprinkle',

opacity: {

value: [0, 1],

...

})

.add({

targets: '.favorite__sprinkle-circle',

height: {

value: [5, 12],

...

})

.add({

targets: '.favorite__sprinkle-circle',

height: {

value: [12, 5],

...

})

.add({

targets: '.favorite__sprinkle-circle',

opacity: {

value: [1, 0],

...

})

.add({

targets: '.favorite__sprinkle-circle',

translateY: {

value: [0, -28],

...

});

Sprinkles

The last and final part is the reverse of the first step. This is where the star once again scales up. Now, in its active color. I won’t cover this part as it is the same as the first step.

Voila, that’s it! Below is the source code on codepen.

Tags