Jump to content
Search Community

Looking for best-practices when it comes to the performance of multiple reveal animations using ScrollTrigger

EduardB test
Moderator Tag

Recommended Posts

Hey guys,

I am happy to be here and use your amazing tools for my upcoming personal website. That being said, I need your advice.

What would you say would be best practices when dealing with multiple reveal animations on scrolling? I've found that mobile performance (on Chrome, since Firefox seems to perform beautifully) is pretty bad when it comes to the way my reveal animations trigger. They feel stuttery and somewhat low-fps. I've tried slowly scrolling to avoid overriding them by dragging the screen (to see if multiple animations firing at once could be the cause), but the choppy animation effect is pretty much the same even if only one animation triggers.

For reference, I've set up my reveal animations using attributes. I place certain attributes on certain elements, and a switch statement detects the type of animation that needs to be fired for each element.

Here's more or less how this code is setup. Maybe you have some pointers on how would be best to setup something like this?
 

function loadGSAPAnimations() {
  let selector = "[data-reveal]"; // Base selector for elements to animate

  // Query the DOM once for each modified selector
  let itemsToAnimate = document.querySelectorAll(selector);

  itemsToAnimate.forEach((itemFound) => {
    const elementTimeline = gsap.timeline({
        delay: itemFound.dataset.delay || 0, // If "data-delay" attribute is present, adjust value
        defaults: { duration: 1, ease: "power4.out" },
      }),
      animationType = itemFound.dataset.reveal;

    switch (animationType) {
      case "multi-element-reveal":
        const childElements = itemFound.querySelectorAll(childSelector);

        gsap.set(childElements, {
          opacity: 0,
          y: 115,
          willChange: "transform, translateY",
        });

        elementTimeline.to(
          childElements,
          {
            opacity: 1,
            y: 0,
            stagger: 0.2,
            ease: "power4.out",
            onComplete: () => {
              gsap.set(childElements, { willChange: "auto" });
            },
          },
          "+=0.3"
        );
        break;

      case "element-reveal":
        gsap.set(itemFound, {
          opacity: 0,
          y: 115,
          willChange: "transform, translateY",
        });

        elementTimeline.to(
          itemFound,
          {
            opacity: 1,
            y: 0,
            duration: 1,
            ease: "power4.out",
            stagger: 0.15,
            onComplete: () => {
              gsap.set(itemFound, { willChange: "auto" });
            },
          },
          "+=0.3"
        );
        break;

      case "title-reveal":
        let elementText = new SplitType(itemFound, {
          types: "lines",
          lineClass: "title-line",
          tagName: "span",
        });

        gsap.set(elementText.lines, {
          opacity: 0,
          y: 115,
          willChange: "transform, translateY",
        });

        elementTimeline.to(
          elementText.lines,
          {
            opacity: 1,
            y: 0,
            duration: 1,
            stagger: 0.15,
            ease: "power4.out",
            onComplete: () => {
              gsap.set(elementText.lines, { willChange: "auto" });
              elementText.revert();
            },
          },
          "+=0.3"
        );
        break;

      case "image-reveal":
        const image = itemFound.querySelector("img");
        const innerWrapper = image.closest(".image-animation-wrapper");

        gsap.set(innerWrapper, {
          scale: 1.15,
          "--reveal-scale": 1,
          force3D: true,
          willChange: "scale, transform, translateY",
        });

        elementTimeline.to(
          innerWrapper,
          {
            "--reveal-scale": 0,
            scale: 1,
            duration: 1.5,
            ease: "power4.out",
            onComplete: () => {
              gsap.set(innerWrapper, { willChange: "auto" });
            },
          },
          "+=0.3"
        );
        break;
    }

    //Controls the distance from the screen when the item gets revealed on scroll
    const revealOffset = itemFound.dataset.offset || 84;

    // Create the ScrollTrigger instance
    ScrollTrigger.create({
      trigger: itemFound,
      animation: elementTimeline,
      start: `top ${revealOffset}%`,
      end: "bottom 35%",
      refreshPriority: -1,
    });
  });
}

 

Link to comment
Share on other sites

Hi @EduardB welcome to the forum! 

 

Without a minimal demo it is hard to help you debug. Javascript is 1/3 of the code that is used for GSAP to do its thing. For instance if you have the following in your CSS transition: all 300ms ease-in; you're going to get choppy animations, because then you have Javascript and CSS fighting for who gets to animate what 

 

I'm not well versed in willChange: but I would set in in CSS instead of JS, because it is a tool to tell the browser "heads up" this is going to change, so setting it in JS when it is changing feels a bit late. Also you can set properties that will-change and scale, translateY are no CSS properties (well they are kinda, but it is not what GSAP uses) it are values of transform. 

 

If you could make a minimal demo we would be happy to take a look at your setup and help you debug. Hope it helps and happy tweening! 

 

See the Pen aYYOdN by GreenSock (@GreenSock) on CodePen

  • Like 2
Link to comment
Share on other sites

i,

 

This is redundant:

gsap.set(childElements, {
  opacity: 0,
  y: 115,
  willChange: "transform, translateY",
});

The property translateY is actually a value of the transform property:

https://developer.mozilla.org/en-US/docs/Web/CSS/transform#syntax

 

This would be as easy as setting it in the CSS:

.my-class[data-reveal] {
  will-change: transform;
}

Unfortunately performance is a deep topic and 99.99% of the times this comes down to rendering more than code processing/JS/GSAP issues. Browser rendering is most likely the bottle neck here. Maybe use GSAP MatchMedia to create simpler animations and use smaller images for smaller screens as well. Other than that I can't think of a way to solve this.

 

Hopefully this  helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

On 2/20/2024 at 1:32 PM, mvaneijgen said:

Thanks a lot guys!

 

The CSS will-change approach already solved some performance issues. Other than that, I wish I was proficient enough with Dev Tools to figure out where the browser bottlenecks, but alas, I'm simply a designer with just a little bit of coding knowledge. This will have to do.

 

 

 

 

Link to comment
Share on other sites

Great to hear you've solved your issue. 

2 minutes ago, EduardB said:

I'm simply a designer with just a little bit of coding knowledge. This will have to do.

Give it time, this was me 10 years ago. Wouldn't have it any other way now, combining the design with the development skills is amazing, you'll find that one day you can build what ever you design! Good luck with the project and happy tweening!

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