Jump to content
Search Community

SvelteKit 2 + Svelte 5 + GSAP - Stuck on ScrollTrigger not working as expected after route change

Rafal Potasz test
Moderator Tag

Recommended Posts

Hi all. 

 

No Codepen or Stackblitz yet. In summary, I tried replicating my project on StackBlitz but I'm using Svelte 5 and whatever I did failed in that environment so I gave up for now.

 

My github code.

 

Video of the problem here

 

I am trying to create a "Base" environment for a SvelteKit 2 + Svelte 5 + GSAP project.

 

I am trying to use: 

  • Svelte transitions for pages
  • ScrollSmoother (No effects currently, just {smooth: 2}, but this is likely to change based on a new project)
  • ScrollTrigger (Currently just tried a ScrollTrigger with 3 heading elements, but this would of course grow in complexity with any project)

On the initial page load, everything works.

 

The issue arises on route change. The ScrollTrigger elements don't animate anymore. They just sit there in their initial states ('x' positions). If I refresh ScrollTrigger, they just update their 'x' positions, but still don't animate. 

 

In the past this was a very simple thing to solve (I think?). I would just refresh effects on smoother. Or just ScrollTrigger.refresh().I also tried setting a new smoother (perhaps this is the problem, I don't know). I tried killing the smoother and setting a new one too. 

But this does not seem to work for me currently and I'm losing my mind. 

 

I even created buttons to refresh these properties on demand and my ScrollTrigger elements simply don't react to the scroll/scrub. 

 

I'm almost 99% sure it's something extremely simple I don't understand, as per usual, but I can't find it. Been trying for the last 2-3 days.

fanpop-dr-cox-having-a-bad-day-scrubs-15

 

Help...

Link to comment
Share on other sites

Update: I think I have figured out everything that works and doesn't work. Am in the process of updating everything before posting a good 'starter' and tips on the above project configuration between Svelte/Kit/GSAP. 

Link to comment
Share on other sites

Hi,

 

This could be about proper cleanup on route changes. For that I would recommend using GSAP Context:

https://gsap.com/docs/v3/GSAP/gsap.context()

 

This demo, doesn't have any routes, but uses GSAP Context in the way we suggest for SvelteKit projects (or any other framework that handles routing in the way SvelteKit does it like Vue and React and SSR libraries/frameworks that use them under the hood):

https://stackblitz.com/edit/sveltejs-kit-template-default-unljf6?file=src%2Froutes%2F%2Bpage.svelte

 

https://stackblitz.com/edit/sveltejs-kit-template-default-dvnud9?file=src%2Froutes%2F%2Bpage.svelte

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

I have faced the same issue in sveltekit, let me share the reason.
I was using native svelte transitions to animate between pages. So it translates the page due to which the scroll positions are effected.

 

If you are using them and you remove them and use view transitions. Scroll trigger will work
image.png.6a750e35e786d4152daab75f78b5fd2f.png

  • Like 2
Link to comment
Share on other sites

@Rodrigo Thanks for the input. I have been testing this for 5 days now and I think I have found the solution. It's not quite perfect yet, but I'm getting there slowly. 

 

A large chunk of a problem was that instead of using +layout.svelte for my smooth-wrapper and smooth-content, I used the +page.svelte component, which was a mistake it seems. My idea was to have a 'fresh start' on route change, but it seems that things broke with that. Wish I just moved the code 3 days earlier, but hey, lesson learned. 

 

Currently I am just killing old smoother effects, getting the new ones and updating ScrollTrigger on a SetTimout until the page's hero is animated in, then it stops. Oddly enough I couldn't create a ScrollTrigger.refresh() that worked as expected without adding timeout, recursion and a way to make it stop. 

 

The scenario is like so: I change routes. My content would be mounted. I would confirm with a few console logs that effects have been applied. ScrollTrigger.getAll() would console log everything. But ScrollTrigger.refresh() wouldn't work until around... 200-300ms down the line. I think without needing complex logic a fix I used in the past was just to run ScrollTrigger.refresh()a few times in the first second of a new route, but I of course had "better ideas" 🤞 this time around...

I will be trying implementing gsap.context(), although I don't fully understand what it does under the hood, so will read up on it a bit.

 

@AdityaBanik That was an early problem I had too!!! Drove me nuts. I have managed to figure out how to get everything I wanted now though:
 

 

  • My SvelteKit/Svelte page transitions now work
  • My ScrollSmoother is updated with new .effects() on route change
  • My ScrollTrigger is now working as expected.

Is ViewTransitions the new method of transitioning between pages? I'd love to use it but the fact it doesn't work on Firefox and Safari bugs me. 

 

Although...

 

image.thumb.png.06f366416e14b2bf389d1e163ac4d469.png

 

 

I'm just testing this and making a new project to MAKE SURE my solution does indeed work across projects. It's a bit chunky for my liking so I keep on refactoring so it's more digestible.

 

I'll post a long post about setting it up once I'm done because these technologies are awesome once you get them working together as expected :)

 

