framer / motion

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

Can't get shared layout animations to work with Next.js 13 #1850

Closed maurocolella closed 5 months ago

maurocolella commented 1 year ago

1. Read the FAQs 👇

2. Describe the bug

With Next 13, using separation between server and client components, shared layout animations don't work when implementing the seemingly trivial navigation menu underline. Using either the deprecated or the modern approach (ie. layoutId on the target components).

3. IMPORTANT: Provide a CodeSandbox reproduction of the bug

This issue could be heavily dependent upon styles and nesting(s). The structure honors HTML and React semantics, but it runs complex animations. Hence: the repro is complete, interactive, commentable and with full source code.

Live: https://portfolio-2023-git-feature-implement-layout-maurocolella.vercel.app/

Implementation: https://github.com/maurocolella/portfolio_2023/pull/2/files#diff-f7c999b982bfdbf5f3790e5b148e2343ffb7611b9ba579756dabab9fc76cb2e7

4. Steps to reproduce

On the example page: https://portfolio-2023-git-feature-implement-layout-maurocolella.vercel.app/

Simply click navigation items. Transition doesn't take place.

5. Expected behavior

I would expect a smooth transition to occur as in the examples.

6. Video or screenshots

N/A.

7. Environment details

Ubuntu Linux 20.04, Chrome beta, Chrome stable, Firefox.

FAQs

Framer Motion won't install

Framer Motion 7+ uses React 18 as a minimum. If you can't upgrade React, install the latest version of Framer Motion 6.

height: "auto" is jumping

Animating to/from auto requires measuring the DOM. There's no perfect way to do this and if you have also applied padding to the same element, these measurements might be wrong.

The recommended solution is to move padding to a child element. See this issue for the full discussion.

Type error with AnimateSharedLayout

AnimateSharedLayout was deprecated in 5.0. Refer to the upgrade guide for instructions on how to remove.

Preact isn't working

Framer Motion isn't compatible with Preact.

AnimatePresence isn't working

Have all of its immediate children got a unique key prop that remains the same for that component every render?

// Bad: The index could be given to a different component if the order of items changes
<AnimatePresence>
  {items.map((item, index) => <Component key={index} />)}
</AnimatePresence>
// Good: The item ID is unique to each component
<AnimatePresence>
  {items.map((item, index) => <Component key={item.id} />)}
</AnimatePresence>

Is the AnimatePresence correctly outside of the controlling conditional? AnimatePresence must be rendered whenever you expect an exit animation to run - it can't do so if it's unmounted!

// Bad: AnimatePresence is unmounted - exit animations won't run
{isVisible && (
  <AnimatePresence>
    <Component />
  </AnimatePresence>
)}
// Good: Only the children are unmounted - exit animations will run
<AnimatePresence>
  {isVisible && <Component />}
</AnimatePresence>
Dimitri-Jacquin commented 1 year ago

Has anyone tried it with spring-react?

alainkaiser commented 1 year ago

Guys, some updates from my side and a small overview of what it looks like on my end:

My setup:

My "PageTransition" component:

'use client'

import {AnimatePresence, motion} from 'framer-motion'
import {usePathname} from 'next/navigation'

interface Props {
  children: React.ReactNode
}

function PageTransition({children}: Props) {
  const pathname = usePathname()

  return (
    <AnimatePresence initial={false} mode='wait'>
      <motion.div key={pathname} variants={variants} initial='in' animate='inactive' exit='out'>
        {children}
      </motion.div>
    </AnimatePresence>
  )
}

const variants = {
  inactive: {
    opacity: 1,
    y: 0,
    transition: {
      duration: 1,
      ease: 'easeInOut',
    },
  },
  out: {
    opacity: 0,
    y: -100,
    transition: {
      duration: 1,
      ease: 'easeInOut',
    },
  },
  in: {
    y: 100,
    opacity: 0,
    transition: {
      duration: 1,
      ease: 'easeInOut',
    },
  },
}

export default PageTransition

My WebLayout component in which the PageTransition component is used (layout.tsx):

