framer / motion

Open source, production-ready animation and gesture library for React
https://framer.com/motion
MIT License
23.43k stars 785 forks source link

[BUG] useTransform does not reflect update of chained MotionValues #2280

Open ali-idrizi opened 1 year ago

ali-idrizi commented 1 year ago

I am building a complicated animation, where a transform depends on several motion values. These values are sometimes repeating, so I extracted them to separate motion values, which are calculated using useTransform. I have managed to reproduce the issue with a very simple example:

const a = useMotionValue(0);
const aHalf = useTransform(a, (a) => a / 2);
const aQuarter = useTransform(aHalf, (h) => h / 2);

const result = useTransform([a, aQuarter], ([a, aQuarter]) => {
  // the aQuarter MotionValue is eventually updated to `25`, but it is never reported here. This will log `100, 0` instead of `100, 25`
  console.log({ a, aQuarter });
  return a + aQuarter;
});

useMotionValueEvent(a, "change", (v) => console.log("a", "change", v));
useMotionValueEvent(aQuarter, "change", (v) => console.log("aQuarter", "change", v));
useMotionValueEvent(result, "change", (v) => console.log("result", "change", v));

React.useEffect(() => {
  setTimeout(() => a.set(100), 500);
}, []);

Here is a Codesandbox of the same: https://codesandbox.io/s/framer-motion-enter-animation-forked-4jfkdc?file=/src/App.tsx

If you check the logs you will notice:

a change 100
{a: 100, aQuarter: 0}
result change 100
aQuarter change 25

As you can see, the useTransform for result never logs the aQuarter value of 25, while the same is being reported by useMotionValueEvent. This issue results in the transform outputting an incorrect value for result of 100, while it is supposed to be 125.

piotrnajda3000 commented 1 month ago

I concur.

I have the following transform:

const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ["start end", "end end"],
 });

const progress = useTransform(scrollYProgress, [0, 0.3, 0.7], [0, 0, 1]);

const vidScale = useTransform(
    progress,
    [0, 1],
    [1, 9],
  );

// even though progress will be 1, vidScale won't reach it's target. 
progress.on("change", (v) => { console.log(v, vidScale.get() });

Even though progress reaches 1, headingScale is shy of it's needed target (9 in this example).