chipit24
chipit24

Reputation: 6987

How to prevent content jumping in framer-motion animation when transitioning between two pages

I have essentially the following setup:

<AnimatePresence initial={false}>
  {value ? (
    <motion.div>
      {/* Page 1 content */}
    </motion.div>
  ) : (
    <motion.div>
      {/* Page 2 content */}
    </motion.div>
  )}
</AnimatePresence>

When value changes from true to false, I want page 1 to slide out to the left while at the same time page 2 slides in from the right. This is very similar to how slideshows work, or page transitions on iOS apps.

I have a simple example set up on CodeSanbox: https://codesandbox.io/s/laughing-leftpad-4kin7k?file=/src/App.js:272-1064. What happens when I toggle value is that the pages slide to the left as expected, but because the pages are different heights, they cause the content under them to jump down. Also, since both pages are rendered at the same time during the animation, the first page causes the 2nd page to render below it as well, so when the animation completes, page 2 jumps up.

enter image description here

How can I do this sort of animation without the content jumping around? Ideally, I want:

Upvotes: 3

Views: 15552

Answers (6)

Johnny Kontrolletti
Johnny Kontrolletti

Reputation: 899

In case you are working with layoutId you can simply append the current pathname to the id.

const pathname = usePathname();


return (
    <motion.div
        layoutId={`${pathname}-some-id`}
        transition={{ ease: "circOut", duration: 0.5 }}
    />
)

Upvotes: 2

smfrdev
smfrdev

Reputation: 53

For newer versions of framer, mode="popLayout" can also be used on AnimatePresence. This will not delay animations of <motion.div> children as in mode="wait".

<AnimatePresence mode="popLayout"> 
    <motion.div>....</motion.div>
</AnimatePresence>

https://www.framer.com/motion/animate-presence/###mode

Upvotes: 1

TheEpic
TheEpic

Reputation: 37

I stumbled upon this problem too. To make it so that there is no delay, and you don't have to wait until the element leaves like the other answers have, you simply have to position the elements on top of each other.

I've found the most elegant solution is to simply add a position: absolute css style to your motion.div component. You can then wrap the outer AnimatePresence component with a div that has a position: relative style and a set height so that it looks correct.

The structure should be like so:

<div style={{position: "relative"}}>
    <AnimatePresence> 
         <motion.div style={{position: "absolute"}}>...</motion.div>
    </AnimatePresence>
</div>

here is your modified codesandbox: https://codesandbox.io/s/hopeful-lumiere-7vwb8n?file=/src/App.js

Upvotes: 1

MrEbrahim XD
MrEbrahim XD

Reputation: 89

Update :

add mode="wait" in your AnimationPresence component

<AnimatePresence mode="wait" initial={false}> 
    <motion.div>....</motion.div>
</AnimatePresence>

Upvotes: 6

Vimal Thanikachalam
Vimal Thanikachalam

Reputation: 275

You need to define exitBeforeEnter property to the AnimatePresence component, which should fix your issue!

Something like this :

<AnimatePresence exitBeforeEnter initial={false}> 
    <motion.div>....</motion.div>
</AnimatePresence>

Upvotes: 0

corn
corn

Reputation: 9

Unfortunately I'm running out of time to dig into finding a technical answer, but I used this Framer-Motion Example as a guide.

Steps:

  1. Wrapped animate presence in a div.
  2. Added Flex and fixed height to max height of largest component in animate presence.
  3. Added duration of 0.5 to exit animations
  4. Added delay of 0.5 to allow exit animations to complete

Before After

Here's the sandbox I used: https://codesandbox.io/s/crazy-forest-5m1p7x?file=/src/App.js

Upvotes: 0

Related Questions