'use client'

import {Provider as WrapBalancerProvider} from 'react-wrap-balancer'
import Footer from '../../components/layout/footer'
import HeaderBar from '../../components/layout/header-bar'
import PageTransition from '../../components/misc/page-transition'
import './../../styles/globals.css'
import TailwindIndicator from '../../components/misc/tailwind-indicator'

export default function WebLayout({children}: {children: React.ReactNode}) {
  return (
    <>
      <div className='flex flex-col screen-height'>
        <WrapBalancerProvider>
          <HeaderBar />
          <PageTransition>
            <main className='flex-1'>{children}</main>
          </PageTransition>
          <Footer />
        </WrapBalancerProvider>
      </div>
      <TailwindIndicator />
    </>
  )
}

The current behaviour:

https://github.com/framer/motion/assets/18368657/39392c79-c0f9-4450-a96a-65c7130103de

What exactly am I doing wrong? I have already tried a few things, but I can't find the right information anywhere. Would greatly appreciate help.

jamesvclements commented 1 year ago

@alainkaiser it's seeming like this is an issue on the Next.js side that will need to be fixed: https://github.com/vercel/next.js/issues/49279

saurabhsri108 commented 1 year ago

I was using old architecture of pages in Next-13.4. This article helped me solved it: https://blog.logrocket.com/advanced-page-transitions-next-js-framer-motion/#adding-animatepresence

The issue was adding key={router.pathname} in the _app.tsx and it worked.

import "@/styles/globals.css";
import type { AppProps } from "next/app";
import { Inter } from "next/font/google";
import Head from "next/head";
import Header from "@/containers/Header";
import Footer from "@/containers/Footer";
import { ClerkProvider } from "@clerk/nextjs";
import Script from "next/script";
import { AnimatePresence, motion } from "framer-motion";

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-inter"
});

const variants = {
  initialState: { opacity: 0 },
  animateState: { opacity: 1 },
  exitState: { opacity: 0 }
};

export default function App({ Component, pageProps, router }: AppProps) {
  return (
    <>
      <Head>
        <title>NextJS 13 | Learn NextJS 13 with old architecture</title>
        <meta
          name="description"
          content="Learn NextJS 13 pages directory architecture"
        />
      </Head>

      {/*<Partytown debug={true} forward={["dataLayer.push"]} />*/}
      <Script id="partytown-gtm" strategy="afterInteractive" dangerouslySetInnerHTML={{
        __html: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','XXX');`
      }} />
      <ClerkProvider {...pageProps}>
        <Header />
        <AnimatePresence initial={false} mode="wait">
          <motion.main
            initial="initialState"
            animate="animateState"
            exit="exitState"
            transition={{
              duration: 0.2
            }}
            variants={variants}
            key={router.pathname}
            className={`${inter.variable} font-inter`}>
            <Component {...pageProps} />
          </motion.main>
        </AnimatePresence>
        <Footer />
      </ClerkProvider>
    </>
  );
}
IntiSilva commented 1 year ago

Having the same problem with the new app directory, a thing I noticed is that it's exclusively the exit animation bc I also have the "initial" prop set to false in AnimatePresence and that works hahaha

`export default function RootLayout({children }) { return (

<html lang="en">
  <body className={inter.className}>
    <Navbar />
    <AnimatePresence type='wait' initial={false}>
    {children}
    </AnimatePresence>
  </body>
</html>

) }`

GentikSolm commented 1 year ago

Ive made some progress on this. I still cannot get exit animations to work, but this snippet works well for enter animations that are pretty smooth. The only problem is that these dont load when the server is initial SSRing the page. after its cached it works fine. In the video, the documents page SSR's on the first click, then after that you get decently smooth transitions.

// app/template.tsx
'use client';

import clsx from 'clsx';
import { useEffect, useState } from 'react';

