Jump to content
Search Community

Nuxt 3 Pagetransition destroys ScrollTrigger.

LimitlessDigital test
Moderator Tag

Go to solution Solved by LimitlessDigital,

Recommended Posts

Hi,

I am busy with an website where I use ScrollTrigger a LOT! Now I want to add pagetransition through the nuxt.config. See screenshot:

image.thumb.png.4d67a8cf7c5bfd94bbad8ca0a8c6dd98.png

 

But now when I change a page, my whole ScrollTrigger isn't working anymore. When I remove the rule above everything works fine.

 

I have read all the topics on this website but I couldn't find a quick/easy fix. Is there a fix already for this problem? I need a pagetransition between all my pages. And I use ScrollTrigger on almost every page as wel ... I saw this codepen too, but this is really complicated to implement in my project ... https://stackblitz.com/edit/nuxt-starter-bthjlg?file=helpers%2FtransitionConfig.js,nuxt.config.ts,app.vue,composables%2Ftransition-composable.js,pages%2Fscroll.vue,pages%2Findex.vue

 

Link to comment
Share on other sites

It's pretty tough to troubleshoot without a minimal demo - the issue could be caused by CSS, markup, a third party library, your browser, an external script that's totally unrelated to GSAP, etc. Would you please provide a very simple CodePen or CodeSandbox that demonstrates the issue? 

 

Please don't include your whole project. Just some colored <div> elements and the GSAP code is best (avoid frameworks if possible). See if you can recreate the issue with as few dependancies as possible. If not, incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, then at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

 

Here's a starter CodePen that loads all the plugins. Just click "fork" at the bottom right and make your minimal demo

See the Pen aYYOdN by GreenSock (@GreenSock) on CodePen

 

If you're using something like React/Next/Vue/Nuxt or some other framework, you may find StackBlitz easier to use. We have a series of collections with different templates for you to get started on these different frameworks: React/Next/Vue/Nuxt.

 

Once we see an isolated demo, we'll do our best to jump in and help with your GSAP-specific questions. 

Link to comment
Share on other sites

Hi,

 

Besides requesting a minimal demo, what exactly is complicated about implementing the approach in that example on your project? It uses GSAP Context, regular Vue life cycle hooks and an extremely simple composable to know exactly when to create the animations and ScrollTrigger instances on the new page.

 

I'd be curious to know how it interferes with your project.

 

Finally be aware that if you're applying any type of transform on your page transition and then you are  pinning something with ScrollTrigger that will cause problems, so it'd be recommended to completely remove all transforms before creating the ScrollTrigger instances.

 

Happy Tweening!

Link to comment
Share on other sites

I'm using this starter: https://stackblitz.com/edit/nuxt-starter-bthjlg?file=README.md

 

My component looks like this:

<script setup>
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { onMounted, onBeforeUnmount } from 'vue'
import InlineButton from '~/components/Buttons/InlineButton'

gsap.registerPlugin(ScrollTrigger)

onMounted(() => {
  const parallaxItem = document.querySelectorAll('.parallax__row')
  parallaxItem.forEach((item) => {
    gsap
      .timeline({
        scrollTrigger: {
          trigger: item,
          start: 'top bottom',
          scrub: true,
        },
        defaults: { ease: 'none' },
      })
      .to(item.querySelector('img'), {
        yPercent: 60,
      })
      .to(
        item,
        {
          yPercent: -10,
        },
        0
      )
  })
})

onBeforeUnmount(() => {
  ScrollTrigger.getAll().forEach((trigger) => trigger.kill())
})
</script>

 

On the first load it works okay (on the homepage the parallax grid). But when you go to the "Fietsendragers" page. The second URL in the menu, and then back to home. The scrolltrigger doesn't seem to work anymore. How can this be solved? https://63e7b435ccbb6b4206b19fa6--uebler.netlify.app

 

This happens everytime I work with pagetransitions on this website.

Link to comment
Share on other sites

Would you mind forking that Stackblitz and recreating the issue there so that we can see it in context? It's not practical to troubleshoot a live web site - there are way too many factors involved and it's impossible to tweak things and see how it affects behavior. You'll greatly increase your chances of getting an answer if you post a well-crafted minimal demo that clearly shows the problem.

 

Here's a Nuxt starter template. (Just hit the "fork" button)

 

Also make sure you're doing proper cleanup, like Rodrigo said. Your sample code doesn't seem to be doing that. gsap.context() is super handy for that. 

 

Minor note: I see that you're animating the trigger element itself inside your ScrollTriggered animation. That's almost always a bad idea because it can throw off the positioning. It's typically best to just wrap that element that you're animating in a container element and use the container as the trigger instead. 

 

Once we see an isolated demo, I'm sure it'll really help get an answer for you. 

Link to comment
Share on other sites

Hi,

 

I just saw the live site you have on netlify and you're using the same page transition animation we have in this example:

https://stackblitz.com/edit/nuxt-starter-bthjlg

 

There are a couple of things that I already mentioned and I'll circle back to them:

4 hours ago, LimitlessDigital said:

Looks like the whole 'onMounted' isn't waiting for the full 'Mount'

Nope, that is working exactly as it should, the problem is when the page is being mounted. Just before the in animation runs, the page is mounted and rendered. At this stage the onEnter hook runs this:

onEnter: (el, done) => {
  gsap.set(el, { autoAlpha: 0, scale: 0.8, xPercent: -100 });
  gsap
    .timeline({
    paused: true,
    onComplete: done,
  })
    .to(el, { autoAlpha: 1, xPercent: 0, duration: 0.25 })
    .to(el, { scale: 1, duration: 0.25 })
    .play();
},

