Motion: [QUESTION] How to Delay Children Animations with AnimatePresence

Created on 26 Aug 2019  路  11Comments  路  Source: framer/motion

Describe the bug
I am using AnimatePresence on a simple example to fade in pages as they are mounted/unmounted. This works as expected. What I am struggling with is delaying the individual children animations on each page to wait until after the fade has completed first.

Using Pose and the PoseGroup I accomplished this with code similar to this:

const Transition = posed.div({
  enter: {
    opacity: 1,
    transition: { duration: 300 },
    delay: 300,
    beforeChildren: true
  },
  exit: { opacity: 0, transition: { duration: 300 } }
});

With Framer Motion and AnimatePresence my code looks similar to this :

const variants = {
  initial: {
    opacity: 0,
  },
  enter: {
    opacity: 1,
    transition: {
      duration: .3,
      delay: .3,
      when: 'beforeChildren',
    },
  },
  exit: {
    opacity: 0,
    transition: { duration: .3 },
  },
}

To Reproduce
Code Sandbox - https://codesandbox.io/s/github/ryanwiemer/gatsby-using-page-transitions
Github Repo - https://github.com/ryanwiemer/gatsby-using-page-transitions/blob/master/src/components/Layout.js

  1. Go to https://transitions.netlify.com/
  2. Navigate between the pages to see the basic page fade.
  3. Navigate to the "Animated" page. The list of items should do a stagger fade in but that animation occurs at the same time as the page fade so you don't see it.

Expected behavior
I expect the top level page fade to occur followed by the children animations. Right now all animations are occurring at the same time.

bug

Most helpful comment

I've been battling with this issue too. I found that if you make your child variant keys exactly the same names as your parent variant keys (in my case "enter" and "exit"), then it works!

Do not add "initial", "animate", or "exit" props on the child components -- only the relevant "variants" prop, like so:

import React from 'react';
import styled from 'styled-components';
import { motion, Variants } from 'framer-motion';

const containerVariants: Variants = {
  enter: {
    x: 0,
    opacity: 1,
    transition: {
      when: 'beforeChildren',
      staggerChildren: 0.4,
    },
  },
  exit: { x: -300, opacity: 0 },
};

const childVariants: Variants = {
  enter: {
    x: 0,
    opacity: 1,
  },
  exit: {
    x: -20,
    opacity: 0,
  },
};

const childVariants2: Variants = {
   enter: {
    x: 0,
    opacity: 1,
    color: 'red',
  },
  exit: {
    x: -20,
    opacity: 0,
    color: '#000',
  },
};

export const Menu = () => {
  return (
    <MenuWrapper
      variants={containerVariants}
      initial="exit"
      animate="enter"
      exit="exit"
    >
      <motion.h2 key="heading" variants={childVariants}>
        My Menu
      </motion.h2>

      <motion.h3 key="subheading" variants={childVariants}>
        Section 1
      </motion.h3>

      <motion.h3 key="subheading" variants={childVariants2}>
        Section 2
      </motion.h3>
    </MenuWrapper>
  );
};

const MenuWrapper = styled(motion.div)`
  position: absolute;
  top: 0;
  bottom: 0;
  width: 300px;
  padding: 20px 1rem;
  background-color: #111;
  color: #fff;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
  z-index: 8;

  h2 {
    margin: 0 0 1rem;
  }
`;

Note that I'm rendering this component inside of a <AnimatePresence> component with React Router Dom:

```
import React from 'react';
import { Route, Switch, useLocation, BrowserRouter } from 'react-router-dom';

import { AnimatePresence, motion } from 'framer-motion';
import { Menu } from './components/layout/Menu';

function App() {
const location = useLocation();

return (






);

export default () => (



);

All 11 comments

I am also having trouble with this. It seems that staggerChildren does not work when transitioning from initial -> animate variants, but when you change the value of animate to a different variant, it does work.

@InventingWithMonster

Is this intentional behavior on AnimatePresence? Or should this be categorized as a bug?

I was incorrect before about initial -> animate not working. If the children do NOT have an animate prop, it will stagger the initial animation. If the children DO have an animate prop, they will all appear at the same time, most likely due to https://www.framer.com/api/motion/animation#propagation

In my CodeSandbox, it correctly staggers the initial children, but when swapping children, they all disappear/appear at the same time:

Edit peaceful-lovelace-l3o64

I think the issue is that there is a disconnect between AnimatePresence and the parent Motion component where staggerChildren/delayChildren/when is defined.

In a structure like this:

<motion.div key="parent" variants={variantsWithOrchestration}>
  <AnimatePresence>
    <motion.div key="child1" variants={childVariants} animate="visible" exit="hidden"/>
    <motion.div key="child2" variants={childVariants} animate="visible" exit="hidden"/>
    <motion.div key="child3" variants={childVariants} animate="visible" exit="hidden"/>

the animate prop on children cancels out any orchestration defined in the parent. My guess is that AnimatePresence needs to accept a variants prop directly, or inherit them from the parent, so it understands how to orchestrate the children.

Since AnimatePresence does not control the existence of its children, it is difficult to see how this should be implemented. What you can do is control the visibility of your elements by yourself, delaying the rendering of components with a wrapping element, something like this:

https://codesandbox.io/s/framer-motion-animate-presence-stagger-children-7j0v3

Adding more items dynamically will need a bit more work, but this should give something to start with.

In my opinion this is not really bug but perhaps something that could be documented better. I spent a fair amount of time figuring out how to implement this.

@marozzocom It has been a long time since I last did something related to this, but from what I remember, I believe I did a similar implementation to yours but the problem is that I could stagger the "enter" animations of child components by manually increasing a shared delay, but I could not stagger the "exit" animations because there was no ability at the time to pass that delay to exiting components.

I don't think it is possible unless AnimatePresence manages its children or allows a component within to manage the delay. I know it is possible because the predecessor to this library, React Pose worked well with enter/exit staggering animations.

This is an issue I'm running into. My use-case is a sidebar that expands out and needs to delay the rendering of its children until it's full expanded.

This is an issue I'm running into. My use-case is a sidebar that expands out and needs to delay the rendering of its children until it's full expanded.

+1 for me. I am trying to animate menu items of sidebar with staggerChildren prop. However, it doesn't work for me

I've been battling with this issue too. I found that if you make your child variant keys exactly the same names as your parent variant keys (in my case "enter" and "exit"), then it works!

Do not add "initial", "animate", or "exit" props on the child components -- only the relevant "variants" prop, like so:

import React from 'react';
import styled from 'styled-components';
import { motion, Variants } from 'framer-motion';

const containerVariants: Variants = {
  enter: {
    x: 0,
    opacity: 1,
    transition: {
      when: 'beforeChildren',
      staggerChildren: 0.4,
    },
  },
  exit: { x: -300, opacity: 0 },
};

const childVariants: Variants = {
  enter: {
    x: 0,
    opacity: 1,
  },
  exit: {
    x: -20,
    opacity: 0,
  },
};

const childVariants2: Variants = {
   enter: {
    x: 0,
    opacity: 1,
    color: 'red',
  },
  exit: {
    x: -20,
    opacity: 0,
    color: '#000',
  },
};

export const Menu = () => {
  return (
    <MenuWrapper
      variants={containerVariants}
      initial="exit"
      animate="enter"
      exit="exit"
    >
      <motion.h2 key="heading" variants={childVariants}>
        My Menu
      </motion.h2>

      <motion.h3 key="subheading" variants={childVariants}>
        Section 1
      </motion.h3>

      <motion.h3 key="subheading" variants={childVariants2}>
        Section 2
      </motion.h3>
    </MenuWrapper>
  );
};

const MenuWrapper = styled(motion.div)`
  position: absolute;
  top: 0;
  bottom: 0;
  width: 300px;
  padding: 20px 1rem;
  background-color: #111;
  color: #fff;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
  z-index: 8;

  h2 {
    margin: 0 0 1rem;
  }
`;

Note that I'm rendering this component inside of a <AnimatePresence> component with React Router Dom:

```
import React from 'react';
import { Route, Switch, useLocation, BrowserRouter } from 'react-router-dom';

import { AnimatePresence, motion } from 'framer-motion';
import { Menu } from './components/layout/Menu';

function App() {
const location = useLocation();

return (






);

export default () => (



);

If I understand correctly, it seems like the root problem here is that the orchestration is queuing transitions based on variant names, rather than the transitions themselves. Is there a plan to change this behavior so that dependent animations will queue regardless of the names in the future?

It seems to me like this should really be the default for any layout animations, and forcing them to run in parallel could be opt-in. Layout rules don't necessarily have compatible interpolation so the safe bet is to make them sequential. Like folding/unfolding origami without ripping the paper.

I think what I find odd about the API in its current form, is that if variant naming is apparently so fundamental to the orchestration, why are the other direct properties even allowed? It's sort of set up as a landmine to be able write things without variants and then find it breaks the whole orchstration model because naming was apparently important.

It's odd that names matter to begin with, but combine that with the names not being required, makes it really confusing.

For the record, the solution thebetternewt posted won't help if you're using AnimateSharedLayout. Doesn't seem there is any way to delay the exit transition for a layout element inside AnimateSharedLayout.

@InventingWithMonster Is there any workaround for this?

Was this page helpful?
0 / 5 - 0 ratings