export default function Template({ children }: { children: React.ReactNode }) {
  const [fade, setFade] = useState(false);
  useEffect(() => {
    setFade(true);
    return () => {
      setFade(false);
    };
  }, []);
  console.log(fade);
  return (
    <div
      className={clsx(
        fade ? 'translate-y-0 opacity-100' : 'translate-y-3 opacity-0',
        'transition duration-500 ease-out',
      )}
    >
      {children}
    </div>
  );
}

https://github.com/framer/motion/assets/34988548/f18dec5d-ec75-40f5-8738-892349efb3a2

AksRK commented 1 year ago

It turned out to start the animation of entry and exit. Maybe someone will be useful 😅

https://github.com/AksRK/next13-page-transition

niloodev commented 1 year ago

Framer motion devs dont want to fix this? like, its been months now

GentikSolm commented 1 year ago

Framer motion devs dont want to fix this? like, its been months now

This is a direct issue with next, and not framer. Once this issue is resolved it should work as indeed.

vikyw89 commented 11 months ago

still doesn't work

4zad commented 11 months ago

still doesn't work

I can second this 😭

That being said I was able to get it working without an exit animation by using the motion tag as the root tag on each page (i.e., home, about, contact) and not using mode='wait' with AnimatePresence.

Dimitri-Jacquin commented 11 months ago

Inter sport

Envoyé à partir de Outlook pour iOShttps://aka.ms/o0ukef


De : Muhammad Ahmed @.> Envoyé : Wednesday, July 19, 2023 3:02:36 AM À : framer/motion @.> Cc : DIMITRI JACQUIN @.>; Mention @.> Objet : Re: [framer/motion] Can't get shared layout animations to work with Next.js 13 (Issue #1850)

still doesn't work

I can second this 😭

That being said I was able to get it working without an exit animation by using the motion tag as the root tag on each page (i.e., home, about, contact) and not using mode='wait' with AnimatePresence.

— Reply to this email directly, view it on GitHubhttps://github.com/framer/motion/issues/1850#issuecomment-1641217682, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AB7YGEVLD7M5E4MMVNYECOTXQ4W2ZANCNFSM6AAAAAATJVFL2I. You are receiving this because you were mentioned.Message ID: @.***>

ShueiYang commented 11 months ago

@4zad I am doing the same thing for the enter animation, but the thing is enter animation can work anyway even if you don't use AnimatePresence, but for Exit Animation the AnimatePresence is required, so still can't figure it out for exit animation.

pzpsofficial commented 10 months ago

If someone could provide a link with example of native implementation with just CSS I would be grateful. Also is there a way to achieve this with just plain JS? Like some kind of hooks, which would allow me to use gsap or any other animation library?

@maurocolella I've seen you stating that finding those examples of effects in just CSS is easy, but to be honest, all I could find was Framer Motion staff :D Seems like a standard in React community (coming from Vue.js background).

nmitic commented 10 months ago

If someone could provide a link with example of native implementation with just CSS I would be grateful. Also is there a way to achieve this with just plain JS? Like some kind of hooks, which would allow me to use gsap or any other animation library?

@maurocolella I've seen you stating that finding those examples of effects in just CSS is easy, but to be honest, all I could find was Framer Motion staff :D Seems like a standard in React community (coming from Vue.js background).

In order to do native JS css anim we need routing guards which has been removed or planned to come in the app dir.

Also, even without framer you would still need to hack things around.

I honestly prefer to wait for issue to be fixed on next js.

pzpsofficial commented 10 months ago

If someone could provide a link with example of native implementation with just CSS I would be grateful. Also is there a way to achieve this with just plain JS? Like some kind of hooks, which would allow me to use gsap or any other animation library? @maurocolella I've seen you stating that finding those examples of effects in just CSS is easy, but to be honest, all I could find was Framer Motion staff :D Seems like a standard in React community (coming from Vue.js background).

In order to do native JS css anim we need routing guards which has been removed or planned to come in the app dir.

Also, even without framer you would still need to hack things around.

I honestly prefer to wait for issue to be fixed on next js.

Thank's for reply. What I am thinking about (which is of course hacking things around) - is assigning an ID to children, that should be wrapped by Framer Motion / any other "delay component unmount" and using portal, wouldn't it do the trick?

