framer / motion

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

[BUG] - broken layoutId animation with Next.js page change and scroll #2006

Closed ValentinH closed 4 months ago

ValentinH commented 1 year ago

Describe the bug

We are using layoutId to animate a current link indicator in our navbar (using fixed positioning) in a Next.js app. The animation works as expected during page navigation except when the page is scrolled. In that case, the initial position of the animation seems to be affected by the scroll offset. See this video:

https://user-images.githubusercontent.com/2678610/224677550-be7bfb11-1eba-45df-a190-77c7c3a99444.mov

Steps to reproduce

Steps to reproduce the behavior:

  1. Go to https://codesandbox.io/p/sandbox/adoring-albattani-qtdt3b?file=%2Fsrc%2FLayout.tsx
  2. Scroll the page
  3. Click on the "Page 1" link
  4. The animation is incorrect

Expected behavior

The animation should work regardless of the scroll.

Environment details

Similar issues

This issue seems similar to https://github.com/framer/motion/issues/1580 but I created a new one because I'm not fully sure that's the same issue. Feel free to tell me to close this one and comment there. 🙏

CRIMSON-CORP commented 1 year ago

I had this exact same issue when building a sidebar navigation with indicator just like this,

my work around was to tap into the route page change event and scroll to the very top of the page just as the page is about to switch to the next page

useEffect(() => {

        const routerEvent = 'beforeHistoryChange';

        const evntHandler = () => {
            window.scroll(0, 0);
        };

        router.events.on(routerEvent, evntHandler);

        return () => {
            router.events.off(routerEvent, evntHandler);
        };
 }, []);

I had already tried adding layoutScroll prop to all of the parents of the side bar but it never worked

CRIMSON-CORP commented 1 year ago

I have another work around🙂, in case this issue still persists in your work.

The idea is to make the parents of the nav and the main page to have the exact height of the screen, and make the content page or its descendant to overflow, that way, the sticky nav is never scrolled, and hence retains its position on the page so it doesn't fly around when the page is changed, this might seem hacky and all but it is the cleanest solution I was able to think off

checkout this spinoff of your sandbox https://codesandbox.io/p/sandbox/sharp-bohr-tph3h1?file=%2FREADME.md

Please note the overflow shouldn't be on the main tag that is from Nextjs or else Nextjs will keep remembering the scroll position for each page change and wont scroll back to top

ValentinH commented 1 year ago

Did you save it? I still see the bug in the link you shared.

CRIMSON-CORP commented 1 year ago

Thank you @ValentinH, I've updated my comment

minardimedia commented 12 months ago

Anybody from the maintainers putting attention to this ?. This looks similar to something that can be fixed with parent the case is that it does not solve the issue. The <motion.div layoutId={"underline"}> elements keeps using the whole window to make is calculations for the transform so when you scroll and change from page the calculation is wrong. The whole point of layoutRoot in my opinion was to avoid this in the the first place but it does not work.

simonlagneaux commented 7 months ago

Same, this such a frustrating issue! I've tried a lot of workarounds but nothing seem to work. Running Next.JS 13.5.4 and Framer Motion 10.16.4.

simonlagneaux commented 7 months ago

Update, this workaround worked for me: https://github.com/framer/motion/issues/1972#issuecomment-1483452870

mattgperry commented 4 months ago

You can also change the nav to <motion.nav layout layoutRoot

ValentinH commented 4 months ago

@mattgperry I just tried your suggestion and it doesn't seem to work: https://codesandbox.io/p/devbox/adoring-albattani-qtdt3b?file=%2Fsrc%2FLayout.tsx. Did I misunderstood something?

@simonlagneaux workaround works though. 🙏