Jump to content
Search Community

Animations triggered all together when scrolling fast using ScollTrigger

PizzaNCoding88 test
Moderator Tag

Recommended Posts

Hello,

 

Newbie with React and Gsap here.

 

I have just made my first portfolio website and encountered an issue.

 

I have a projects section and a contact form section to which I have applied scroll trigger animations where the projects' cards and the form appear on scroll (simple opacity animation, nothing too fancy).

 

Basically all the animations work fine when scrolling down slowly/in a normal way, but if from the top I scroll down fast (even just a few lines), all the animations are triggered all together. 

 

The projects' card are rendered from a parent component, let me know if a snippet of this one is required too.

 

Code snippets below.

 

Projects' cards

import { LuGithub, LuArrowRight } from "react-icons/lu";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/all";
import { useEffect, useRef } from "react";
import data from "../data/projectData";

const ProjectCard = (props) => {
  const { image, title, description, link, repo, id, alt } = props;
  const img = useRef();
  const buttonLink = useRef();
  const buttonGit = useRef();
  gsap.registerPlugin(ScrollTrigger);
  useEffect(() => {
    let tl = gsap.timeline({
      scrollTrigger: {
        trigger: img.current,
        start: "top 95%",
      },
    });
    tl.to(img.current, { autoAlpha: 1, duration: 3 });
  });
  useEffect(() => {
    let tl = gsap.timeline({
      scrollTrigger: {
        trigger: buttonLink.current,
        start: "top 95%",
      },
    });
    tl.to(buttonLink.current, {
      autoAlpha: 1,
      duration: 0.5,
      ease: "power3.out",
      delay: -0.2,
    });
    tl.to(buttonGit.current, {
      autoAlpha: 1,
      duration: 0.5,
      ease: "power3.out",
    });
  });

  return (
    <div
      className={`${
        id === data.length
          ? "md:flex md:flex-col md:justify-center md:items-center md:mx-auto"
          : ""
      } mb-14`}
    >
      <div
        className=" mt-10 mb-4 w-[95%] mx-auto relative group shadow-projects-shadow hover:shadow-none transition-shadow duration-300 opacity-0 rounded-md overflow-hidden "
        ref={img}
      >
        <img
          alt={alt}
          src={image}
          className="group-hover:blur-[8px] group-hover:opacity-40 group-hover:transition-all group-hover:duration-300 group-hover:ease-out group-hover:scale-[.98]"
          loading="lazy"
        />
        <div className="absolute left-[5%] bottom-[5%] w-4/5 ">
          <h2 className="text-secondary text-sm sm:text-xl md:text-lg uppercase opacity-0 font-bold group-hover:opacity-100 group-hover:transition-all group-hover:duration-1000 group-hover:ease-out mb-8 xl:text-xl">
            {title}
          </h2>
          <p className="text-additional text-xs sm:text-lg md:text-sm opacity-0 group-hover:opacity-100 group-hover:transition-all group-hover:duration-1000 group-hover:ease-out xl:text-lg">
            {description}
          </p>
        </div>
      </div>
      <div className="w-[95%] mx-auto flex items-center justify-center gap-[20%] sm:gap-[30%] ">
        <a href={link} target="_blank" rel="noreferrer">
          <button
            className="projects-buttons group duration-500 text-lg sm:text-xl lg:text-lg xl:text-2xl"
            ref={buttonLink}
          >
            Link{" "}
            <LuArrowRight className="projects-button__icon text-lg sm:text-xl lg:text-lg xl:text-2xl" />
          </button>
        </a>
        <a
          aria-label="github"
          href={repo}
          target="_blank"
          rel="noreferrer"
          ref={buttonGit}
          className="opacity-0"
        >
          <LuGithub className="projects-button__icon hover:text-white hover:scale-105 transition-all duration-300 text-lg sm:text-xl lg:text-lg xl:text-2xl" />
        </a>
      </div>
    </div>
  );
};

export default ProjectCard;

and lastly the form's snippet