maurocolella commented 10 months ago

@pzpsofficial I will turn off notifications for this thread after this reply, as I receive my fair share of notifications and cannot keep up with everything, but the actual standards of internet and front-end dev should always be CSS, HTML and ECMAscript first. By that I mean: it's critical to have a good grasp on them, and frameworks are just sugar coating.

That said, people do rely on Framer Motion because it abstracts these away, and simplifies the task of translating a visual effect into CSS/JavaScript under the hood, but it's really (almost) all that it does.

Does it very nicely, but still.

Whether using Vue or Next or any other framework, any effect you see can be accomplished using web standards.

Here using pure CSS. It is not optimal, but it is pure CSS: https://codepen.io/rm/pen/AXpmja

Here is another example that uses more elegant coding techniques along with a step-by-step breakdown: https://vitaliy-kirenkov.medium.com/sliding-underline-in-navigation-b95399fc4601

Also, visual effects like this do not and should not depend on routing, but on user interaction (so you could trigger them independently on separate events as well, like mouseenter/mouseleave).

Effects are a styling concern, independently from routing, navigation, or content. Either should be able to work without the other.

Finally, I found this by searching for "sliding underline nav" on Google - Google is adaptive, returns results based on what you were searching before. Hence: keep it simple.

Back to basics works.

pzpsofficial commented 10 months ago

Thanks for reply, I think there was a misunderstanding as I had page transitions in mind the whole time.

In cases like this simple underline the basics are even more than enough ;)

Kind regards, Bartek

Gaurav4604 commented 9 months ago

Hi everyone, I followed this thread and came across a hacky solution, It seems to work well for me

Check it out, might be useful.

using

The thread itself has a working example, but I've implemented a solution with much less code as well.

My working example (it is a frontendmentor.io challenge solution)

Repo link

This is the FrozenRouter implementation that makes exit animations possible

Here is where the FrozenRouter component is being used

I hope this is helpful to all facing this issue.

joshdavenport commented 9 months ago

Feels like npostulart is right in their asessment that this feels hacky but it does seem to work pretty well. Has anybody tried this in the wild and found any weirdness or conversely that it just works with no real issue?

Gaurav4604 commented 9 months ago

@joshdavenport I’ve added a personal example to my comment, where I’ve used it for a page transition itself, it does seem to work perfectly fine.

joshdavenport commented 9 months ago

Indeed @Gaurav4604, it's your example that triggered me wondering about it - seeing more people use the technique I mean. I'm wondering if anybody has managed to try this in prod in an app with many moving parts, complex routing, complex state, etc.

Gaurav4604 commented 9 months ago

Ah ok, that makes sense, I'll let you all know via this thread if I do so myself as well. Does seem to get the job done for time-being.

cdebotton commented 9 months ago

Indeed @Gaurav4604, it's your example that triggered me wondering about it - seeing more people use the technique I mean. I'm wondering if anybody has managed to try this in prod in an app with many moving parts, complex routing, complex state, etc.

The biggest concern is more so that the technique uses code that isn’t publicly exported from nextjs, which would mean it’s not intended for anything but internal use. A minor or patch update could break this functionality without notice under semver.

cdebotton commented 9 months ago

Just a follow up, I'm trying this approach in Next 13.5 which just came out today and I'm finding that any components underneath the Frozen Router Component won't update via HMR which makes it pretty much unusable.

Gaurav4604 commented 9 months ago

Just a follow up, I'm trying this approach in Next 13.5 which just came out today and I'm finding that the any components underneath a Frozen Router won't update via HMR which makes it pretty much unusable.

@cdebotton I just updated my import for frozenRouter for it to work with AnimatePresence on nextJS 13.5.1, You can find it here but I get it, this needs a much more permanent fix, since these are not publicly exported components from nextJS.

joshdavenport commented 9 months ago

Just a follow up, I'm trying this approach in Next 13.5 which just came out today and I'm finding that the any components underneath a Frozen Router won't update via HMR which makes it pretty much unusable.

