Home
Projects
Products
Blog
About
Back to all blogs
Rebuilding

How `motion` keeps your animations alive however it should be removed from the DOM?

In this blog we will replicate `AnimatePresence` functionality using native JavaScript and React (for simple use cases).

1 January 2024
Using `motion` and `AnimatePresence`Rebuilding `AnimatePresence`Prerequisites1. Compare the previous `childrens` with the new `children`, and activate `animate__exit` animation2. Remove any children marked with `animate__exit` class after the animations are complete3. Manage new `children`

If you are a frontend developer, you probably know that motion is a powerful library for animating your components. One of its standout features is ensuring that elements with animations are not removed from the DOM until their animations are complete. However, the library comes with a size cost: 116.5k (gzipped: 37.5k), which can be significant for simple use cases. If you only need basic animations with AnimatePresence, you can rebuild it without motion.

In this blog, we will learn how to create a simple animation using (motion and AnimatePresence) and then replicate its functionality using native JavaScript and React.

Using motion and AnimatePresence

import { motion, AnimatePresence } from "motion/react";
import { useState } from "react";

function Ball() {
  const [visible, setVisible] = useState(true);

  return (
    <AnimatePresence>
      {visible && (
        <motion.div
          initial={{ opacity: 0, x: -30 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: 30 }}
        >
          ⚾
        </motion.div>
      )}
    </AnimatePresence>
  );
}
  • With AniamtePresence
  • Without AnimatePresence
⚾

With AniamtePresence

In the With AnimatePresence case, the Show/Hide Button does not shift until the animation is complete. This behavior occurs because AnimatePresence compares the previous childrens with the new children. If it detects removed children, it triggers the "exit" animation before finally removing the elements.

Rebuilding AnimatePresence

Steps:

  1. Compare the previous childrens with the new children, and activate animate__exit animation
  2. Remove any children marked with animate__exit class after the animations are complete
  3. Manage new children added to the DOM

Prerequisites

  • What are the differences between ReactElement and ReactNode?
  • What is React.Children and how to use it?

1. Compare the previous childrens with the new children, and activate animate__exit animation

We’ll maintain a state to track the current children and compare them with new children to detect removals. Removed elements will get the animate__exit class.

import React from "react";

// Minimal types for cloning element
interface CloningElementProps {
  className?: string;
  style?: { animation?: string }
}

function AnimatePresence({ children }: { children: React.ReactNode }) {
  const [childrens, setChildrens] = React.useState<ReactNode[]>(
    React.Children.toArray(children)
  );

  useEffect(() => {
    const pre = childrens.map((child) => {
      const exists = React.Children.toArray(children).some((curChild) => {
        return (
          React.isValidElement(child) &&
          React.isValidElement(curChild) &&
          child.key == curChild.key
        );
      });

      return !exists && React.isValidElement<CloningElementProps>(child)
        ? cloneElement(child, {
            className: (child.props.className ?? "").replace(
              "animate__enter",
              "animate__exit"
            ),
          })
        : child;
    });

    setChildrens(pre);
  }, [children]);

  useEffect(() => {
    // STEP 2
  }, [childrens]);

  return childrens;
}

Explanation

  • The useEffect listens for changes in children.
  • We compare previous childrens with new children using the key property.
  • For removed elements, we replace animate__enter with animate__exit to trigger the exit animation.

Hold on

In my case, I designed AnimatePresence to work with simple keyframes. As when animating out, I'm going to remove the animate__enter class, and add animate__exit class.

Another interesting tip 😊! You can use this cubic bazier cubic-bezier(0.34, 1.56, 0.64, 1), to make a spring like transition (like in motion)

/* Enter */
.animate__enter {
  transform: translateX(-30px);
  opacity: 0;
  animation: enter 350ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes enter {
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Exit */
.animate__exit {
  animation: exit 350ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes exit {
  to {
    transform: translateX(30px);
    opacity: 0;
  }
}

2. Remove any children marked with animate__exit class after the animations are complete

useEffect(() => {
  // STEP 2
  childrens.forEach((child) => {

    if (!React.isValidElement < CloningElementProps > child) return;
    if (!child.props.className) return;

    if (child.props.className.includes("animate__exit")) {
      setTimeout(() => {
        setChildrens((prevChildrens) => {
          return prevChildrens.filter(
            (prevChild) =>
              React.isValidElement(prevChild) && prevChild.key !== child.key
          );
        });
      }, child.props.style?.transitionDuration ?? 300);
    }
  });
}, [childrens]);

Tip: If you wish, you could change the timeout duration using something like data-duration attribute to ensure the removal aligns with the animation length.

3. Manage new children

useEffect(() => {
  // Unchaged `pre`

  const post = React.Children.toArray(children).filter((child) => {

    const exists = childrens.some((curChild) => {
      return (
        React.isValidElement(child) &&
        React.isValidElement(curChild) &&
        child.key == curChild.key
      );
    });

    return !exists;
  });

  setChildrens(pre); 
  setChildrens([...pre, ...post]); 
}, [children]);

Explanation

  • pre identifies removed elements by comparing childrens with children.
  • post identifies new elements by comparing children with childrens.
  • Then merge them.

Finally, you can use this AnimatePresence in simple use cases instead of motion. Let's see our implementation:

  • motion
  • mine
⚾

motion

If you find areas for improvement in this approach or have suggestions, we’d love to hear your feedback! 💚

Osama Mohammed

Building things for the web, Sorry EVERYTHING.

Pages

  • Home
  • Projects
  • Products
  • Blog
  • About

Social

© 2026 Osama Mohammed. All rights reserved.mail.osmhmd@gmail.com