Jump to content
Search Community

Implementing SmoothScroller with fixed elements in Nuxt 3 and CSS page transitions

Tulip781 test
Moderator Tag

Recommended Posts

Hello GSAP Community,

 

I'm currently working on a Nuxt3 project and facing some challenges with implementing SmoothScroller, particularly when using CSS page transitions and setTimeout. I've already checked the minimal reproduction example but still can't get it to function as expected.

 

My project involves CSS page transitions, and to manage these transitions, I'm employing a Timeout during the mount phase. This approach was a recommended solution in a previous discussion (https://gsap.com/community/forums/topic/39242-inconsistent-scroll-trigger-with-pagetransitions-in-nuxt-3/#comment-195335). However, I'm struggling with the following:

 

Initiating Smooth Scroll: I need guidance on correctly initiating smooth scroll, considering I'm using setTimeout and multiple GSAP tweens.

 

Pinning Elements: I'm trying to pin elements in a way that replicates fixed positions, but I encounter a jumping effect. The issues include:

 

Pinned elements jumping or resizing once pinned.

 

Pinned elements moving erratically during route changes.

 

My goal is implement the SmoothScroller into pages with GSAP Scroll Trigger tweens/timelines and elements that behave like they're fixed.

 

This is the app without smooth scroller, and it is the behaviour I wish to replicate:  Nuxt 3 Demo - Without SmoothScroller.

 

Here's a link to the same app where I'm attempting to implement the smooth scroller: Nuxt 3 Demo - With Smooth Scroller.

 

I'm aware of the method to place fixed elements outside the container. However, due to the complex nature of my layout, where fixed elements are deeply nested within grids, I’d prefer to use the pinning technique.

 

Any insights, advice, or suggestions to overcome these challenges would be highly appreciated. Thank you in advance for your time and help!

Link to comment
Share on other sites

Hi,

 

I think this most likely stems for overcomplicating things too much with the whole setTimout funky business. My guess is that this is throwing the whole timing of your app off, in terms of when ScrollSmoother and ScrollStrigger instances are being created, that's why in our demo with page transitions we're using just GSAP for everything and have that composable that tells us when the page transition is completed. I noticed you don't have any of that in your setup.

 

Here is a simple demo of using ScrollSmoother and ScrollTrigger with pinning on a Nuxt app using different routes:

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

 

Unfortunately I don't have time now to create a demo that also includes page transitions in it, but you should be able to extrapolate things from both demos (the one I linked above and the one with page transitions) in order to make things work.

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

Thanks for the reply Rodrigo. So, if I convert my example to include gsap transitions for pages, at what point should I instantiate the scroll smoother? After the transition is complete? And do I need to kill this / reset this each page change?

 

Does this mean the jumping pins is likely because of the funky business with the set time out? I'm going to try and covert the page transition to a GSAP page transition to remove the timeouts.

 

Thanks for the help.

Link to comment
Share on other sites

5 hours ago, Tulip781 said:

So, if I convert my example to include gsap transitions for pages, at what point should I instantiate the scroll smoother? After the transition is complete? And do I need to kill this / reset this each page change?

The main approach in our page transitions demo is basically the bullet proof way of doing this. You need to know when the new route has been completely mounted and rendered, with that you can be sure that the height of the document is accurate and the calculations made by ScrollTrigger will be as well. GSAP gives you a more fine grain control over this because the way to know if a CSS animation is completed is a bit difficult and not 100% certain. GSAP on the other way will know this.

 

