Jump to content
Search Community

ScrollSmoother - refresh after routing

Born05 test
Moderator Tag

Recommended Posts

Just a quick question: I'm trying to replace LocomotiveJS with the new ScrollSmoother plugin inside a Nuxt site.

On the initial page load everything seems to work fine, but when I navigate to another page, the contents of the #smooth-content div get replaced, but the height that is set inline on the body doesn't get updated. How would I go about refreshing that?

 

Thanks!

Link to comment
Share on other sites

Hi @Born05

 

Would you be kind enough to make a minimal demo of your solution in CodeSandbox? I'm trying to nail down some best practices for how to handle ScrollSmoother in frameworks like Nuxt, and then put that in a blog for our users. Seeing how other people are using it would definitely help shape that as they might have some better ideas.

 

This is how created it in Nuxt. Note that I don't have any routing. I was just trying to come up with with a way to create a ScrollSmoother that can be accessed inside any component.

 

https://codesandbox.io/s/gsap-scrollsmoother-nuxt-js-starter-2zgxyo?file=/pages/index.vue

 

 

  • Like 2
Link to comment
Share on other sites

I tried by forking your demo, and adding a new page. Unfortunately what works for me in my current project doesn't seem to work entirely inside codesandbox. You can find my demo here: https://codesandbox.io/s/gsap-scrollsmoother-nuxt-js-starter-forked-s727oq

 

The things I did:

 

- create an extra about page

- added a header to the layout with nuxt links to home and about page

- added a `gsap.client.js` plugin that registers the gsap plugins and adds convenience methods to Vue prototype

- added `pageTransition` to nuxt.config.js, which uses the hooks to emit global Nuxt events for resetting, stopping or starting scroll

- added `scrollSmoother` mixin, used by default layout, that sets up the ScrollSmoother instance and listens for those Nuxt events

 

Upon routing, scroll position gets reset to 0 as I would expect, but the inline height set on the body doesn't get updated, so the about page can be scrolled way beyond its actual height. And I'm not sure why that is.

 

 

  • Like 1
Link to comment
Share on other sites

I might be doing something wrong here, but I tried to add those urls in CodeSandbox under `Dependencies`, but then CodeSandbox throws an error: 

 

error https://assets.codepen.io/16327/ScrollTrigger.min.js?v=3.10.3b: Extracting tar content of undefined failed, the file appears to be corrupt: "Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?"

 

Link to comment
Share on other sites

Thanks.

 

I tried out but that didn't solve the original problem. However, turns out I totally missed the CSS inside the layout that was setting an explicit height of 4000px. I moved that to a scoped styling block inside index.vue so the layout itself doesn't have an explicit height.

 

Now everything is working as expected: when navigating to the about page, the scroll position is reset, and by pausing and then unpausing ScrollSmoother, the height gets recalculated and set on the body.

Would be nice to have a `refresh` method on ScrollSmoother.js though, instead of pausing/unpausing. :)

 

 

 

Link to comment
Share on other sites

  • 3 months later...

Hello folks 👋,

 

I am trying to implement ScrollSmoother in a Nuxt3 ("nuxt": "3.0.0-rc.4") application and for now it works quite well. However when setting normalizeScroll: true and changing between pages I have the same problem as @Born05. Here is my setup for Nuxt 3:

<!-- APP.VUE file -->

<script setup>
import { gsap } from 'gsap'
import ScrollSmoother from 'gsap/ScrollSmoother'
import ScrollTrigger from 'gsap/ScrollTrigger'
if (process.client) {
  gsap.registerPlugin(ScrollSmoother, ScrollTrigger)
}

onMounted(() => {
  ScrollSmoother.create({
    wrapper: "#smooth-wrapper",
    content: "#smooth-content",
    smooth: 2,
    effects: true,
    normalizeScroll: true,
    ignoreMobileResize: true,
    smoothTouch: 0.1,
  })
})
</script>

<template>
  <!-- FIX CONTENT -->
  
  <!--
  <li>
    <NuxtLink to="/">Home</NuxtLink>
    <NuxtLink to="/about">About</NuxtLink>
    <NuxtLink to="/gallery">Gallery</NuxtLink>
  </li>
  -->

  <div id="smooth-wrapper">
    <div id="smooth-content">
      <NuxtPage />
    </div>
  </div>
</template>

Do you know how I can call ScrollTrigger.refresh() easily when passing to a new page ? 

 

Many thanks in advance for your suggestions ! 🙏 

Link to comment
Share on other sites

Hey there!

 

You'll need to take a look at Nuxt's lifecycle hooks for that.

https://nuxtjs.org/docs/concepts/nuxt-lifecycle

Different frameworks have difference callbacks or hooks you can pop a refresh() into. You'll need to look for the one that gets called after the DOM is loaded. In your case mounted()
 

