Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Framer Motion dynamic variants don't work when modifying initial properties

According to the docs I can make variant properties dynamic: https://www.framer.com/docs/animation/##dynamic-variants.

But this doesn't work when I try to make the initial properties dynamic.

For example:

import React, { useState, useEffect } from "react";
import { motion, useAnimation } from "framer-motion";

//make div appear from either bottom or right, depending on "origin" custom prop
const variant = {
  hidden: (origin) =>
    origin === "bottom"
      ? { x: 0, y: 200, opacity: 0 }
      : { x: 200, y: 0, opacity: 0 },
  visible: { x: 0, y: 0, opacity: 1, transition: { duration: 1 } },
};

function App() {
  const [origin, setOrigin] = useState("bottom");
  const controls = useAnimation();

  //after 2 secs make origin "right"
  useEffect(() => {
    setTimeout(() => {
      setOrigin("right");
    }, 2000);
  }, []);

  //after 4 secs start the animation
  useEffect(() => {
    setTimeout(() => {
      controls.start("visible");
    }, 4000);
  }, [controls]);

  return (
    <motion.div
      style={{ width: 100, height: 50, background: "red" }}
      variants={variant}
      initial="hidden"
      animate={controls}
      custom={origin}
    />
  );
}

export default App;

Here I made a dynamic variant to make a div appear from either the right or bottom, which I can control from a custom prop. Initially this custom prop is set to "bottom". After 2 secs, this is changed to "right". When I start the animation after 4 secs, I expect the div to appear from the right but it still appears from the bottom:

enter image description here

like image 991
Kazuto_Ute Avatar asked Oct 23 '25 19:10

Kazuto_Ute


1 Answers

This is because the component is already rendered and is still the same component even if the origin prop being passed to the component has changed.

You can do two things:

  1. Use a isVisible state variable where the render method will observe for changes and render the component when it becomes true.
function App() {
  const [isVisible, setIsVisible] = useState(false);

  ...

  //after 4 secs start the animation
  useEffect(() => {
    setTimeout(() => {
      setIsVisible(true);
      controls.start("visible");
    }, 4000);
  }, [controls]);

  return (
    isVisible && (
      <motion.div
        ...
      />
    )
  );
}

DEMO

  1. Add a key prop to the component with the origin value so that when the value changes, React will re-render the component.
function App() {
  ...

  return (
    <motion.div
      key={origin}
      ...
    />
  );
}

DEMO

2nd option may be your preferred choice if you need to toggle between the origin.

like image 160
Scratch'N'Purr Avatar answered Oct 26 '25 08:10

Scratch'N'Purr



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!