Ouch. Yes, this is a deal breaker. I guess not too bad if you're only working on something small but for anything else this drastically affects the development experience.

Seems like we're in a kind of limbo with this, framer obviously can't change what Next is doing, and there's not really been any movement on the Next side on this (https://github.com/vercel/next.js/issues/49279) which is a shame but understandable - they have more demanding priorities for sure.

baptistebriel commented 9 months ago

Hi there! Whereas this solution worked fine for me to get page transitions to work with Next.js 13, I am getting the following error from Framer Motion's AnimatePresence & PopChildMeasure. Any fixes existing to avoid this error? The lifecycle of animations is working fine, just the error is being thrown.

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `PopChild`.
Frumba commented 9 months ago

@baptistebriel Check the new version of the Child with the forwardRef here https://github.com/npostulart/nextgram-with-page-transitions/blob/main/src/app/ClientLayout.tsx#L12 😄

Gaurav4604 commented 9 months ago

Hi there! Whereas this solution worked fine for me to get page transitions to work with Next.js 13, I am getting the following error from Framer Motion's AnimatePresence & PopChildMeasure. Any fixes existing to avoid this error? The lifecycle of animations is working fine, just the error is being thrown.

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `PopChild`.

Take a look at the docs for the same It mentions that if you're to use a custom component as a child of AnimatePresence with "popLayout", you need to pass a ref accordingly, so as to let framer motion track the component.

baptistebriel commented 9 months ago

Thank you @Frumba and @Gaurav4604 — that worked, thank you for the quick reply.

refirst11 commented 9 months ago

Hello. I haven't seen it for a while, but has the exit motion is working?

wangwailok commented 9 months ago

Hello. I haven't seen it for a while, but has the exit motion is working?

just tried with 13.5.3 and "framer-motion": "^10.16.4", still not working

devchitect commented 9 months ago

Actually i got an idea and it quite a bit not a hacky solution, Instead of using exit animation, because next router and ssr works different than react. I use 2 enter animation, this requires router.push() instead of using href in component.

1 for exit, i use setTimeOut() to create a delay before it actually changes in url, before url change, i change state which display an component for exit only.

and when the url changed, i use useEffect to change the state again to hide that exit component. then the default enter animation come to work.

const navigate = (currpath : string) => {
        const navigateDelay = 1000;
        dispatch(transition('exit'));
        setTimeout(()=>{
            router.push(currpath, { scroll: false });        
        }, navigateDelay)
 }
 useEffect(() => {
        dispatch(transition('enter'));
    },[dispatch,path])

    return (
       <>
            <div>
                {transitionValue === 'exit' && <ExitAnimation/>}

                <motion.div
                key={path}
                style={{background: "#000000"}}
                className='fixed top-full right-0 left-0 w-screen h-screen z-30 '
                variants={slidedown}
                initial='initial'
                animate='animate'
                transition={{delay: 0, duration: 1, ease: 'easeInOut'}}
                />

                {children}
                {/* <ScrollTrigger/> */}
            </div>
        </>
    )
evankirkiles commented 8 months ago

Here's my experimental branch of next.js that enables exit transitions with AnimatePresence by adding in a custom Glue component (supplied in a glue.tsx) where we can monitor the lifecycle of routes:

Draft PR: https://github.com/vercel/next.js/pull/56591 Discussion: https://github.com/vercel/next.js/discussions/56594

The final solution in next.js is going to have to inevitably be something of this sort, as this enables route transitions even within parallel routes (where you can't rely on pathname or global navigation events to perform the transitions).

MrUltimate commented 7 months ago

Has anyone tried this with NextJS 14?

rfros commented 6 months ago

Has anyone tried this with NextJS 14?

yes -- issue still present in 14.0.4; am hopeful draft PR from @evankirkiles gets some eyeballs on it... seems to have gone somewhat stale, though 😞 https://github.com/vercel/next.js/pull/56591

mattgperry commented 5 months ago

Closing here as framework-specific integrations isn't something I'm going to concentrate on this year.