Quote

Same as for the client part, everything is happening in the browser but only when navigating via <NuxtLink>. Furthermore, no page content is displayed until all blocking tasks are fulfilled.

 
Check out the component chapter to see more info on <NuxtLink>
  • middleware (blocking)
    • Global middleware
    • Layout middleware
    • Route middleware
  • asyncData (blocking) or full static payload loading
  • beforeCreate & created (Vue lifecycle methods)
  • fetch (non-blocking)
  • beforeMount & mounted

 

Link to comment
Share on other sites

  • 1 month later...
On 4/1/2022 at 8:45 PM, OSUblake said:

Hi @Born05

 

Would you be kind enough to make a minimal demo of your solution in CodeSandbox? I'm trying to nail down some best practices for how to handle ScrollSmoother in frameworks like Nuxt, and then put that in a blog for our users. Seeing how other people are using it would definitely help shape that as they might have some better ideas.

 

This is how created it in Nuxt. Note that I don't have any routing. I was just trying to come up with with a way to create a ScrollSmoother that can be accessed inside any component.

 

https://codesandbox.io/s/gsap-scrollsmoother-nuxt-js-starter-2zgxyo?file=/pages/index.vue

 

 

Hey @OSUblake!

 

Did you ever get around to writing a blog post on best practices of using ScrollSmoother (and maybe gsap animations/scrolltriggers at large) in Nuxt projects? 

Link to comment
Share on other sites

@Cassie I'm sorry to hear that! I hope he's okay.

 

As for what I'm stuck on, it's similar to @BenjaminO's issue above - implementing ScrollSmoother properly in Nuxt 3.  I found this Codesandbox which I think OSUBlake wrote for Nuxt 2, and was wondering if there maybe was an updated version for Nuxt 3 in a blogpost somewhere. 

 

I suppose maybe implementing it the way he's done it, and emitting a global event whenever a new page loads to pop a refresh is the best way to go about it? I tried something similar in Nuxt 2 but experienced scroll freezes for a few seconds after user started scrolling after a page nav (which is why I was so excited about the codesandbox above, but ended up migrating to nuxt 3).

 

Unless you have a better suggestion, I'll just continue migrating my app and see if that works fine I guess :)

 

Link to comment
Share on other sites

Hey @Aerio, I managed to create a workaround for my code (not sure that it will suits your project). Basically I am locking the scroll, then I am doing an animation for the page transition (not needed for all cases) and finally I am releasing the scroll 100ms after the ScrollTrigger.refresh() event. At first I tried something like this :

ScrollTrigger.refresh(true) // with the safe parameter true
ScrollTrigger.addEventListener("refresh", () => smoother.paused(false)); // smoother is SmoothScroller instance

but as described in the Documentation (https://greensock.com/docs/v3/Plugins/ScrollTrigger/static.refresh()), with the safe parameter activated, it will wait for at least one requestAnimationFrame tick, and up to roughly 200ms before initiating the refresh so the user can be block if he tries to scroll during the page transition.

 

I opted for the following approach :

// app.vue
<script setup lang="ts">
import gsap from 'gsap'
import ScrollSmoother from 'gsap/ScrollSmoother'
import ScrollTrigger from 'gsap/ScrollTrigger'
import { scrollStore } from '~~/stores/piniaStore'
import { delay } from '~~/assets/ts/utils'

if (process.client) {
  gsap.registerPlugin(ScrollSmoother, ScrollTrigger)
}

const isLock = scrollStore()

onBeforeMount(() => {
  if (ScrollTrigger.isTouch === 1) {
    ScrollSmoother.create({
      ignoreMobileResize: true
    })
  } else {
    ScrollSmoother.create({
      smooth: 2,
      normalizeScroll: true
    })
  }
})

onMounted(() => {
  const Smooth = ScrollSmoother.get()
    isLock.$subscribe(() => {
    isLock.isReady === true ? Smooth.paused(true) : Smooth.paused(false)
  })
})

// const onLeave, onEnter not detailed
// Check this doc : https://vuejs.org/guide/built-ins/transition.html#javascript-hooks
const onBeforeLeave = (_el: HTMLDivElement) => {
  isLock.$patch({
    isReady: true
  })
  ScrollTrigger.getAll().forEach((t) => t.kill()) // kill all instance of ScrollTrigger except ScrollSmoother. ScrollTrigger.killAll() doesn't seem to work properly
}

const onAfterEnter = (_el: HTMLDivElement) => {
  ScrollTrigger.refresh()
  const promise1 = delay(100)
  promise1.then(() => {
    isLock.$patch({
      isReady: false
    })
  })
}
</script>
<!-- app.vue -->
<template>
  <div id="smooth-wrapper">
    <div id="smooth-content">
        <router-view v-slot="{ Component }"> <!-- documentation : https://vuejs.org/guide/built-ins/transition.html#javascript-hooks -->
          <Transition
            mode="out-in"
            :css="false"
            @before-leave="onBeforeLeave"
            @leave="onLeave"
            @enter="onEnter"
            @after-enter="onAfterEnter"
          >
            <component :is="Component" />
          </Transition>
        </router-view>
    </div>
  </div>
</template>
// utils.ts
const delay = (n: number) => {
  n = n || 2000
  return new Promise<void>((resolve) => {
    setTimeout(() => {
      resolve()
    }, n)
  })
}

export { delay }
// piniaStore.ts
import { defineStore } from 'pinia' // I am using @pinia/nuxt, but you can manage without it https://github.com/vuejs/pinia/tree/v2/packages/nuxt

export const scrollStore = defineStore({
  id: 'smooth-store',
  state: () => {
    return {
      isReady: true
    }
  }
})

 

  • Like 2
Link to comment
Share on other sites

  • 4 weeks later...

Hello @thei ! My code is a bit complex because it  suits my needs but you don’t need that complexity. If your on nuxt 3, basically initialize the ScrollSmoother instance onberforemount or onmounted in your app.vue then just call a scrolltrigger refresh right after the page changed. You can lock the scroll with pause() method when your transitioning from a page to another.

 

I assume the logic will stay the same on nuxt 2. In addition there is a module named nuxt-gsap-module from Ivo Dolenc that you can use for Nuxt 2. Maybe you can handle things a bit differently with this tool.

  • Thanks 1
Link to comment
Share on other sites

  • 2 months later...
On 9/29/2022 at 12:27 PM, BenjaminO said:

Hello @thei ! My code is a bit complex because it  suits my needs but you don’t need that complexity. If your on nuxt 3, basically initialize the ScrollSmoother instance onberforemount or onmounted in your app.vue then just call a scrolltrigger refresh right after the page changed. You can lock the scroll with pause() method when your transitioning from a page to another.

 

I assume the logic will stay the same on nuxt 2. In addition there is a module named nuxt-gsap-module from Ivo Dolenc that you can use for Nuxt 2. Maybe you can handle things a bit differently with this tool.

Every thing work well but i have only one issue .

the issue with NuxtLayout.

 

<div id="smoother-wrapper" ref="smoother__wrapper">
<div id="smoother-content" ref="smoother__content">
<div class="app_container">
<NuxtLayout>
<NuxtPage
:transition="{
name: 'my',
mode: 'out-in',
onBeforeEnter
}"
/>
</NuxtLayout>
</div>
</div>
</div>
,when i put without nuxtlayout it's work .
<div id="smoother-wrapper" ref="smoother__wrapper">
<div id="smoother-content" ref="smoother__content">
<div class="app_container">
<NuxtPage
:transition="{
name: 'my',
mode: 'out-in',
onBeforeEnter
}"
/>
</div>
</div>
</div>
 
so how should we make it in top of NuxtLayout !

 

Link to comment
Share on other sites

Hi,

 

Is a bit difficult to troubleshoot without a minimal demo, please try to provide one. We have this starter templates so you can fork it and accommodate it to fit you project's specifications:

https://stackblitz.com/edit/nuxt-starter-q72fhs?file=app.vue

 

The only thing I can suggest is to use a ref to store the ScrollSmoother instance and refresh it when the route changes. Perhaps use the mount hook to create the ScrollSmoother instance and revert it when it gets unmounted. If you create a reactive property using the router's path those hooks should trigger on every route change. Also I see that you are using transitions. You can also use the JS hooks for the transition component in order to create and revert a GSAP Context instance.

 

Hopefully this helps.

 

Happy Tweening!

Link to comment
Share on other sites

ScrollSmoother typically needs its wrapper element at the root (inside <body>), but it looks like you've got it tucked inside of another element with an id of __nuxt. So when ScrollSmoother's wrapper element is set to position: fixed, it takes it out of the document flow, thus your __nuxt element gets a height of 0. 

 

Try defining your wrapper as that __nuxt element: 

wrapper: "#__nuxt",

 

Link to comment
Share on other sites

8 hours ago, GreenSock said:

ScrollSmoother typically needs its wrapper element at the root (inside <body>), but it looks like you've got it tucked inside of another element with an id of __nuxt. So when ScrollSmoother's wrapper element is set to position: fixed, it takes it out of the document flow, thus your __nuxt element gets a height of 0. 

 

Try defining your wrapper as that __nuxt element: 

wrapper: "#__nuxt",

 

Oh, Thank you so much for the explanation and help. its work.

image.png

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...