Spicing Up the UI: a Guide for Approaching CSS Animations

August 3, 2021 By Mark Otto Off

I guess every front-end developer had that moment in their career when work on a website was already at the finish line, but the website looked like it lacked some life. Users weren’t able to interact with the design, and the only plan for making it more dynamic was to turn the cursor into a pointer via cursor: pointer to indicate links and buttons.

Today, a high-quality user interface is not complete if it lacks interactivity and smooth motion — especially with the number of instruments available to web developers.

Here, we’ll discuss different approaches to creating animations on the web and spicing up the UI.

Basic principles of animation

First, let’s cover why animations are so important.

Every day we interact with objects and observe objects’ reactions to those interactions. If you throw a ball from some height, its speed will be continuously increasing right until it hits the ground. After that, it will bounce a few times, and every jump will be shorter than the previous one, right until the ball finally stops.

These are the laws of physics. We are used to observing objects’ responses, and having objects provide us with some sort of response is an approach common for modern design. E.g., you can hear a shutter sound on your mobile phone when you take a new photo. It’s designed this way because users like products that work similarly to what they’re used to. Users like it when it’s predictable how a product reacts.

Let’s go through four main principles of animation:

Suitability. Animations must suit the context of the website. Animations must work either as responses to users’ actions or as a silent background that doesn’t draw too much attention. The website should never “scream” with animations.

Stability. Animations must compliment the UI, not break it. No content should be jumping or falling apart when the animation starts.

Unity. Pay attention to the timing and smoothness of animations. Timing of the animations of basic UI elements must be synchronized (buttons, links, etc.). It’s common sense to separate timing parameters and easing in stand-alone variables.

Usability. It’s not enough for elements to only move smoothly. Usability also matters. An animation too long will most probably interrupt the user experience rather than make it more pleasant.

Just think of animations as of a drummer in a band: nobody pays attention to a drummer until they fail.

CSS-animations

The easiest way to make the website feel more cheery is to use transition or animation properties in CSS. Transition is used for making the animation smooth, but it’s not a function that makes an object animated. The animation starts when the property changes. E.g., you can write transition: color 0.3s and change color on : hover.

On the contrary to transition, the animation function launches the animation immediately. Most of the time, I use transition since animations should respond to users’ actions. Animation function can be used to animate the background.

Without animations, the UI isn’t attractive. You may have an excellent website, but when it feels “cold” and doesn’t respond to the user’s actions, it feels unfinished or broken. For example, the menu below begs for transition:

What if you add a bit of smoothness?

Keep in mind that we didn’t change how the menu acts when we hover over a link or move between lists. We only added a transition to some actions. It looks much better now, right?

Still, something is still missing. When you move between lists, all elements are animated in the same way, which doesn’t serve a native experience. Just remember what happens when the train starts moving or when cars start driving on the green light — they all start their motion with an inevitable delay.

Let’s apply this principle to animation. Consider the element a user clicks on to be “leading”, and others “to follow”. Technically, we’re adding +1 to the sibling elements’ index (i) and apply transition-delay to each element i * 0.1s. The further the element is from the “leading” element, the more significant the animation delay.

Such an approach has the name of a stagger animation effect. You can check a good article about it.

You can find more animation samples on the website called Hotel spider.

In CSS, you can set transition-delay depending on the element’s position on the list:

.selector:nth-child(1) { transition-delay: 0.1s;
}
.selector:nth-child(2) { transition-delay: 0.2s;
}
.selector:nth-child(3) { transition-delay: 0.3s;
}

With preprocessors you can use loops to generate required styles:

@for $i from 1 through 10 { &:nth-child(#{$i}) { transition-delay: #{$i/10}s; }
}

Speed of the website

When we speak of the website performance, the first thing that comes to mind is the page loading speed, and points websites receive at Google’s PageSpeed Insights. However, besides those numbers, you should also think of user experience.

When there is a freeze, e.g., when a user sends data from the contact form to the server or switches between pages, you could show them an animation. It would distract their attention, so a user doesn’t notice that loading takes too much time.

If you are writing in plain HTML (no React or Angular), I recommend using the barba.js library for organizing smooth transitions between pages. It allows creating navigation similar to one in Single Page Applications.

FPS: How it works

Any animation is the transition of an object from one state to another.

To change from 0 to 1 in a specific time t, we split the process into iterations, and 0 changes in certain intervals until it becomes 1. Animating from 0 to 1 is the basics for building anything.

If you learn to animate from 0 to 1, you’ll be able to calculate the position of any element using the interpolation formula.

This is the formula of linear interpolation. In our case, when an x changes from 0 to 1, y will change linearly from a to b.

60 FPS

In a browser, animation speed is calculated according to 60 FPS (frames per second). Why 60 but not 30 or 24?

24 FPS is the TV standard. In general, our brain needs only 10–12 FPS to start treating separate frames as an animation. When the cinema was born, silent movies were shot with time frames from 12 to 40 FPS. Back then, it was easy to manage the scenes’ swiftness by increasing or decreasing the FPS.

The 24 FPS standard appeared when movies got sound, and you had to sync video and audio. While the ±5% is hard to notice when it comes to video, such a difference is critical when we speak about sound. 24 became a standard because it is the smallest number that can be divided by 2, 3, 4, 6, and 8 at the same time.

This has significantly simplified the video editing process. When a video plays at a speed of 24 FPS, you may notice that complicated and fast scenes blur. Such an effect is called motion blur. The only way to get rid of it is to increase the frame rate.

30 FPS is used in the game industry to create complicated scenes and without the blur effect. Additional six frames allowed to solve the blur issue, plus didn’t overload the hardware, and the number 30 was selected randomly. In the USA, the AC frequency is 60 Hz, and most TV screens and monitors can update the picture 60 times per second. Basically, if you draw every second frame, you get 30 FPS. Even in modern games, you choose between 60 FPS but a worse picture and 30 FPS but better quality.

60 FPS provides a very high-quality motion effect and helps to avoid blur. Today, it’s standard for animations.

There are higher rates, like 120 FPS, but they are made for specific cases and require particular hardware (at least a monitor supporting such FPS). Mostly it’s professional gamers who demand high FPS — they can feel the difference as they’re playing very dynamic games. For an average user, 60 FPS is more than enough.

You can read more about the frame rate here. You can compare pictures with different frame rates here.

In animation for browsers, we use 60 FPS. So one frame takes about 16 ms. You have to agree, that’s not much. That is why it’s important not to overdo your animations. When the browser doesn’t have enough time to calculate 16 ms, the next frame gets skipped, not delayed. The browser will decrease the frame rate, which will affect the quality of the picture.

FPS. Debugging

You can check the current frame rate in Google Chrome using Chrome DevTools. 

One way is to open DevTools and launch the Command Menu (Windows: Ctrl + Shift + P; Mac: Cmd + Shift + P). Then type FPS into search and select Show frames per second (FPS) meter.

To hide the FPS meter, press Hide frames per second (FPS) meter.

Another way is to open the Rendering tab and select Frame Rendering Stats:

In the previous version of Google Chrome, the FPS meter was showing the current real frame rate.

After the update, you can find more detailed stats:

The percentage of frames rendered on time (16 ms per frame) informs how well the website performs — the higher the number, the better. 

In my work, I use the FPS extension built by Yuri Artyukh every day. It allows you to check if the website functions fast enough. I only use Chrome’s functionality when I find a problem, and I need to debug and see where exactly FPS drops.

You can find out how CSS animations perform in the animations folder. Here you’ll find timings, delays, and easing curves. You can edit all of that and relaunch animations with corrected values to select the best version.

Regarding rendering in browser

If you read the documentation on transition, you’ll see that you can animate all countable properties. So you can animate via opacity from 0 to 1, but you can’t do display: none to display: block. It’s technically impossible to split transition values into frames in display.

By the way, if you need to hide an element and make it unclickable, do visibility: hidden and delay the animation. You can find a detailed description of the trick in this article. I’ll be pleased if this helps you to fix an issue with the modal window or burger menu opening/closing.

Let’s get back to numbers. In CSS, there are plenty of countable properties. When animated, some move smoothly while others “jump”.

To understand what properties can be animated almost without any additional resources, let’s try to understand how rendering in a browser works, and what exactly happens when we change styles.

  • Calculate styles does what it says, and here we use CSS.
  • Flow is about building the page structure and calculating elements and margin sizes — for each element to take its designated place on the page.
  • Paint is about page visualization. There is a rendering of fonts and images, and text or background colors and shadows apply to the content.
  • Composite layout is about elements being drawn layer by layer in the correct order. It’s easy to notice when one element overlaps another, and you need to place them in the right order with the help of z-index. When rendered using transform or opacity, elements are published to a different layer, and it helps to visually transform them. At the same time, they stick to their order, and it also helps to control opacity.

You can read more about this in the article on visualization optimization from Google.

Our task is to shorten the number of steps after styles re-calculation. When we change the style, the browser identifies whether it needs to recalculate the position of elements, repaint colors, or only work with the element within its composite layout

A perfectly done animation requires only one step — the composite layout. That’s where transform and opacity help. When you use those two, your animations will be smooth and pleasant.

When people learn that it’s not recommended to animate anything that causes reflow of size and margins, they immediately ask, “How do I change those then?”. There were only one or two moments when I had to animate something that causes reflow in my whole career. Transform and opacity cover 99,9% of all needs. Of course, sometimes you may also want to animate colors but it will come more expensive than transform and opacity.

Then, there is also an optimization trick. Instead of animating the background-color feature using transparent, we can add a fake element :: before with the required font color and animate its opacity.

/* bad */
.selector { background-color: transparent; transition: background-color 0.3s;
}
.selector:hover { background-color: red;
}
/* good */
.selector::before { background-color: red; opacity: 0; transition: opacity 0.3s;
}
.selector:hover::before { opacity: 1;
}

The main job is not to cause reflow. You may read a detailed explanation of how changing the attribute influences processes in browsers here on the CSS Triggers website.

However, not only CSS changes can trigger reflow. Beware when using DOM methods through JavaScript. E.g., estimating the window width or the current position of the scroll has the same effect. If you’re applying “expensive” methods for rendering each animation frame, it will be hard to stick to 60 FPS.

You can read more on what methods have a terrible influence on performance here.

The most common approach here is animating during the scroll — when the content of a page is hidden until the user scrolls down to the section they need, and the content loads. Back in the day, you had to estimate the page’s scroll, window size, and the distance from the beginning of the document to the required element — all to understand if the element gets into the current viewport. All the methods that caused reflow were up and running as soon as a user started scrolling down the page.

The system overload is quite significant. The modern browsers’ APIs now include the 

Intersection Observer. It helps to define whether the element is contained within the viewport and sends the callback each time the element is in or out of visibility. Since the browser’s native API is involved, the performance is better than the manual calculation of the element’s position during the scroll. There is a good article covering this topic on CSS Tricks.

Acceleration

For an element’s animation, browsers define its coordinates on the page and how it interacts with other elements. When we animate using properties that cause reflow, browsers must recalculate the element within the whole page’s limits. This process costs a lot for the browser, especially if we’re talking about heavy pages rendering many elements.

The cheapest way to deal with the issue is to transfer the element into a separate layer and recalculate its coordinates. That is actually how transform or opacity animations work. A browser creates a separate layer with the required element and a layer with the page without this element. Then, it recalculates the coordinates of a single element (the one we’re animating) within the limits of the layer. Then layers are sent to the GPU for rendering.

Today, you can see how a page splits into layers and how each is animated inside a browser. In Chrome, you can find it by opening the Layers tab.

You can even see your page in 3D there.

When the browser animates using transform, FPS drops. In such a case, we can utilize the GPU to calculate each animation frame’s element’s state. The GPU’s main advantage is that GPU operates thousands of cores, all built strictly for performing mathematical operations and much more than the CPU with a few cores.

As you remember, the animation is just math, so calculating the state of an element using GPU is a smart move.

There’re a few ways of making a modern browser calculate the animation using the video card:

  • translateZ(0), translate3D(x, y, 0). In general, browsers can work with 3D animation in CSS, and it’s calculated using GPU. Currently, we don’t need 3D animation; however, we can say that the element has moved along the z-axis for 0 pixels. The browser will have to calculate the position of the element using GPU.
  • will-change: transform. Using the will-change function in CSS, we ask our browser to optimize its resources for the animation of a particular element.

Here comes a question, why don’t we just write * {will-change: transform} or * {transform: translateZ(0)} and forget about optimization issues? The thing is that the video card is a computer on its own, so the browser has to prepare the data to send it to GPU and then to review the response. So when we create many layers that we do not use, we make the browser utilize the extra resources to communicate with GPU, which harms FPS. Below, you can see an example of using * {transform: translateZ(0)}

As you can see, a layer was applied over each element, so the browser got stuck each time I tried to scroll down with the layers tab open.

This is why you should not optimize blindly — you can make things even worse. An excellent example of smart optimization would be adding will change: transform using Javascript before a complicated animation launch and deleting it after the animation was rendered.

Here are a few good articles on animation optimization that you should read:

3D in CSS

I’ve already mentioned adding translateZ(0) or translate3d(x, y, 0) as an animation optimization hack. However, if you change from 0 to another number. Nothing will change. To turn on 3D animations in a browser, you have to add perspective to the parent element, so you’ll apply 3D animations to elements within the container and see them work.

.box { perspective: 500px; &:hover { span { transform: translateZ(200px) } }
}

Find an example on codepen here.

In production, I used this approach for on-load animations.

For those who want to understand how 3D animations work in CSS in more detail, I’m leaving a link to a useful guide. The main idea is that you have to apply perspective to the parent element. Afterward, you can experiment.

Where to get inspiration from

To grow towards understanding motion UI development, you have to develop your vision level. I mean that you have to expand your perspective and understand the capabilities of modern browsers. You don’t have to create all possible animations, but you have to know what’s possible. Then, the practice will help.

You can get inspiration from visiting dedicated websites:

  • awwwards — here dozens of new websites are published each day, and you vote for the best. You’ll be able to see implementations of various ideas. If you decide to do the reverse-engineering of the animation that you liked, you’ll be able to grow your skills fast.
  • codrops — it’s a list of articles about the most creative website designs. You can find code samples and guides to creating different animations. I recommend following this website both for professional development in your job and inspiration.
  • Yuri Artyukh’s YouTube channel — here you can find videos covering very different front-end development and animation aspects, with detailed explanations and code samples.

To sum up

In the end, I’d like to give you a few recommendations that will help you become a better web developer:

  • Dig deeper. Always try to understand why one thing works this way and not another way. Knowing small details and utilizing that knowledge is what makes you stand out among other developers. This isn’t only about animations but also development in general.
  • Apply knowledge to practice. When you see some animation you like, learn how it’s been done and create your own version. When watching a live coding session, repeat after it. Knowledge is best absorbed when put into practice, not into the bookmarks folder.
  • Expand your perspective. Study different resources, check how other websites are built. To put it shortly, stay open-minded.
  • Do not blindly optimize. Follow the metrics and learn to debug the problem first.
  • Become better in Maths. You don’t have to become a Ph.D., but you have to understand geometry and trigonometry basics to understand animations better. For example, once I had a task to create pagination in a slider, where dots would spread around in a circle, and the circle was being painted (depending on the number of the slide):

At the same time, the number of dots could change depending on the number of slides, and their coordinates had to be calculated on the go. If I didn’t know the basic formulas, e.g. a formula for finding the coordinate of a specific place in the circle, I wouldn’t have completed the task.

Afterword

My commercial experience of front-end development began when I joined a company specializing in web animation. There, I learned and implemented many different approaches to creating a high-quality UI — from simple animations in CSS to drawing in Canvas and using a magical technology called WebGL. I was actually the first person in the company to start using WebGL in production, which provided me with some sort of perks at work. Thanks to nice animations, my websites were getting into awwwards, which was an admirable achievement for a front-end developer.

To help people enjoy the results of their work was my primary motivation for writing this article.

Related Articles