Jump to content
Search Community

Gsap wrapper for disabling animations

JeffreyArts test
Moderator Tag

Recommended Posts

Hi,

I have an application where I make heavy use of GSAP to animate all kinds of operations. Now I'd like to respect the "prefers-reduced-motion" configuration of the user (and also allow users to disable the animations via a configuration setting), but I don't feel much for manually wrapping every gsap.to statement with a manual check for an if statement. So I thought I could write a gsap wrapper that would take care of this.

import gsap from "gsap"
let allowAnimation = false

const gsapWrapper = gsap
gsapWrapper.to = function(targets: gsap.TweenTarget, vars: gsap.TweenVars) {
  if (!allowAnimation) {
      vars.duration = 0
  }

  return gsap.to(targets, vars)
}

export default gsapWrapper

 

But this will throw a TS error
 

Type '(targets: TweenTarget, vars: TweenVars) => Tween' is not assignable to type '{ (targets: TweenTarget, vars: TweenVars): Tween; (targets: TweenTarget, duration: number, vars: TweenVars): Tween; }'.
  Types of parameters 'vars' and 'duration' are incompatible.
    Type 'number' is not assignable to type 'TweenVars'


So before diving into the rabbit hole of trial and error,  resolving these errors whenever I run into them. I'd like to know if anyone has experience with achieving the same goal but via a simpler path. Is there something like this already in the gsap core for instance? Would love to hear some other points of view on this subject.

Link to comment
Share on other sites

Have you seen gsap.matchMedia()https://gsap.com/docs/v3/GSAP/gsap.matchMedia()/ With it you can do something like and then do or don't animate something if that is set. 

{
  isDesktop: "(min-width: 800px)",
  isMobile: "(max-width: 799px)",
  reduceMotion: "(prefers-reduced-motion: reduce)"
}

Hope that solves your issue. If not please provide a minimal demo, so that we can dive directly in to the code.  Happy tweening! 

Link to comment
Share on other sites

Hi @mvaneijgen,

Thank you for your addition, I have not seen this feature before but it is not applicable to the goal I desire. This method would require to wrap all my gsap.to functions across dozens of files with this matchMedia function. While this might achieve the desired outcome (no animations on certain conditions), it would make the codebase cluttered and prone to errors in maintaining the application.

 

The goal I'd like to achieve is to have a single location where I can write some conditions to execute gsap triggered animations or not. This goal originates from the problem that I have dozens of gsap.to methods set-upped around many files.

 

Among that, simply wrapping my gsap.to methods as suggested would not work as desired since it will most likely mess up various application states because it will most surely intertwine with gsap.set methods.

Link to comment
Share on other sites

Hi,

 

I think Mitchel's suggestion of using GSAP MatchMedia is the right one for the reduced motion preference. Keep in mind that GSAP MatchMedia is a wrapper for a GSAP Context instance with responsive superpowers, so it can be used in the same way a GSAP Context instance is used:

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

 

Now based on this:

3 hours ago, JeffreyArts said:

The goal I'd like to achieve is to have a single location where I can write some conditions to execute gsap triggered animations or not. This goal originates from the problem that I have dozens of gsap.to methods set-upped around many files.

 

Among that, simply wrapping my gsap.to methods as suggested would not work as desired since it will most likely mess up various application states because it will most surely intertwine with gsap.set methods.

This sounds more like state management than anything else, beyond GSAP Context and MatchMedia there is nothing else on the library that can do what you're looking for, since there are already specific state management tools out there that can do that. This is more an arquitectural/app-design thing rather than an animation one:

 

Finally regarding this:

3 hours ago, JeffreyArts said:

While this might achieve the desired outcome (no animations on certain conditions), it would make the codebase cluttered and prone to errors in maintaining the application.

This can become a problem regardless of the tech stack you use and if GSAP is present or not. I've seen this problem in some SaaS projects because the app design and structure is not properly done at the start which leads to things becoming messy. I've seen my fair share of apps when the concept of spitting almost everything into it's own component, while a good idea, goes a bit out of hand and creates more problems than it solves.

 