import React, { useRef, useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/all";

const Contact = () => {
  const [result, setResult] = React.useState("");

  const onSubmit = async (event) => {
    event.preventDefault();
    setResult("Sending....");
    const formData = new FormData(event.target);

    formData.append("access_key", "4359a0b1-14fd-4278-b15b-c45673a1d9dc");

    const res = await fetch("https://api.web3forms.com/submit", {
      method: "POST",
      body: formData,
    }).then((res) => res.json());

    if (res.success) {
      setResult(res.message);
    } else {
      setResult(res.message);
    }
  };

  const contacts = useRef();
  gsap.registerPlugin(ScrollTrigger);

  useEffect(() => {
    gsap.fromTo(
      contacts.current,
      {
        autoAlpha: 0,
      },
      {
        autoAlpha: 1,
        duration: 1.5,
        ease: "power1.inOut",
        scrollTrigger: {
          trigger: contacts.current,
          start: "top 90%",
        },
      }
    );
  }, []);

  return (
    <section id="contact">
      <div
        className=" sm:max-w-[70%] sm:mx-auto lg:flex lg:mt-16 lg:gap-16 lg:items-center"
        ref={contacts}
      >
        <h3 className="text-center text-secondary mt-12 mb-8 font-bold text-xl md:text-2xl lg:mt-0 px-4 sm:px-0 xl:text-3xl font-PrimaryF lg:font-SecondaryF">
          Have you got a project in mind? Let&#39;s talk
        </h3>
        <form
          onSubmit={onSubmit}
          className="w-4/5 mx-auto rounded-lg px-8 py-4 mb-12 shadow-3xl sm:max-w-[65%] lg:max-w-[50%]"
        >
          <label
            htmlFor="name"
            className="text-secondary pl-2 font-PrimaryF relative"
            aria-label="name"
          >
            <input
              type="text"
              id="name"
              className="form-input"
              placeholder="Name"
              required
            />
          </label>

          <label
            htmlFor="email"
            aria-label="email"
            className="text-secondary pl-2 font-PrimaryF -translate-x-8"
          >
            <input
              type="email"
              id="email"
              className="form-input"
              placeholder="Email"
              required
            />
          </label>

          <label htmlFor="textarea" aria-label="textaread">
            <textarea
              id="textarea"
              name="Text"
              className="form-input mt-8"
              placeholder="Message"
            ></textarea>
          </label>
          <input
            type="submit"
            className="text-secondary bg-transparent border-2 border-secondary px-8 py-1 rounded-lg block mx-auto hover:bg-secondary hover:text-additional
          transition-all duration-300 font-PrimaryF mb-8 mt-12"
            aria-label="submit"
          />
          <span className=" text-secondary mx-auto text-center block">
            {result}
          </span>
        </form>
      </div>
    </section>
  );
};

export default Contact;

Thanks in advance for any help or suggestion provided

 

PS: I know I should have posted a CodePen demo, but it has proved very difficult to create for me. 

Link to comment
Share on other sites

It's pretty tough to troubleshoot without a minimal demo - the issue could be caused by CSS, markup, a third party library, your browser, an external script that's totally unrelated to GSAP, etc. Would you please provide a very simple CodePen or StackBlitz that demonstrates the issue? 

 

If you're using something like React/Next/Vue/Nuxt or some other framework, you may find StackBlitz easier to use. We have a series of collections with different templates for you to get started on these different frameworks: React/Next/Vue/Nuxt.

 

If you're using React also read the following blog post!

 

Please don't include your whole project. Just some colored <div> elements and the GSAP code is best (avoid frameworks if possible). See if you can recreate the issue with as few dependancies as possible. If not, incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, then at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

 

Here's a starter CodePen that loads all the plugins. Just click "fork" at the bottom right and make your minimal demo

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

 

Once we see an isolated demo, we'll do our best to jump in and help with your GSAP-specific questions. 

Link to comment
Share on other sites

Hi @PizzaNCoding88 and welcome to the GreenSock forums!

 

Is great to hear that you were able to fix your issue!

 

The one thing I can tell you from a quick glance at the code you posted is that you're not using GSAP Context and doing proper cleanup in your code (as explained in the GSAP & React article link provided in the post above).

 

Also you have an image in your layout, normally images can take a bit more time to load than the time it takes to your custom JS to be executed, that can create some problems with the calculations ScrollTrigger makes, because it will cause a layout shift. The solution is to either give the image a fixed height or create your ScrollTrigger instances after the images (especially the ones that affect your ScrollTrigger's start and end points) are loaded.

 

Finally keep in mind that in React child components are rendered first and then React works it's way up the component tree, so it's always a good idea to have some sort of diagram (even in paper) of the structure of your app and how one sibling can affect another down the lines, especially if they have images.

 

Happy Tweening!

Link to comment
Share on other sites

20 hours ago, Rodrigo said:

Hi @PizzaNCoding88 and welcome to the GreenSock forums!

 

Is great to hear that you were able to fix your issue!

 

The one thing I can tell you from a quick glance at the code you posted is that you're not using GSAP Context and doing proper cleanup in your code (as explained in the GSAP & React article link provided in the post above).

 

Also you have an image in your layout, normally images can take a bit more time to load than the time it takes to your custom JS to be executed, that can create some problems with the calculations ScrollTrigger makes, because it will cause a layout shift. The solution is to either give the image a fixed height or create your ScrollTrigger instances after the images (especially the ones that affect your ScrollTrigger's start and end points) are loaded.

 

Finally keep in mind that in React child components are rendered first and then React works it's way up the component tree, so it's always a good idea to have some sort of diagram (even in paper) of the structure of your app and how one sibling can affect another down the lines, especially if they have images.

 

Happy Tweening!

Hello Rodrigo,

 

Many thanks for the suggestions above, and for reading through my code even though I haven't provided a demo.

 

I have to say that the useLayoutEffect solution didn't really work. On desktop the issue seems to have gone (most of the times), but on mobile it's still there. And I am now noticing this is actually due to the image. A slow scroll works fine, but if I scroll fast to the image, it gets animated correctly but at the same time triggers all the animations below it (even though they are defined in other functions and components).

 

So far I have applied GSAP context to all the animations in the project, done some clean-ups, fixed an issue with another animation which was showing some error in the console.

 

Re the image, what do you mean  by creating the ScrollTrigger istances after the images?

 

At the moment I have wrapped the image in a div and also changed the animation from animating the scale to animating only the opacity, but this didn't seem to have fixed it either.

 

Edit: if it helps, the issue seems to be present only on chrome on mobile, while firefox works great.

Edited by PizzaNCoding88
small edit for additional information
Link to comment
Share on other sites

3 hours ago, PizzaNCoding88 said:

Re the image, what do you mean  by creating the ScrollTrigger istances after the images?

Let's say that you have a Hero section with a 100vh height, then you have a section with images (section A) and then another section where you create a ScrollTrigger instance (section B). When your app first loads those images are not loaded and their heights is zero pixels. The ScrollTrigger instance in Section B is created, then as your images are loaded they get their natural height based on the CSS you have applied to them. What happens? The start and end points of the ScrollTrigger instance in section B now are off because the height of section A is bigger that what it was when ScrollTrigger made it's calculations. That's called layout shift or more commonly Cumulative Layout Shifting (you'll find a ton of info on google about this).

 

4 hours ago, PizzaNCoding88 said:

At the moment I have wrapped the image in a div and also changed the animation from animating the scale to animating only the opacity, but this didn't seem to have fixed it either.

Yep, this is not about wrapping the image around one or more elements, is about the height of either the image and it's wrapping elements. The best option is to wait until all the images are loaded and then create your ScrollTrigger instances or call ScrollTrigger.refresh():

https://greensock.com/docs/v3/Plugins/ScrollTrigger/static.refresh()

 

Hopefully this helps.

Happy Tweening!

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