technical

A Guide to animations that feels right

A study on animations that provide a sense of enjoyment and create delightful user experiences.
Oct 04, 2024
13 min read

Animation is a challenging field to master, primarily because it’s an important part of interaction design, and design itself is difficult. I see that the understanding of animations is not about how they are done but more about you. It has much to do with your general knowledge of the world around you. The more mindful and observant you are, the better you will understand animations that feel good and natural. In this article, I want to share some of the understanding that I have gained on this topic.

The key to creating delightful animations is to understand the principles behind them and apply them in a way that feels intuitive to the user.

Single-Element Animations

Single-element animations are the simplest ones. They do one thing, and they better do it right. It can be as simple as transitioning the background color of the button on hover. For eg.

If you are on mobile, tap the button then tap outside

In this example, it is not so much about which one feels good but more about which one is right. If you do not have an answer to this question, then you are missing a very critical piece of information. When you interact with an element, you expect it to respond immediately. If it doesn’t, it feels like something is not normal. When you ring a doorbell, you expect the instant sound of the bell the moment you press the switch. In the above example, both the buttons have the same attributes except the transition duration. The first one has a transition duration of 150ms and the second one has 400ms. The first one feels snappy and responsive but the second one feels slow to react.

Note

The reaction time of humans is around 200ms. And 150ms - 300ms is a good range to have for single-element animations.

Let’s take another example.

In the above example, the animations are completely different. But there is something odd with the first one. It starts very small and grows big because of the number of items it has to hold. The animation looks great but somehow it doesn’t feel right. However, the second one feels more natural. But do you know why?

Our brain is wired to process information sequentially. And that’s why it feels more natural when the items appear one by one with a little delay of say 10ms. A higher delay will make it feel slow and a lower delay will make it feel abrupt. It will be scary to see a car approaching you at a high speed and breaking right in front of you.

Since the list is long, zooming in the entire list is not very intuitive which is demonstrated by the scaling effect. However, in the second one, there is no scaling effect. The container is created and opened with a slide-down effect and along with that the items appear one by one. This is more natural and feels more intuitive. This gives the user a sense of direction.

Note

Direction in animation refers to the perceived movement of an object or element on the screen. It’s about guiding the viewer’s eye and creating a sense of flow or progression.

This is specifically important for dropdowns with a big list. Scaling a lengthy drop down from 0 to 1 feels too abrupt. You might consider increasing the animation time. But your boundary is 300ms since it’s a single element.

Let’s try the same animation by just changing the size of the button and its items and see how it looks.

In this case, the scaling effect mimics real-world physics better. For instance, when an object comes closer, it usually grows in size, and this animation captures that behavior. And since the dropdown has fewer items, the scaling effect is not overwhelming and is more playful and reactive.

Multiple Element Animations

When you are dealing with features where you have to animate multiple elements, timing is critical. In the below example, Let’s assume that the content is a form and the user has to fill the form step by step.

You will notice that the animation of the step indicator and the content are in sync. They start and end at the same time. I could have added a progress indicator in the counter itself that it’s validating the content. But I choose to keep the progress indicator on the button. This is simply because the button gets disabled and the loading indicator sort of gives that feedback. It serves the purpose of telling the user - “Wait, don’t click me again. I am working on it”. The three animations are in sync and they feel like they are part of the same process.

Animation that requires Layout Changes

When you pull out a book from a packed shelf, the remaining books naturally shift and slide into place to fill the gap. The layout allows for that change to happen. But what if you want to change the layout itself? From vertical placement to horizontal stacking? This is where layout animations come in.

Objects don’t just disappear and reappear somewhere else; they move fluidly from one place to another. Applying this principle will make the animation feel more intuitive and natural and so enhance the user experience.

When we are working with any frameworks like React, animations are much harder especially if you want to animate by adding and removing elements. Let’s see an example.

Click on an item to remove it from the grid
1
2
3
4
5
6
7
8
const itemsData = [1, 2, 3, 4, 5, 6, 7, 8];

export function DemoLayout() {
  const [items, setItems] = useState(itemsData);
  
  const removeItem = (id) => {
    setItems(items => items.filter((item) => item !== id)); 
 };

  return (
    <div>
      <div className="...">
          {items.map((item) => (
            <div
              key={item}
              className="..."
              onClick={() => removeItem(item)}
            >
              {item}
            </div>
 ))}
      </div>
      <button onClick={()=>setItems(itemsData)}>
 Reset Grid
      </button>
    </div>
 );
}

In the above example, when you click on one of the items, I filter that item out. When I do so, react removes it from the DOM immediately. So there is no way for me to animate the exit of the element. We have to tell React to wait until the animation is complete. And we can solve it by having a timeout.

const [items, setItems] = useState(itemsData);
const [isAnimating, setIsAnimating] = useState(false);

const removeItem = (id) => {
  setIsAnimating(true);
  setTimeout(()=>{
    setItems(items.filter((item) => item !== id)); 
    setIsAnimating(false);
 },300)
};

Now you have 300ms to animate the exit of the element. But can you imagine how complex it will be to handle multiple elements each having its own delay and exit animation?

For crafting delightful animations, you need to have complete control of all the elements with high accuracy. Framer Motion is one such library for creating fluid animations with physics-based concepts and with full control.

One of the components of framer motion is AnimatePresence which is meant to tackle exactly this problem. Let’s see how it works.

Click on an item to remove it from the grid
1
2
3
4
5
6
7
8
return (
 ...
    <AnimatePresence>
      {items.map((item) => (
        <motion.div
          key={item}
          className="..."
          onClick={() => removeItem(item)}
        >
          {item}
        </motion.div>
 ))}
    <AnimatePresence>
 ...
);