Rafal

P.S. If anyone is curious about what I'm babbling about I have recorded my thought process and code in this loom recording. This is only important before I write up an article on setting things up, once I test more.

Link to comment
Share on other sites

Hi,

 

We've seen some issues with the view transitions implementation in NextJS as well, so there could be some broader problems with that specification.

 

My recommendation would be to wait until the view transition is completed before creating the new ScrollTrigger instances and be sure to remove all the ScrollTrigger and GSAP instances when you're changing routes (cleanup), which can be done super easy with GSAP Context.

 

Finally you might want to have a look at this video by @SteveS on last year's Svelte Summit:

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

@Rafal Potasz Not sure what solution you came up with, but I tried my hand and made something work:
https://github.com/StevenStavrakis/svelte5-gsap-scrollsmoother

(You'll have to install GSAP yourself because I get a 403 when trying to use the private npm registry for some reason)

The meat of the code is here:

<script lang="ts">
  import "../app.css";
  import { navigating } from "$app/stores";
  import { ScrollSmoother } from "gsap/ScrollSmoother";
  import {ScrollTrigger} from "gsap/ScrollTrigger";
  import gsap from "gsap";

  let { children } = $props();

  gsap.registerPlugin(ScrollSmoother, ScrollTrigger);

  $effect(() => {
    // on first load, we need to create the smoother
      ScrollSmoother.create({
        wrapper: "#smooth-wrapper",
        content: "#smooth-content",
      });
  })

  $effect(() => {
    const smoother = ScrollSmoother.get();
    if ($navigating === null) {
      // When navigating is null, that means we are no longer navigating; the page has been loaded
      ScrollSmoother.refresh();
    } else {
      // when we start navigating, we scroll to the top otherwise we get some weird behavior
      if (!!smoother) {
      smoother.scrollTo(0)
      }
    }
  });
</script>
<div class="flex" id="smooth-wrapper">
  <div class="h-screen w-[200px] bg-slate-600 text-white p-8">
    <h2 class="text-xl font-bold">Menu</h2>
    <a class="block" href="/">Home</a>
    <a class="block" href="/second-page">Second page</a>
  </div>
  <div class="w-full" >
    <div id="smooth-content">
      {@render children()}
    </div>
  </div>
</div>


It makes use of effects and the `navigating` store. There is some strange behavior where the scroll position isn't reset when navigating pages, so I just force a scroll to the top of page. I'm sure there is a more elegant way to deal with it, but that is probably already covered in another post.
 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

16 hours ago, SteveS said:

Additionally, if you have to wait 200-300 ms to do anything inside an effect or otherwise state related, it is worth looking at the tick() function.

This was exactly my thinking! But tick() didn't do anything. I think tick relates to rendering. And the GSAP effects were applied to elements already rendered, so tick() didn't achieve anything. Honestly, the simplest solution is to fire ScrollTrigger.refresh()a few times in the first second (works perfectly 100% of the time in another project). 

The way I'm handling it at the moment: I created some functions for animations. When I apply an animation to a page's hero section, I also start refreshing gsap every 200ms. When onEnter is triggered by the hero section, the refreshes stop. Simple as that. Not very elegant, but it's quite accurate so far.

 

Going back to tick(), somehow I have missed this in the past. During this project I found it but it didn't work. Looking at the docs: "It returns a promise that resolves as soon as any pending state changes have been applied to the DOM (or immediately, if there are no pending state changes)." - Gsap doesn't count as a State change to content I guess?? So tick() wouldn't fire? Rendering by state would be relating to something like #key or #eachetc? 

So many questions, so little time this week :D 

Link to comment
Share on other sites

21 minutes ago, Rafal Potasz said:

Honestly, the simplest solution is to fire ScrollTrigger.refresh()a few times in the first second (works perfectly 100% of the time in another project). 

Hi,

 

I would try to avoid calling the refresh method recursively like that. There is a lot that goes on when that happens and it could lead to some bad UX if the user starts scrolling before the refresh method is called. If possible disable scrolling, wait a full second, allow scrolling again and call the refresh method then.

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

10 minutes ago, Rodrigo said:

If possible disable scrolling, wait a full second, allow scrolling again and call the refresh method then.

Not a bad idea 😮! Might do that whilst some loader-screen is infront of the actual content. That would simplify things massively. 

I haven't experienced negative side effects as of yet, by doing it that way, but most of my projects have been relatively simple. I'm sure with any added complexity it would sour the UX in some way.

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