Jump to content
Search Community

Sveltekit Page Transition with GSAP (incl. ScrollSmoother)

Moritz L test
Moderator Tag

Go to solution Solved by Moritz L,

Recommended Posts

Hi there!

 

This is a StackBlitz of a minimal demo showing two routes that I want to transition between: https://stackblitz.com/edit/sveltejs-kit-template-default-2zmrub?file=src%2Froutes%2F%2Blayout.svelte

 

The transition should be a simple wipe from bottom to top – meaning the old page is wiping away and revealing the new page underneath. Sounds simple, but I'm struggling with implementation.


Without the transition, everything works just fine. But when I work on the transition, I have the following problems: 

 

1. During transition, Svelte puts the HTML of the new page next to the HTML of the old page. Since I am transitioning the <main> element, we have two <main> elements in the DOM for a short period of time. I want to keep the "old page" on top of everything. So I am using gsap.set to absolutely position the "old page" on top of everything and then animating the height property to reveal the "new page" underneath. Afterwards, Sveltekit unmounts the "old page". The positioning already works as you can see in the demo (after clicking the nav link, the "old page" stays there for 2 seconds before the new page becomes visible). But the wipe doesn't. If I am animating autoAlpha instead, it does work.

2. When I scroll down the page a bit and then clicking the link, the content is jumping up to the top of the page and then animating to the new page. Is this a Sveltekit behavior? Or GSAP? I tried window.scrollTo to the value of window.scrollY just before animating but that did not work (it did work without ScrollSmoother, though). 

3. The SmoothScroll doesn't work after route change. Weirdly, this works on my local project which is setup the same way.

4. There is a gap at the end of the page and I don't know where it comes from.

 

I should add that I got the transition working without SmoothScroller. And I got SmoothScroller working without the transition. Just together, everything falls apart. So my best guess is, it has something to do with absolutely positioning the elements, since SmoothScroller uses a fixed element as a wrapper. 

 

Pushing me in the right direction would already help me tremendously. I can then figure out the details.

 

Thanks so much!

Link to comment
Share on other sites

  • Solution

I solved my issue when I completely refactored how I implemented scroll trigger. I moved all the logic to +layout.svelte like so: 

 

<script>
	import '../app.css'
	import { gsap } from 'gsap'
	import { ScrollSmoother } from 'gsap/dist/ScrollSmoother'
	import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'
	import BaseNav from '$lib/components/base-nav.svelte'
	import { onMount } from 'svelte'

	export let data

	/**
	 * @type {ScrollSmoother}
	 */
	let smoother

	if (typeof window !== 'undefined') {
		gsap.registerPlugin(ScrollTrigger, ScrollSmoother)
	}

	onMount(() => {
		smoother = ScrollSmoother.create({
			smooth: 1,
			effects: false
		})
	})

	/**
	 * @param {HTMLElement} node
	 * @param {Object} options
	 * @param {number} [options.duration]
	 * @param {number} [options.delay]
	 */

	// out transition
	function wipe(node, { duration = 250, delay = 0 }) {
      // pulling "from" page out of document flow and positioning it absolutely over "to" page
		gsap.set(node, {
			position: 'fixed',
			inset: 0,
			width: '100%',
			height: window.innerHeight,
			zIndex: 9999,
			overflow: 'hidden',
			backgroundColor: '#fff',
			opacity: 1,
			visibility: 'visible'
		})

		// prevent scrollY from jumping to top of page
		gsap.set(node.firstChild, {
			y: -window.scrollY
		})

		// make sure "to" page is scrolled to top
		smoother.scrollTop(0)

		const tl = gsap.timeline({ delay, defaults: { duration: duration / 1000 } })

		tl.to(node, {
			height: 0,
			ease: 'power4.inOut'
		})

		return {
			duration,
			delay,
			/**
			 * The function to call on each animation frame.
			 * @param {number} t - The current tick value.
			 */
			tick: (t) => {
				tl.progress(1 - t)
			}
		}
	}
</script>

<BaseNav />
<div id="smooth-wrapper">
	<div id="smooth-content">
		{#key data.url.pathname}
			<main out:wipe={{ duration: 2000 }} on:outroend={() => ScrollTrigger.refresh()}>
				<slot />
			</main>
		{/key}
	</div>
</div>

I hope that helps anyone who tries to work with Svelte transitions and ScrollSmoother.

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