Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom page / route transitions in Next.js

I'm trying to achieve callback-based route transitions using Next.js's framework and Greensock animation library (if applicable). For example when I start on the homepage and then navigate to /about, I want to be able to do something like:

HomepageComponent.transitionOut(() => router.push('/about'))

ideally by listening to the router like a sort of middleware or something before pushing state

Router.events.on('push', (newUrl) => { currentPage.transitionOut().then(() => router.push(newUrl)) });

Main Problem

The main problem is that I also have a WebGL app running in the background, decoupled from the React ecosystem (since it uses requestAnimationFrame). So the reason I want callback-based transitions is because I need to run them after the WebGL transitions are done.

Current Implementation

I've looked into using React Transition Group and I've seen the docs for the Router object but neither seems to be callback-based. In other words, when I transition to a new page, the WebGL and the page transitions run at the same time. And I don't want to do a hacky solution like adding a delay for the page transitions so they happen after the WebGL ones.

This is what I have right now:

app.js

<TransitionGroup>
  <Transition
    timeout={{ enter: 2000, exit: 2000 }}
    // unmountOnExit={true}
    onEnter={(node) => {
      gsap.fromTo(node, { opacity: 0 }, { opacity: 1, duration: 1 });
    }}
    onExit={(node) => {
      gsap.to(node, { opacity: 0, duration: 1 });
    }}
    key={router.route}
  >
    <Component {...pageProps}></Component>
  </Transition>
</TransitionGroup>

webgl portion

Router.events.on('routeChangeStart', (url) => {
  // transition webGL elements

  // ideally would transition webGL elements and then allow callback to transition out html elements
});

I've also tried using the eventemitter3 library to do something like:

// a tag element click handler
onClick(e, href) {
  e.preventDefault();
  this.transitionOut().then(() => { Emitter.emit('push', href); });
  // then we listen to Emitter 'push' event and that's when we Router.push(href)
}

However this method ran into huge issues when using the back / forward buttons for navigating

like image 824
Chang Liu Avatar asked Nov 14 '25 12:11

Chang Liu


1 Answers

Bit late on this but I was looking into this myself today. It's really easy to use Framer Motion for this but I also wanted to use GSAP / React Transition Group.

For Framer Motion I just wrapped the Next < Component > with a motion component:

  <motion.div
    key={router.asPath}
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }}
    exit={{ opacity: 0 }}
  >
    <Component {...pageProps} />
  </motion.div>

For GSAP / React Transition Group, not sure if this is the right way but it's working as intended for me (see comments):

  const [state, setstate] = useState(router.asPath) // I set the current asPath as the state

  useEffect(() => {
  const handleStart = () => {
    setstate(router.asPath) // then on a router change, I'm setting the state again
    // other handleStart logic goes here 
  }
  const handleStop = () => {
    ... // handleStop logic goes here
  }

  router.events.on("routeChangeStart", handleStart)
  router.events.on("routeChangeComplete", handleStop)
  router.events.on("routeChangeError", handleStop)

  return () => {
    router.events.off("routeChangeStart", handleStart)
    router.events.off("routeChangeComplete", handleStop)
    router.events.off("routeChangeError", handleStop)
  }
}, [router])

  <Transition
    in={router.asPath !== state} // here I'm just checking if the state has changed, then triggering the animations
    onEnter={enter => gsap.set(enter, { opacity: 0 })}
    onEntered={entered => gsap.to(entered, { opacity: 1, duration: 0.3 })}
    onExit={exit => gsap.to(exit, { opacity: 0, duration: 0.3 })}
    timeout={300}
    appear
  >
    <Component {...pageProps} />
  </Transition>
like image 62
Tom Barber Avatar answered Nov 17 '25 05:11

Tom Barber