Advanced animation patterns with Framer Motion

April 20, 2021

/ 18 min read /

0 Likes β€’

0 Replies β€’

0 Reposts

Last Updated April 20, 2021

I got ✨a lot✨ of positive feedback from my Guide to creating animations that spark joy with Framer Motion, and it's undeniable that this library has piqued many developers' interests in the world of web-based animations.

While I introduced in this previous post many of the foundational pieces that compose an animation, and how one can orchestrate multiple transitions very easily with Framer Motion, I did not touch upon many of the more advanced features that this library provides.

Ever wondered how to propagate animations throughout several components or to orchestrate complex layout transitions? Well, this article will tell you all about these advanced patterns and show you some of the great things one can accomplish with Framer Motion!

Like the original blog post, this article contains a series of interactive widgets and playgrounds with preset examples to allow you to apply the Framer Motion concepts we're about to see without the need to set up anything!

Let me know what you think about these examples, and whether they were helpful. Your feedback is super important and will help me do better for my future blog posts πŸ˜„!

Propagation

One of the first advanced patterns I got to encounter when I tried to add some micro-interactions with Framer Motion on my projects is propagation. I quickly learned that it's possible to propagate changes of variants from a parent motion component to any child motion component. However, this got me confused at the beginning because it broke some of the mental models I originally had when it comes to defining animations.

Remember in my previous blog post when we learned that every Framer Motion Animation needed 3 properties (props) initial, animate, transition, to define a transition/animation? Well, for this pattern that's not entirely true.

Framer Motion allows variants to "flow down" through every motion child component as long as these motion components do not have an animate prop defined. Only the parent motion component, in this case, defines the animate prop. The children themselves only define the behavior they intent to have for those variants.

A great example where I used propagation on this blog is the "Featured" section on the home page of this blog. When you hover it, the individual cards "glow" and this effect is made possible by this pattern. To explain what really is happening under the hood, I built this little widget below where I reproduced this effect:

Hover me!

✨ It's magic! ✨