So the flow is this:

  1. You navigate to a new route
  2. The out animation is played
  3. The ScrollSmoother instance, the ScrollTrigger instances and any other GSAP instance should be killed (here GSAP Context's revert is super handy)
  4. The in animation of the next route starts
  5. The in animation of the next route is completed
  6. First create the ScrollSmoother instance
  7. Now create the ScrollTrigger and other GSAP instances as well
  8. Live a happy life!

I'm not saying that this cannot be achieved with CSS transitions, it requires a bit more planning and deeper knowledge of the transitions durations, that's all. This is simpler with GSAP IMHO, but I'm 100% biased because I've been using GSAP for over 12 years, so I never touched CSS transitions because I never needed to, that's all. I don't use frameworks like Angular, Alpine, Ember, Astro just to mention a few, that doesn't mean that I consider that they are bad, I just never had the need to use them, that's all.

 

Hopefully this clear things up.

Happy Tweening!

Link to comment
Share on other sites

Do I need to do anything to the scroll smooth or scroll trigger instances when I resize the page? I notice that if I am scrolled to the bottom of the page, and I resize from the bottom up and down, I can shuffle my way up the page. I would normally expect the page to be pinned at the bottom. Not sure if this behaviour is related to scroll trigger, or scroll smoother? And if i need to do anything to those instances on window resize? Thanks for letting me know. https://stackblitz.com/edit/nuxt-starter-hud37n?file=README.md

Link to comment
Share on other sites

Here is my final Vue file with those adjustments. Does this seem correct to you? 

 

I've changed all transitions to GSAP transitions. I notice that if I quickly scroll down the page as soon as routing landing on the page, it seems to mess up the initialisation of the scroll trigger / smoother. 

 

Also am I suppose to set watchers for every ref I need to attach triggers too? As some refs may not be ready by the time the transition completion state is truthy.

 

<template>
  <div>
    <TheHeader class="fixed top-0" />
    <div id="smooth-wrapper">
      <div id="smooth-content">
        <main ref="background">
          <div ref="expWrap" class="exp relative">
            <ClientOnly><HomeTheExperience ref="experience" /></ClientOnly>
          </div>
          <div class="z-50 flex h-[calc(100svh-64px)] w-screen items-center justify-center">
            <transition name="fade">
              <img
                v-if="showBannerLogo"
                ref="logo"
                class="logo-selector z-30 h-[150px] md:h-[250px]"
                src="~/assets/logo.svg"
                alt="Logo"
              />
            </transition>
          </div>
          <div
            ref="skyColour"
            class="fixed top-0 -z-10 h-[100lvh] w-screen bg-gradient-to-b from-[#1c2c47] to-[#7bace8]"
          ></div>
          <HomeShowreelSection v-if="data" :home="data" />
        </main>
      </div>
    </div>
  </div>
</template>

<script setup>
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { ScrollSmoother } from 'gsap/ScrollSmoother';
import transitionConfig from '../helpers/transitionConfig';
import { useTransitionComposable } from '../composables/transition-composable';
import { homeQuery } from '@/queries/contentQueries';

const { transitionState } = useTransitionComposable();
let ctx;
const expWrap = ref(null);
const skyColour = ref(null);
const background = ref(null);
const experience = ref(null);
const showBannerLogo = ref(false);
const logo = ref(null);
const data = await useSanityData({ query: homeQuery });

const mainStore = useMainStore();
mainStore.setLogoVisibility(false);
gsap.registerPlugin(ScrollTrigger, ScrollSmoother);

watch(logo, (newValue) => {
  if (transitionState.transitionComplete) {
    ctx.add(() => {
      ScrollTrigger.create({
        trigger: logo.value,
        start: 'bottom top',
        end: 'center center',
        onLeave: () => {
          mainStore.setLogoVisibility(true);
        },
        onEnterBack: () => {
          mainStore.setLogoVisibility(false);
        },
      });
    });
  }
});

watch(() => transitionState.transitionComplete, (newValue) => {
  if (newValue) {
    ctx = gsap.context(() => {
      ScrollSmoother.create({
        smooth: 1,
        effects: true,
        smoothTouch: 0.1,
      });
      ScrollTrigger.create({
        trigger: skyColour.value,
        start: 'top top',
        end: 90000,
        pin: true,
        pinSpacing: false,
      });
      ScrollTrigger.create({
        trigger: expWrap.value,
        start: 'top top',
        end: 90000,
        pin: true,
        pinSpacing: false,
      });
      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: background.value,
          start: 'top top',
          end: 'bottom bottom',
          scrub: true,
        },
      });
      tl.to(skyColour.value, { opacity: 0 });
      if (expWrap.value) {
        tl.to(expWrap.value, { opacity: 0, autoAlpha: 0 });
      }
    }, '#smooth-wrapper');
  }
});

onMounted(() => {
  showBannerLogo.value = true;
});

onUnmounted(() => {
  mainStore.setLogoVisibility(true);
  ctx && ctx.revert();
});

definePageMeta({
  pageTransition: transitionConfig,
  layout: 'landing',
});
</script>

 

  • Like 1
Link to comment
Share on other sites

Hey windpixel, thanks for the message. I’m out at the minute but I will share a minimal demo once back in. How I found it to work was to initialise smooth scroller in the watcher that waits for the transition state to finish. So far I’ve found that it generally re initialises each time on the new page, because you revert the context each time when the component unmounts, and it is added to the context. I’m still have problems when u scroll to quickly after the page loads, not giving scroll smoother time to start. 

  • Like 1
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...