With AnimatePresence, React will wait until the animation is complete before removing the element from the DOM. This way we can animate the exit of the element. However, after the element is removed, I would not expect the next elements to disappear and appear in their new positions. Instead, they should move to their new positions smoothly. This is where the layout prop comes in.

Click on an item to remove it from the grid
1
2
3
4
5
6
7
8
return (
 ...
    <AnimatePresence> 
      {items.map((item) => (
        <motion.div
          layout
          key={item}
          className="..."
          onClick={() => removeItem(item)}
          initial={{ opacity: 0, scale: 0.8 }}
          animate={{ opacity: 1, scale: 1 }}
          exit={{ opacity: 0, scale: 0.5 }}
        >
         {item}
        </motion.div>
 ))}
    <AnimatePresence>
 ...
);

I have also added how the animation should start and end. I am scaling the element from 0.8 to 1 when it appears and from 1 to 0.5 when it exits.

The layout prop is designed to handle layout shifts, making it a powerful feature for creating fluid UIs. Here’s how the layout prop works internally:

  1. Framer Motion monitors the element’s bounding box (size and position) before and after a layout change (due to DOM updates).
  2. It calculates the difference between the old and new layout (position and size).
  3. It then animates between the old and new bounding boxes using GPU-accelerated CSS transforms (translate, scale), ensuring smooth transitions.
  4. It avoids layout thrashing and ensures efficient reflows and batching layout calculations to reduce the performance overhead.
  5. Handles nested and complex layouts by ensuring all child elements animate in sync with the parent layout changes.

More complex layout animations

Earlier we saw that we can animate the exit of an element and the entrance of the next elements. But what about animating an element from a relative position to a fixed position?

Let’s see an example. What do you think about the below animation when you click on the avatars?

To me, it felt a bit too much. I see new elements appearing and everything is happening just so fast. Everything relocating to a new position at the same time and this is too much for me to process at the same time. Also, the closing of the modal is too abrupt.

Let’s ignore the too much part. How is the concept though? It could have been a popup window as well which appears from the top or bottom of the screen.

Important

When a container with avatars transitions into a modal, the animation preserves contextual consistency. This continuity reduces cognitive load, helping users remain oriented within the interface. As elements shift and reshape, users don’t have to mentally “teleport” to a new interface element. Instead, they are gently guided into the new state.

The idea and the concept are good, but how did it feel? I think we can do better by breaking down the animation into smaller parts and sequence them properly. If you look at the slow motion, you can see that all the elements are out of sync.

Visualize a crowd doing “the wave”:

  • Sections of the crowd stand up in sequence, creating a flowing visual.
  • If everyone stood up at once, it would feel chaotic.
  • Staggered animations create a smoother experience, like the wave.

This is a very complex animation but with appropriate timing, it didn’t feel as overwhelming as the earlier one. So what happened?

  1. On clicking the avatars, the container shrinks just a bit and bounces to expand.
  2. The heart and the counter disappear immediately, as they are not the primary focus.
  3. The modal header and footer appear first.
  4. Then the avatars change from horizontal to vertical position.
  5. Once the avatars are halfway to their final position, the names fade in immediately.
  6. The like count slides in from the left.
  7. With all the elements in place, the modal flexes slightly like a spring.

Want to know when I publish new content?

Enter your email to join my free newsletter.

When you close the modal, you will see there is a little bounce effect once it reaches its final position.

Note

Once the user has identified the source and seen the final element, there’s no need to prolong the closing process. They’ve obtained the information they were looking for.

When a notification pops up on a screen, it typically appears slowly to draw the user’s attention. However, when the user dismisses it, it often disappears quickly. This is also very common with dropdowns.

CSS VS JS-based animations

You can choose between CSS and JavaScript animations based on the complexity of your animations and the level of control you need. While both can be performant, they have different strengths and weaknesses.

  • CSS animations can take advantage of the GPU, making them smoother and more efficient, especially for simple animations (transforms, opacity, etc.).

  • Browsers are generally optimized for CSS animations, leading to less jank or dropped frames.

  • JavaScript allows for full control over animations. You can start, stop, pause, reverse, and synchronize animations with events in real time. That’s not the case with CSS animations.

  • Physics-based spring animations are more natural, provide smoother transitions, and adjust dynamically to changes in velocity or state. This results in fewer animation jumps and smoother interactions.

Here are some key differences between the two:

CriteriaCSS AnimationsJavaScript Animations
ComplexityBest for simple animationsBest for complex animations
PerformanceUsually more performantMay have overhead but can be optimized
ControlLimited control over timing and sequenceFull control over animations and timing
InteractivityBasic hover effectsHandles complex user interactions
State ManagementLimited to CSS statesCan tie animations to application state
ChainingCSS keyframes allow easy chainingLibraries provide chaining and sequencing

Conclusion

Creating joyful and delightful animations is a lot of work. You can follow sites like Awwwards, Dribbble, and Behance to get inspiration. The more you stop by and look at others work, you will start to see patterns and understand what feels right. I try to spend at least 30 mins every day to see others work and try to understand whats so special about it that makes it feel so good. Validate your work with others and get feedback. I hope this article was able to provide you with some directions on how to think about animations. The more you understand, the better you will be able to break down an animation into smaller stories and create links between them in a sensible and meaningful way thats feels right.

If you have any questions or feedback, feel free to reach out to me on twitter.

Did you like the post?
0000