As I said before this falls more into the state management side of things rather than GSAP/animations.

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

At first glance the rabbit hole did not seem that deep, I might be wrong when I apply it across my entire application (I already notice that I should replace the setTimeouts with a delay for instance). But in short, this is how I approached it.

I have made a file that operates as a wrapper for gsap, it imports an external file that manages the state of the animations  (a Pinia datastore in my case). Which has a boolean property called "showAnimations"  to determine wether animations will be played or not. 

/services/gsap-wrapper.ts

import gsap from "gsap"
import appStore from "@/stores/app"
const gsapWrapper = {...gsap}

gsapWrapper.to = (
    target: gsap.TweenTarget,
    durationOrVars: number | gsap.TweenVars,
    vars?: gsap.TweenVars
) => {
    let options = {} as gsap.TweenVars

    if (typeof durationOrVars === "number") {
        options.duration = durationOrVars
    } else {
        vars = durationOrVars
    }

    options = {
        ...options,
        ...vars,
    } as gsap.TweenVars

    // Pinia datastore
    const app = appStore()

    // Overwrite duration & delay when animations are not allowed
    if (!app.showAnimations) {
        options.duration = 0
        options.delay = 0
        options.ease = "none"
        if (typeof options.stagger == "number") {
            options.stagger = 0
        } else if (typeof options.stagger === "object" && options.stagger !== null) {
            options.stagger.each = 0
        } 
    }
    return gsap.to(target, options)
}

gsapWrapper.fromTo = (
    target: gsap.TweenTarget,
    durationOrFromVars: number | gsap.TweenVars,
    fromOrToVars: gsap.TweenVars,
    toVars?: gsap.TweenVars
) => {
    let options = {} as gsap.TweenVars

    let fromVars = fromOrToVars
    if (typeof durationOrFromVars === "number") {
        options.duration = durationOrFromVars
        fromVars = fromOrToVars
    } else {
        toVars = fromOrToVars
        fromVars = durationOrFromVars
    }

    options = {
        ...options,
        ...toVars,
    } as gsap.TweenVars

    // Pinia datastore
    const app = appStore()
    
    // Overwrite duration & delay when animations are not allowed
    if (!app.showAnimations) {
        options.duration = 0
        options.delay = 0
        options.ease = "none"
        if (typeof options.stagger == "number") {
            options.stagger = 0
        } else if (typeof options.stagger === "object" && options.stagger !== null) {
            options.stagger.each = 0
        } 
    }

    return gsap.fromTo(target, {
        ...fromVars,
    }, options)
}

export default gsapWrapper



In the other files where I normally include gsap via import gsap from "gsap", I now simply import it as followed:
 

import gsap from "/services/gsap-wrapper"


This approach allows me to enable/disable all gsap animations from 1 single location which seem to have the following up- and downsides.

Upsides

  • I have on boolean variable that I can modify to disable ALL animations throughout my application
  • The code is relatively simple, and can be easily adjusted and expanded
  • It uses the default gsap library, so unless breaking changes occur in the gsap.to method. Everything will remain working with future gsap updates

Downsides

  • It is applicable to ALL gsap.to methods. As a workaround you could import both the gsap-wrapper and gsap into a file, but that could easily become a slippery slope
  • It can be quite a hassle to figure out how the wrapper function should be written to respect the original gsap method and its types


 

Link to comment
Share on other sites

I wouldn't normally recommend this and it's not the perfect solution, but you could consider: 

// make everything go super-fast
gsap.globalTimeline.timeScale(9999);

Just keep in mind that'll affect EVERYTHING, including delayedCall()s. 

 

Also keep in mind that the wonderful thing about gsap.matchMedia() is that it'll handle all the cleanup and re-running for you automatically, like if the user toggles their prefers-reduced-motion preference or resize their screen, etc. If I understand it correctly, your approach would only work on first load (no adjusting dynamically at runtime) which may be totally fine for your use case. In other words, if a user loaded your site and then enabled prefers-reduced-motion, they'd continue to see animations. 

 

Good luck!

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