Then the onMount hook runs, probably after one or two ticks after the timeline instance is created (most likely 1 tick, that is 16 milliseconds). At that point all the calculations ScrollTrigger does will be wrong, that's why we need to wait for the transition to be completed and only then create the ScrollTrigger instances. So as I mentioned on the the my first post on this thread, this is about when the animations are created, hence the need for the composable. This can also be achieved using either VueX or Pinia as well, the idea is anything that can be reactive, so when it toggles the page knows about it and can create the animations.

 

You are in fact using the same animation which includes scale and translation on the X axis. I'd recommend you to remove those styles because if you use ScrollTrigger to pin any element you'll see the same issues you can read about in this thread (where you can find the solution as well):

Finally I can't see why an extremely simple composable can't be implemented. You are mentioning Nuxt3 which works on top of Vue3 so that feature is actually supported. In this subject we find ourselves needing an extremely simple minimal demo that illustrates why you can't use a composable and simple life cycle hooks.

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

Hi @Rodrigo,

 

Thanks for your reaction, that clarifies a LOT.

How would you initialize the scrolltrigger on a cold refresh then? With the composable. When you go to a subpage and then refresh, the watcher doesn't trigger so you can only initialize the scrolltrigger on the mounted right?

 

Or how would you do it?

 

I updated the pagetransition though: https://63e8c1da81d24b68a483a5f7--uebler.netlify.app . But some blocks are still not working after "page switch". Example when you go from "home" to "fietsendragers" the cards aren't animating. But when you refresh they do.

 

Modified the transitionhelper to this:

 

import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'

import { useTransitionComposable } from '../composables/transition-composable'
gsap.registerPlugin(ScrollTrigger)

const { toggleTransitionComplete } = useTransitionComposable()

const pageTransition = {
  name: 'page-transiton',
  mode: 'out-in',
  onEnter: (el, done) => {
    gsap.fromTo(
      '.pagetransition',
      {
        y: 0,
      },
      {
        y: '-100%',
        duration: 0.8,
        ease: 'power4.inOut',
        force3D: true,
        onComplete: () => {
          toggleTransitionComplete(true)
          done()
          ScrollTrigger.refresh()
        },
      }
    )
  },
  onLeave: (el, done) => {
    toggleTransitionComplete(false)
    gsap.fromTo(
      '.pagetransition',
      {
        y: '100%',
      },
      {
        y: 0,
        duration: 0.8,
        ease: 'power4.inOut',
        force3D: true,
        onComplete: () => {
          done()
        },
      }
    )
  },
}

export default pageTransition

 

Without the "ScrollTrigger.refresh()" it ain't working at ALL after a page switch ... can't really find what's going wrong here. I can give you access to my git repo if you DM you your git credentials.

 

Link to comment
Share on other sites

  • Solution

I fixed it like this: on the mounted check if the transition is completed (on a hard refresh it is always completed) so then run the init timeline. When you switch pages the transitioncomplete toggles from false to true, I've set a watcher to watch this value and then init the timeline on change to true. Do you think this solution is okay @Rodrigo? Seems to work.

 

watch(
  () => transitionState.transitionComplete,
  (state) => {
    if (state) {
      initTimeline()
    }
  }
)

onMounted(() => {
  if (transitionState.transitionComplete) {
    initTimeline()
  }
})

 

Link to comment
Share on other sites

Hi,

 

As @mvaneijgen says: "If it works, it works" ;)

 

What you could explore is add a nextTick callback in your watcher and see how that works.

 

Although I believe the issue here is that watchers are lazy by design, so you need to get the watcher to work on the first mount and not on every update:

https://vuejs.org/guide/essentials/watchers.html#eager-watchers

 

What gets my attention is that while watchers are lazy in Vue while Nuxt uses suspense (which is still in experimental) to get a similar effect, that's why is not included in those examples as it is on the Vue example (line 31):

https://stackblitz.com/edit/vitejs-vite-w8wtpj?file=src%2Fviews%2FScrollView.vue

 

Give that a try and let us know how it works.

Happy Tweening!

Link to comment
Share on other sites

  • 1 month later...
On 2/14/2023 at 4:13 PM, Rodrigo said:

Hi,

 

As @mvaneijgen says: "If it works, it works" ;)

 

What you could explore is add a nextTick callback in your watcher and see how that works.

 

Although I believe the issue here is that watchers are lazy by design, so you need to get the watcher to work on the first mount and not on every update:

https://vuejs.org/guide/essentials/watchers.html#eager-watchers

 

What gets my attention is that while watchers are lazy in Vue while Nuxt uses suspense (which is still in experimental) to get a similar effect, that's why is not included in those examples as it is on the Vue example (line 31):

https://stackblitz.com/edit/vitejs-vite-w8wtpj?file=src%2Fviews%2FScrollView.vue

 

Give that a try and let us know how it works.

Happy Tweening!

Hello.
About this issue, I'm having the exact same problem. The stackblitz example works fine, there is just the small problem mentioned above that on a hard refresh the watcher doesn't get triggered and therefore the animation doesn't execute (only on refresh, if you navigate away and come back, th en the watcher will trigger with the animation).
One way to solve it would be to do something like op said, having a function that will execute onMounted and use that same function inside the watcher. It just doesn't feel right to do and I wish there was a workaround.
 

I tried using nextTick with the app.vue 'toggleTransitionComplete' but the problem is the same.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...