DEV Community

Jasper
Jasper

Posted on • Originally published at jasperclarke.com

Integrating Svelte 5 with GSAP 3

Inspired by this post

GSAP is, in my opinion, the go-to library for creating page animations.

If however you have ever tried to use GSAP with Svelte you will know that it can be a cluttered mess of code and a bit fidgety to use.
I mean just take a look at the code I was using for my website.

<script>
    let heroSection;
    let philosophySection;
    let projectsSection;
    let experienceSection;
    let contactSection;

    onMount(() => {
        gsap.registerPlugin(ScrollTrigger);
        // Hero section animations
        gsap.from(heroSection.querySelector('h1'), {
            y: 30,
            opacity: 0,
            duration: 1,
            delay: 0.5
        });

        gsap.from(heroSection.querySelector('.hero-text'), {
            y: 20,
            opacity: 0,
            duration: 1,
            delay: 0.8
        });

    // and so on...
  });
</script>
Enter fullscreen mode Exit fullscreen mode

Obviously there is a better way to do this, after finding the post I linked above it already made things a lot easier!

// animate.js
import { gsap } from 'gsap';

export function animate(node, { type, ...args }) {
  let method = gsap[type];
  return method(node, args);
}
Enter fullscreen mode Exit fullscreen mode
<!-- +page.svelte -->
<a
  use:animate={{
    type: 'from',
    duration: 1,
    scale: 0.9,
    opacity: 0.5,
    ease: 'expo.inOut',
  }}
>Link</a>
Enter fullscreen mode Exit fullscreen mode

There is still a problem with this though. Since it's in JavaScript, there is no type checking and you can't use the autocomplete in your IDE.

After making a few changes it now fully supports TypeScript and meets all the requirements of a Svelte action so the LSP doesn't complain.

// animate.ts
import { gsap } from 'gsap';

type AnimationType = keyof typeof gsap;

interface AnimationOptions extends GSAPTweenVars {
    type: AnimationType;
}

export function animate(
    node: HTMLElement,
    { type, ...args }: AnimationOptions
): { destroy?: () => void } {
    const method = gsap[type] as
        | ((target: gsap.TweenTarget, vars: GSAPTweenVars) => GSAPTween)
        | undefined;

    if (!method) {
        console.warn(`GSAP method "${type}" does not exist.`);
        return {};
    }

    // Create the animation
    const tween = method(node, args);

    return {
        destroy() {
            // Kill the animation when the element is removed
            tween.kill();
        }
    };
}
Enter fullscreen mode Exit fullscreen mode

But there is still one more issue, the scrollTrigger option doesn't work!
And adding gsap.registerPlugin(ScrollTrigger); to your onMount function doesn't work either. A little extra work is needed to get this working.

// animate.ts
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

// Register the ScrollTrigger plugin with GSAP
gsap.registerPlugin(ScrollTrigger);

type AnimationType = keyof typeof gsap;

interface AnimationOptions extends GSAPTweenVars {
    type: AnimationType;
    scrollTrigger?: ScrollTrigger.Vars;
}

export function animate(
    node: HTMLElement,
    { type, scrollTrigger, ...args }: AnimationOptions
): { destroy?: () => void } {
    const method = gsap[type] as
        | ((target: gsap.TweenTarget, vars: GSAPTweenVars) => GSAPTween)
        | undefined;

    if (!method) {
        console.warn(`GSAP method "${type}" does not exist.`);
        return {};
    }

    // Create the animation with ScrollTrigger if provided
    const tween = method(node, {
        ...args,
        scrollTrigger: scrollTrigger
            ? {
                    ...scrollTrigger,
                    trigger: scrollTrigger.trigger || node
                }
            : undefined
    });

    return {
        destroy() {
            // Kill the animation when the element is removed
            tween.kill();

            // If using ScrollTrigger, make sure to kill that instance too
            if (scrollTrigger && tween.scrollTrigger) {
                tween.scrollTrigger.kill();
            }
        }
    };
}
Enter fullscreen mode Exit fullscreen mode

This updated code registers the ScrollTrigger plugin with GSAP and allows you to pass in a scrollTrigger option to the animate function. In doing this it will automatically set the element you have used this action on as the scrollTrigger.trigger element!

Now you can use it like this and have full type checking in your IDE:

<div class="overflow-hidden">
  <h1
    class="font-serif text-7xl font-medium"
    use:animate={{
      type: 'from',
      duration: 1,
      yPercent: 100,
      ease: 'power4.out',
      scrollTrigger: {
        start: 'top 70%',
        end: 'top 20%',
        toggleActions: 'play none none reverse'
      }
    }}
  >
    Other content
  </h1>
</div>
Enter fullscreen mode Exit fullscreen mode

I hope this helps and massive shoutout to Khutso Siema for the original post.

Top comments (2)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas • Edited

You should be able to further type the args based on the key. Or are they the same for all (GSAPTweenVars)?

Generally speaking, gsap provides functions. So typeof (typeof gsap)[key] returns the function's type. So wrap this type in Parameters.

Collapse
 
jasper-clarke profile image
Jasper

Hey Jose,
Thanks for your comment. I totally get what your saying and I think that all of the methods available, "to", "fromTo" and so on all have the same args (GSAPTweenVars), so wrapping the specific method in parameters isn't going to benefit much.

Excellent thinking though!