You can see that hovering (or tapping if you're on mobile) the card or even the label above it triggers the glow effect. What kind of sorcery is this?! By clicking on the "perspective" button, you can see what happens under the hood:

  1. There's an "invisible" motion layer covering the card and the label. This layer holds the whileHover prop which sets the variant "hover"
  2. The "glow" itself is a motion component as well, however, the only thing it defines is its own variants object with a hover key.

Thus when hovering this invisible layer, we toggle the "hover" variant and any child motion component having this variant define in their variants prop will detect this change and toggle the corresponding behavior.

Example of propagation pattern with Framer Motion

1
const CardWithGlow = () => {
2
const glowVariants = {
3
initial: {
4
opacity: 0
5
},
6
hover: {
7
opacity: 1
8
}
9
}
10
11
return (
12
// Parent sets the initial and whileHover variant keys
13
<motion.div initial="initial" whileHover="hover">
14
{/* child motion component sets variants that match the keys set by the parent to animate accordingly */}
15
<motion.div variants={glowVariants} className="glow"/>
16
<Card>
17
<div>Some text on the card/div>
18
</Card>
19
</motion.div>
20
)
21
}

You can check out the full code of a similar example by navigating to the Card with glow effect on hover snippet page.

Now let's apply what we learned about the propagation mechanism of Framer Motion! In the playground below you'll find a motion component with a "hover" animation. When hovering it, a little icon will show up on the right end side of that component. You can try to:

  • Modify the variant key used in the motion component wrapping the button and see that now that it defers from what's being set by the parent component, the animation does not trigger and the button is not visible on hover.
  • Set ananimate prop on the motion component that wraps the button and see that it now animates on its own and does not consume the variant set by the parent on hover.
  • Intro to SwiftUI
  • Animate components when they are unmounting

    So far, we've only seen examples of animation being triggered either on mount or following some specific events like hover or tap. But what about triggering an animation right before a component unmounts? Some sort of "exit" transition?

    Well, in this second part, we'll take a look at the Framer Motion feature that addresses this use case and also the one that impressed me the most: AnimatePresence!

    I tried to implement some kind of exit animations before learning about AnimatePresence, but it was hacky and always required extra code to set a proper "transitional" state (like isClosing, isOpening) and toggle the corresponding animation of that state. As you can imagine, it was very error-prone.

    A very hacky way to implement an exist animation without AnimatePresence

    1
    /**
    2
    This is mostly pseudo code, do not do this!
    3
    It's not good practice
    4
    **/
    5
    6
    const MagicComponent = () => {
    7
    const [hidden, setHidden] = React.useState(false);
    8
    const [hidding, setHidding] = React.useState(false);
    9
    10
    const variants = {
    11
    animate: (hidding) => ({
    12
    opacity: hidding ? 0 : 1,
    13
    })
    14
    initial: {
    15
    opacity: 1
    16
    },
    17
    }
    18
    19
    const hideButton = () => {
    20
    setHidding(true);
    21
    setTimeout(() => setHidden(true), 1500);
    22
    }
    23
    24
    return (
    25
    <motion.button
    26
    initial="initial"
    27
    animate="animate"
    28
    variants={variants}
    29
    onClick={hideButton}
    30
    custom={hidding}
    31
    >
    32
    Click to hide
    33
    </motion.button>
    34
    )
    35
    }

    On the other hand, AnimatePresence is extremely well thought of and easy to use. By simply wrapping any motion component in an AnimatePresence component, you'll have the ability to set an exit prop!

    Example of use case for AnimatePresence

    1
    const MagicComponent = () => {
    2
    const [hidden, setHidden] = React.useState(false);
    3
    4
    return (
    5
    <AnimatePresence>
    6
    {!hidden && (
    7
    <motion.button
    8
    initial={{ opacity: 1 }}
    9
    exit={{ opacity: 0 }}
    10
    onClick={() => setHidden(true)}
    11
    >
    12
    Click to hide
    13
    </motion.button>
    14
    )}
    15
    </AnimatePresence>
    16
    );
    17
    };

    In the interactive widget below, I showcase 2 versions of the same component:

    • the one on the left is not wrapped in AnimatePresence
    • the second one, however, is wrapped

    That's the only difference code-wise. But as you can see the difference is pretty striking!

    Without AnimatePresence
    πŸš€
    With AnimatePresence
    πŸš€

    πŸ‘‰ Something I discovered while writing this article

    To set the proper direction of the transition, left or right, depending on which button is pressed, I set a state while the user hovers the button which will change the direction of the transition accordingly (not the best implementation I know but it works πŸ˜„) Despite this working perfectly on the example on the right, the one that uses AnimatePresence, you may have noticed that on the example on the left, the transition kicks in just by hovering one of the buttons.

    It seems that AnimatePresence keeps track of which motion component is rendered at a given time and throughout state changes.

    I still need to investigate the inner workings of Framer Motion for that, but, despite it being a surprise to me, this behavior makes sense given the use case.

    We now have a new awesome tool to use to make our transitions even better! It's time it a try in the playground below:

    • Try to remove the AnimatePresence component. Notice how this makes Framer Motion skip the animation specified in the exit prop.
    • Try to modify the animation defined in the exit prop. For example, you could make the whole component scale from 1 to 0 while it exit. (I already added the proper animation objects commented in the code below πŸ˜„)
    • Intro to SwiftUI
    • Awesome React stuff
    • Styled components magic
    • A guide to Typescript

    Layout animations

    We now know how to:

    • propagate animations throughout a set of motion components
    • add an exit transition to a component so it can unmount gracefully

    Those advanced patterns should give us the ability to craft some pretty slick transitions right? Well, wait until you hear more about how Framer Motion can handle layout animations!

    What is a "layout animation"?

    A layout animation is any animation touching layout related properties such as:

    • position properties
    • flex or grid properties
    • width or height
    • sorting elements

    But to give you a little bit more of an idea of what I'm talking about here, let's try to take a look at the playground below that showcases 2 versions of the same component:

    • the first one animates justify-content property between flex-start and flex-end by simply using the patterns we only know so far: setting this property in the animation prop
    • the second one uses a new prop: layout. It's here set to true to tell Framer Motion that a "layout related property", and thus by extension the layout of the component, will change between rerenders. The properties themselves are simply defined in CSS as any developer would do normally when not using Framer Motion.
    Switch 1: Attempt at animating justify-content in a Framer Motion animation object.

    Switch 2: Animating justify-content using layout animation and the layout prop.

    We can observe multiple things here:

    1. The first example does not work, it looks here that Framer Motion can't transition between justify-content properties the same way you'd transition an opacity from 0 to 1 gracefully.
    2. The second component however transitions as expected between the flex-start and flex-end property. By setting layout to true in the motion component, Framer Motion can transition the component's justify-content property smoothly.
    3. Another advantage of the second component: it does not have as much of a "hard dependency" with Framer Motion as the first one. We could simply replace the motion.div with a simple div and the component itself would still work

    I plan on revisiting some of the animations implemented on this blog and maybe convert them to proper layout animations to simplify the code. I'm pretty sure my Header and Search component could benefit from it since their animations are centered around layout/height changes.

    Shared Layout Animation

    So we now know what layout animations are and how to leverage those for some specific use cases. However, if we try some complex examples like nesting several of those layout animations, you will notice some weird behavior. Those can end up out of sync make elements overlap during a transition and that's not a desirable output. Thankfully the AnimateSharedLayout component is here to save the day ✨!

    But first, what do I mean by "weird behavior" exactly? Let's take a look at the little example below to see what's happening when AnimateSharedLayout is absent and present:

    You have 0 pets

      By clicking on the + button you will make items appear on the card. The card's height will adjust based on the element gracefully thanks to the layout prop that is set in both the card and items components. However, you may notice that:

      • when not using AnimateSharedLayout, the item appears AND slides down as the layout animation of the card is still happening
      • but when using AnimateSharedLayout, the item appears already in its final position, despite the layout animation of the card still being in progress

      This change is very subtle but it makes our animations look so much better! The only difference between those 2 transitions in the example above is the use of AnimateSharedLayout and we're lucky because just like AnimatePresence it's very easy to use:

      Example showcasing how simple it is to use AnimateSharedLayout

      1
      const MagicWidgetComponent = () => {
      2
      return (
      3
      /*
      4
      The only thing to do when wanting to "share" a layout animation
      5
      is to wrap the desired motion components in <AnimateSharedLayout>
      6
      component! That's it!
      7
      */
      8
      <AnimateSharedLayout>
      9
      <CardWrapper layout initial={{ borderRadius: 25 }}>
      10
      <motion.p layout>
      11
      You have {items.length} {items.length === 1 ? 'pet' : 'pets'}
      12
      </motion.p>
      13
      <Grid as="ul" columns="repeat(auto-fill, minmax(50px, 1fr))" gap="8px">
      14
      {items.map((item) => (
      15
      <Circle
      16
      key={item.id}
      17
      layout
      18
      initial={{ opacity: 0 }}
      19
      animate={{ opacity: 1 }}
      20
      >
      21
      {item.photo}
      22
      </Circle>
      23
      ))}
      24
      </Grid>
      25
      </CardWrapper>
      26
      </AnimateSharedLayout>
      27
      );
      28
      };

      That's right! The only thing to do is to wrap the set of layout animations we want to keep in sync in a AnimateSharedLayout component. That's it!

      It's now time to give a try at what we just learned! This last example compiles all the previous playgrounds together to create this list component with filters. This component contains:

      • Some layout animations to transition the items on the lists when their sorting changes.
      • AnimateSharedLayout to gracefully resize the list when an item expands on hover revealing more details.

      You can try to:

      • comment out AnimateSharedLayout and notice the subtle differences when a layout animation is triggered.
      • try to add the layout prop on the motion.div component surrounding the title and see it gracefully adjusting when the item's height changes after clicking on it.
      Sort by:
      • A guide to Typescript
      • Awesome React stuff
      • Intro to SwiftUI
      • Styled components magic

      Conclusion

      Congrats, you are now a Framer Motion expert πŸŽ‰! From propagating animations to orchestrating complex layout animations, we just went through some of the most advanced patterns that the library provides. We saw how well designed some of the tools provided are, and how easy it is thanks to those to implement complex transitions that would usually require either much more code or end up having a lot more undesirable side effects.

      I really hope the examples provided in this blog post helped illustrate concepts that would otherwise be too hard to describe by text and that, most importantly, were fun for you to play with. As usual, do not hesitate to send me feedback on my writing, code, or examples, I'm always striving to improve this blog!

      Did you come up with some cool animations after going through this guide?

      Don't hesitate to send me a message showcasing your creations!

      Want to see more examples?

      The Framer Motion documentation has tons of those to play with on Codepen.

      If you want to dig a bit deeper, below is the list of links to check out the implementations of the widgets featured in this article:

      Fetching Replies...

      Do you have any questions, comments or simply wish to contact me privately? Don’t hesitate to shoot me a DM on Twitter.


      Have a wonderful day.
      Maxime

      Subscribe to my newsletter

      Get email from me about my ideas, frontend development resources and tips as well as exclusive previews of upcoming articles.


      Β© 2021 Maxime Heckel β€”β€” SF/NY