New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BUG] Exit animation with Next.js #1375
Comments
In <AnimatePresence exitBeforeEnter>
<Component {...pageProps} key={router.pathname} />
</AnimatePresence> See: https://wallis.dev/blog/nextjs-page-transitions-with-framer-motion This used to work well with older versions of next.js, but doesn't work anymore. I can't figure out why. |
I struggled to get this working as well. However, I was able to get it to work after correctly with Next v12.0.7 specifying a key for the component as suggested by @MarcGuiselin. export default function App({ Component, pageProps, router }) {
return (
<AnimatePresence exitBeforeEnter>
<Component {...pageProps} key={router.pathname} />
</AnimatePresence>
);
} https://www.framer.com/docs/animate-presence/##animating-custom-components I'm relived that all my nested |
Same issue here =/ |
Thanks a lot, it fixed the issue.
|
It doesn't work for me on Next's latest version |
@teauxfu solution resolved this for me on 12.0.8. <Header />
<AnimatePresence
exitBeforeEnter
initial={false}
onExitComplete={() => window.scrollTo(0, 0)}
>
<Component {...pageProps} key={router.pathname} />
</AnimatePresence>
<Footer /> |
Ok, it's an issue with React 18 (concurrent mode), see #1421 |
you can refer to this example from official documentation of Nextjs https://github.com/vercel/next.js/blob/canary/examples/with-framer-motion/pages/_app.js lately, i don't know why animation presence don't work like expected in the initial animation? |
the exit animation is not working in dynamic routes |
@aluku7-wq Working for me, but be sure to pass the key as <Component {...pageProps} key={router.asPath} /> |
Hello! I have the same problem but it only happens in some transitions. I have (an example) the following routes: I have my app.js as the comment above, wrapped in LazyMotion with domAnimation and AnimatePresence: <LazyMotion features={domAnimation}>
<AnimatePresence
exitBeforeEnter
onExitComplete={() => {
console.log("EXIT COMPLETE", router.asPath);
}}
>
<Component {...pageProps} key={router.asPath} />
</AnimatePresence>
</LazyMotion> When I navigate from /projects.js to /journal.js onExitComplete runs, but when I try to navigate from any of those two pages to one of [...slug.js] onExitComplete does not run. Still, when I navigate between pages from [...slug.js] (from /studio to /legal) the transition works, so I am quite confused as to why this is happening. Since my app is pretty complex by now (localized routes, caches, styled components) I don't know how I could make a sandbox to give an example, but maybe someone has any idea of what is happening. Thank you all! |
Okay, I found the cause. I had my _app.js main component wrapped in appWithTranslation HOC from next-i18next and this broke the transitions. Do any of you guys have any idea how to fix this? |
@cristobalbahe did you find any solution for that? I am experiencing the same. |
Hey @f4z3k4s, I managed to fixed it by changing from next-i18next to next-intl. I guess since next-intl uses a regular component () instead of a HOC the context of framer doesn't get lost (I am not sure is because of this though). The thing is it works now :) |
@cristobalbahe Thanks for the answer. Since then, I've bypassed the issue by implementing a routing animation myself without AnimatePresence with the help of router events. Then, we know it's probably an issue with next-18next based on your solution. |
Done some extensive testing all options here. It's not isolated to next-i18next. I DO not get the issue if I have a [id].tsx Something about the spread operator in the page name makes it happen. |
The solution from @teauxfu and @MarcGuiselin works flawlessly for me. Using React 18.1.0 & Next 12.1.6. Thanks for the knowledge |
Having the same problem in nextjs 13.0.5 and react 18.0.2 with framer-motion 7.6.12; none of the previous solutions have been helpful. onExitComplete never triggers at all |
@iamfrisbee W/ Next 13.0.5 + React 18.2.0 + framer-motion 7.6.17 I was able to get page entrance & exit animations to work w/ this snippet:
|
hi, have you tried with next13 app directory? I do the same but still not working |
So I had to upgrade a few packages to get that, but no, it doesn't work. I verify this by putting a console.log in onExitComplete and it never runs. |
I'm experiencing the same thing as @iamfrisbee - I tried many different ways yesterday, and none worked. FWIW, commenters on this YouTube video also are running into the same thing. |
Yeah we've tried everything here and still nothing. We're on Next 13.1.1, React 18.2.0 and Framer Motion 8.0.2. Fingers crossed this gets fixed soon! |
Running into this issue as well |
In case this isn't resolved soon and anyone else find this thread.. I found a hacky workaround for route animations thats compatible with the nextjs app directory feature. const router = useRouter()
const controls = useAnimationControls()
const onRoute = useCallback((href: string) => async () => {
await router.prefetch(href)
await controls.start('exit')
await router.push(href)
await controls.set('hidden')
await controls.start('enter')
}, [router, controls]) <motion.main
animate={controls}
variants={{
hidden: { opacity: .3, x: -200, y: 0 },
enter: { opacity: 1, x: 0, y: 0 },
exit: { opacity: .3, x: 0, y: -100 },
}}
transition={{ type: 'keyframes', duration: 2 }}>
{children}
</motion.main> <button onClick={onRoute('page-1')}>
Link
</button> You can manually trigger enter/exit animations via the NOTE: this code only works in |
Next: Same issue. I will wait without exit animation until this is resolve. |
So i've faced the same issue using next^12.1.0 & react^18.2.0 & react-dom^18.2.0 & framer-motion^7.6.4
It works fine on static pages that catches multiple routes like [[...slug.js]], but struggles to remove the elements from the dom if its a regular [...slug.js]. Couldn't find a proper way to handle this but instead of wrapping the Component in _app.js, I wrapped the layouts in [[...slug.js]] files fixed the issue.
|
have the same issue, it also stated on the beta docs from nextjs to use templates. I can see on react-devtools the |
Mine is not working either, but on normal components, not even pages. |
This work for me on next 13.1.6
|
@hasolu everything works fine using the pages except using the new app dir and the layout template |
@davidkhierl now i realize this code is not working in production, i'm dealing with this. |
I'm having issues with this too. React 18.2, Framer Motion 10.0.1 and Next.js 13.2.3 UPDATE: if you get that error, change |
The problem is the |
I am still fighting with this!!! Not even in non-dynamic pages and not using the new app dir structure - I can't get the exit animation to trigger nor the Dep versions:
Anyone cracked this already? |
@psoaresbj The Exit Animation work in the page dir, did you give a try ? |
@ShueiYang not super helpful. We all know the exit animations work with the page directory—the entire point of this issue is that they don't work with Next.js 13's app directory. |
@harshhhdev my bad if i misunderstood when he said it's not working even when he don't use the new app dir, I am also fighting with the exit animation in the app dir that's why i am here. |
Yes, in page dir, everything works fine! |
appdir is officially stable, hope this solved soon |
no idea what needs to happen to fix this, but Ill be super happy when app directory works with exit animations |
I tracked down what I think is the issue with appdir - documented here vercel/next.js#49596 TLDR; Next is sticking an unkeyed component between the |
Whaat? index.jsx import Head from 'next/head' export default function Welcome(props) {
} export async function getStaticProps(context) {
} __app.js export default function App({ Component, pageProps }) { /home.jsx export default function Home() {
} |
I'm trying to make it work with the app directory as well, but can't seem to get an exit animation working properly.. Instead the elements are just removed without an animation. I thought the if statement within the // PageWrapper.tsx
'use client';
import { useEffect, useState } from 'react';
import { usePathname } from 'next/navigation';
import { AnimatePresence, motion } from 'framer-motion';
export const PageWrapper = ({ children }: PageWrapperProps) => {
const pathname = usePathname();
const [isTransitioning, setIsTransitioning] = useState(false);
useEffect(() => {
setIsTransitioning(true);
setTimeout(() => {
setIsTransitioning(false);
}, 2000);
}, [pathname]);
const variants = (index: number) => ({
hidden: {
scaleY: 0,
transition: {
duration: 0.2,
delay: index * 0.1,
},
},
visible: {
scaleY: 1,
transition: {
duration: 0.2,
delay: index * 0.1,
},
},
});
return (
<AnimatePresence
initial={false}
onExitComplete={() => console.log('EXIT COMPLETE')}
>
{isTransitioning ? (
<>
<motion.div
key={`${pathname}_animation_1`}
variants={variants(1)}
initial="hidden"
animate="visible"
exit="hidden"
className="w-[50vw] h-[100vh] bg-rnny-primary fixed bottom-0 left-0 z-50"
/>
<motion.div
key={`${pathname}_animation_2`}
variants={variants(2)}
initial="hidden"
animate="visible"
exit="hidden"
className="w-[50vw] h-[100vh] bg-rnny-primary-tint fixed bottom-0 left-[50vw] z-50"
/>
</>
) : null}
{children}
</AnimatePresence>
);
}; What am I missing or not understanding? EDIT: |
@rnnyrk can you provide a code example of how you solved it? |
@dnlaviv For full code see my repo // layout.tsx
import './global.css';
import type * as i from 'types';
import { PageWrapper } from 'modules/layouts/PageWrapper';
const Layout = ({ children }: Props) => {
return (
<html lang="en">
<head />
<body>
<main>
<PageWrapper>{children}</PageWrapper>
</main>
</body>
</html>
);
};
type Props = i.NextPageProps<{
children: React.ReactNode;
}>;
export default Layout; // PageWrapper.tsx
'use client';
import { Fragment, useEffect, useRef, useState } from 'react';
import { usePathname } from 'next/navigation';
import { AnimatePresence, motion, Variants } from 'framer-motion';
import { cn } from 'utils';
export const PageWrapper = ({ children }: PageWrapperProps) => {
const pathname = usePathname();
const [isTransitioning, setIsTransitioning] = useState(true);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setIsTransitioning(true);
timeoutRef.current = setTimeout(() => {
setIsTransitioning(false);
window.scrollTo(0, 0);
}, 1100);
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [pathname]);
const variants: (index: number) => Variants = (index: number) => ({
hidden: {
scaleY: 0,
transition: {
duration: 0.4,
delay: index * 0.05,
ease: 'easeInOut',
},
},
visible: {
scaleY: 1,
transition: {
duration: 0.4,
delay: index * 0.05,
ease: 'easeInOut',
},
},
});
return (
<AnimatePresence>
{isTransitioning ? (
<Fragment key="route_transition_animator">
{[...Array(4).keys()].map((index) => {
return (
<motion.div
key={`${pathname}_animation_${index}`}
variants={variants(index)}
initial="hidden"
animate="visible"
exit="hidden"
/>
);
})}
</Fragment>
) : null}
{isTransitioning ? <div className="min-w-screen min-h-screen" /> : children}
</AnimatePresence>
);
};
type PageWrapperProps = {
children: React.ReactNode;
}; |
The exit animation seems to be determined by whether the element is unmounted or not. And AnimatePresence is too complex and it seems that the timing of the exit animation can't be customized, so I wrote a component to control when the motion animations are executed. |
@rnnyrk Please, don't mind my question as I'm not a web dev per se - how does your solution relate to SSR? I guess when you wrap all your components in a component with |
@colonder This solution indeed doesnt support SSR. But there shouldn't be any hydration errors, because it's just an visual element on top of the current page. Next page loaded in the background. Didn't find a better solution yet. Open for one tho |
losing the SSR of the entire app at once is not the best sacrifice for the sake of page transition animation. + on pages, the animation did not break the entire SSR. I hope that in the near future there will be a solution from the side of the developers |
#1850 (comment) |
Hi everyone,
Hope this will help someone. // src/app/layout.tsx
import PageAnimatePresence from 'components/HOC/PageAnimations/PageAnimatePresence'
const RootLayout: React.FC<React.PropsWithChildren> = ( { children } ) => (
<html lang='en'>
<body>
{/* <SiteHeader /> */}
<PageAnimatePresence>
{ children }
</PageAnimatePresence>
{/* <SiteFooter /> */}
</body>
</html>
)
export default RootLayout // src/components/HOC/PageAnimations/PageAnimatePresence/index.tsx
'use client'
import { usePathname } from 'next/navigation'
import { AnimatePresence, motion } from 'framer-motion'
import FrozenRoute from './FrozenRoute'
const PageAnimatePresence: React.FC<React.PropsWithChildren> = ( { children } ) => {
const pathname = usePathname()
return (
<AnimatePresence mode='wait'>
{/**
* We use `motion.div` as the first child of `<AnimatePresence />` Component so we can specify page animations at the page level.
* The `motion.div` Component gets re-evaluated when the `key` prop updates, triggering the animation's lifecycles.
* During this re-evaluation, the `<FrozenRoute />` Component also gets updated with the new route components.
*/}
<motion.div key={ pathname }>
<FrozenRoute>
{ children }
</FrozenRoute>
</motion.div>
</AnimatePresence>
)
}
export default PageAnimatePresence // src/components/HOC/PageAnimations/PageAnimatePresence/FrozenRoute.tsx
'use client'
import { useContext, useRef } from 'react'
import { LayoutRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime'
const FrozenRoute: React.FC<React.PropsWithChildren> = ( { children } ) => {
const context = useContext( LayoutRouterContext )
const frozen = useRef( context ).current
return (
<LayoutRouterContext.Provider value={ frozen }>
{ children }
</LayoutRouterContext.Provider>
)
}
export default FrozenRoute // src/components/HOC/PageAnimations/PageFadeInOut.tsx
'use client'
import { motion, type HTMLMotionProps, type Variants } from 'framer-motion'
const fadeInOut: Variants = {
initial: {
opacity: 0,
pointerEvents: 'none',
},
animate: {
opacity: 1,
pointerEvents: 'all',
},
exit: {
opacity: 0,
pointerEvents: 'none',
},
}
const transition: HTMLMotionProps<'div'>[ 'transition' ] = {
duration: 0.2,
staggerChildren: 0.1,
}
const PageFadeInOut: React.FC<
React.PropsWithChildren<HTMLMotionProps<'div'>>
> = ( props ) => (
<motion.div
initial='initial'
animate='animate'
exit='exit'
variants={ fadeInOut }
transition={ transition }
{ ...props }
/>
)
export default PageFadeInOut And the page route: // src/app/page.tsx
import PageFadeInOut from '@/components/HOC/PageAnimations/PageFadeInOut'
const HomePage: React.FC = ( props ) => (
<PageFadeInOut>
<h1>Home page</h1>
</PageFadeInOut>
)
export default HomePage |
I tried implementing your workaround and the animation on exit does work but I get these warnings. By any chance do you get warning errors like this:
|
Hi @zachuri, |
Not sure what the solution is, but i see the warning as well. Furthermore @alessiofrittoli i think your solution is the best one so far, so thanks for pointing it out! Although I think this issue might break HMR as well, not sure if that's the case for you guys as well? |
In some of these cases, children were not being changed, in some For whole page transitions, if it's a problem with client/server components, it's probably better to prefer the View Transitions API. |
1. Read the FAQs 👇
2. Describe the bug
I tried to integrate framer motion to next.js, I have components that appear on every page and when the road changes there is an animation.
3. IMPORTANT: Provide a CodeSandbox reproduction of the bug
https://codesandbox.io/s/github/MatteoGauthier/vertical-gallery/tree/52627524a54ee628bbde2f360c77b6d75c41593e
5. Expected behavior
Exit animation on route change
6. Video or screenshots
Enregistrement.de.l.ecran.2021-12-02.a.00.38.58.mov
Thanks
The text was updated successfully, but these errors were encountered: