Jump to content
Search Community

GSAP + SvelteKit: Elements with data-speed don't work on route change

grizhlie test
Moderator Tag

Go to solution Solved by grizhlie,

Recommended Posts

Hi all,

 

Wondering if someone has come across this and found a solution. Or if I am missing something obvious 🥲

 

Any element with data-speed="auto", lets say an image I want to parralax, does not work after I change routes (lets say navigating from Home to Portfolio).

 

But I can see scrollTrigger working since scrollSmoother is working and the page is gliding down. I have used ScrollTrigger.refresh() on route changes, added delays to this, etc, nothing seems to be working.

 

Is there some mechanism that refreshes elements that the use data-speed attribute? I have not found one, although a Nuxt related post example suggested something like this exists but it did not work for me in SvelteKit.

 

Basic Demo (I made it as simple as possible). Just move between Home/About and click the Refresh ScrollTrigger button when you want. I realise I can do it with onMount but as I said, simple as possible. Hoping that I am making some silly mistake.

 

I realise that SvelteKit will gain examples and knowledge going frowards since it's finally in 1.0 so I'm not expecting miracles, just hoping for one :)

 

Edit: I have even added a fixed button that has ScrollTrigger.refresh() function during my tests (after I am 10000% certain everything is loaded). Still nada.

 

Cheers!

Link to comment
Share on other sites

I'm totally unfamiliar with SveltKit, but I believe the problem is that when you create you ScrollSmoother, it finds anything in the DOM at that point which has a data-speed or data-lag attribute. But then you reroute elsewhere and dynamically load new elements in that have those attributes, but ScrollSmoother has no idea. You need to kill() the old effects (the ones controlling the elements that are now flushed out of the DOM) and create new ones for the fresh elements getting loaded in. 

 

// when destroying/unmounting...
smoother.effects().forEach(t => t.kill());

// when loading in new stuff:
smoother.effects("[data-speed], [data-lag]");

I'm not familiar enough with Svelte to know the lifecycle methods or how to trigger those things at the right time, but hopefully that gets you going in the right direction. 

  • Like 1
Link to comment
Share on other sites

That’s incredibly useful information! Understanding how GSAP works behind the scenes will probably save me here. I’ll explore first thing tomorrow, thanks a ton Jack! 
 

I’ll share any findings/solutions here if I have any :)

  • Thanks 1
Link to comment
Share on other sites

Happy to say that your advice worked splendidly. That tiny bit of info about how Gsap needs to be given the new elements was everything.

 

I have updated the Basic Demo and tried making it obvious as to how it's all built. So everything is a function or a component, tried doing it step by step.

 

For my future self or anyone else interested in this:

 

In a nutshell, when changing routes:

  1. Kill all existing effects
  2. Create new effects by telling Gsap about new elements
  3. Refresh ScrollTrigger.

 

More information: 

  • In the above solution our ScrollSmoother is available as a store, we can access it anywhere in the application. I find this to be the easiest approach to just call functions from anywhere in the application. But you could use a mixture of setContext, svelte store and dispatching updates when a new route is mounted (onMount).
  • I created functions specific to that stored ScrollSmoother so we can update anything we need, with clarity, for example:
    smoother.updateEffectsLifecycle() ; this function can be called from any page/route. 
    • The above function is further broken down in the code into the 3 steps mentioned above.
  • Instead of using a store like I did you can also use a mixture of setContext + svelte store, within +layout.svelte. This could be considered as more correct and clean way of doing it but for my use case the effect is identical.

 

Thanks again Jack, super helpful info.

  • Like 2
Link to comment
Share on other sites

  • Solution

I'm learning a lot, ok i'll fork the original and try the above. ^ Sounds WAY simpler haha. 

 

I think I spotted my real issue too. Last time I really read documentation for GSAP I was still learning everything else. If I read some of these things now, they would actually make sense so I think I'll slow down and just read more next time 🙃

So Solution 2 is way simpler. No store needed, awesome to just find ScrollSmoother anywhere in the app with ScrollSmoother.get().

Edit: I created a video explanation of the solution. Partially to check my understanding but also to explain this to anyone else who might be new and confused like I was.

Basically it was as simple as creating this component and injecting it in any page. Could add more logic to it but didn't feel a need for it.

Basic order of things: 

  1. Component with the logic to register ScrollTrigger & ScrollSmoother, whilst also setting up ScrollSmoother. 
    Of course you can just use this code in +layout.svelte, it does not matter for functionality. I just throw stuff into components.
    <GsapSetup.svelte />
  2. Inject this component into +layout.svelte
  3. Component with the following logic that runs when we are on a new route and the DOM (html elements) are loaded/mounted on a new page (like switching from homepage to the about page) 
    <GsapPageRefresh />
    1. Kill old-route effects (ScrollSmoother stores elements it is creating effects for in an array (list)
    2. Inform ScrollSmoother of the new elements (so we killed home-elements and are now adding about-elements to our array (list)
    3. We just need to refresh ScrollTrigger now and it will recalculate based on all of this information.
       
  4. Inject this component into any route/page.
     
// GsapSetup.svelte

<script>
  import gsap from "gsap-trial";
  import { ScrollTrigger } from "gsap-trial/dist/ScrollTrigger";
  import { ScrollSmoother } from "gsap-trial/dist/ScrollSmoother";
  import {onMount} from 'svelte'
  let smoother;

  onMount(() => {
    if(typeof window !== "undefined"){
      gsap.registerPlugin(ScrollTrigger, ScrollSmoother);
    }

    const ctx = gsap.context(() => {
      smoother = ScrollSmoother.create({
        smooth: 2,
        normalizeScroll: false,
        ignoreMobileResize: false,
        effects: true
      });
    })
    return () => ctx.revert();

  })
</script>



Injected the above into +layout.svelte

// +layout.svelte

<script>
  import GsapSetup from '$lib/GsapSetup.svelte'
  // Other stuff...
</script>

<GsapSetup />

// Other stuff...


Created a component with our "Inform Gsap of our new-route elements whilst destroying old ones" logic.
 

// GsapPageRefresh.svelte

<script>
  import {onMount} from 'svelte';
  import { ScrollSmoother } from "gsap-trial/dist/ScrollSmoother";
  import { ScrollTrigger } from "gsap-trial/dist/ScrollTrigger";

  onMount(() => {
    if(ScrollSmoother.get()){
      let smoother = ScrollSmoother.get();
      smoother.effects().forEach((effect) => effect.kill());
      smoother.effects('[data-lag], [data-speed]');
      ScrollTrigger.refresh()
    }
  })
</script>

 

Then I just used the GsapPageRefresh component in every route, it looks like this:
 

// home route (routes/+page.svelte) and about route (route/about/+page.svelte) has more or less identical code.

<script>
  import PageGsapRefresh from '$lib/PageGsapRefresh.svelte'
  
  const images = [
    'https://source.unsplash.com/random/1000x1000/?volcano',
    'https://source.unsplash.com/random/1001x1000/?volcano',
    'https://source.unsplash.com/random/1002x1000/?volcano',
    'https://source.unsplash.com/random/1003x1000/?volcano',
    'https://source.unsplash.com/random/1004x1000/?volcano',
    'https://source.unsplash.com/random/1005x1000/?volcano',
    'https://source.unsplash.com/random/1006x1000/?volcano',
    'https://source.unsplash.com/random/1007x1000/?volcano',
  ]
</script>

<PageGsapRefresh />

<!-- svelte:head stuff... -->

<div class="container">
  {#each images as image, i}
    <div class="img-container">
      <img src="{image}" alt="nada!" class="img" data-speed="auto" data-lag="0" />
    </div>
  {/each}
</div>

<!-- Styling... -->


(A cleaner way would be to create onDestroy and onMount logic but this was sufficient for my understanding.)

Thanks for the new lesson :) 




 

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