Jump to content
Search Community

ScrollTrigger Start position does not change if other component are changing height in React

Pollux Septimus test
Moderator Tag

Recommended Posts

Hello,

I am using ScrollTrigger in React and I've noticed that the start position doesn't update when a component before it changes height. I've tried refreshing the ScrollTrigger on Complete, but since the different sections are in different components, this doesn't work for me.  Sorry for the lack of a minimal demo but the animations are a bit complex for me to be able to quickly put a minimal demo together. 

Component that changes size

import { useState, useEffect, useRef } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/all';

import useIsMobile from 'hooks/useIsMobile';

import styles from './styles.module.css';

const Tabs = ({ config }) => {
  const [activeTab, setActiveTab] = useState(0);
  const [tabsHeight, setTabsHeight] = useState();

  const isMobile = useIsMobile(501);
  const isTablet = useIsMobile(1200);

  const tl = useRef();
  const tabsContainerRef = useRef();
  const tabsBG = useRef();

  useEffect(() => {
    setTabsHeight(tabsBG.current.getBoundingClientRect().height);
  }, [isMobile]);

  useEffect(() => {
    gsap.registerPlugin(ScrollTrigger);

    const context = gsap.context(() => {
      tl.current = gsap.timeline({
        scrollTrigger: {
          trigger: tabsContainerRef.current,
          start: 'top bottom-=50',
          end: 'bottom',
          toggleActions: 'restart none none reverse',
        },
      });

      tl.current
        .from(tabsBG.current, {
          width: 45,
          height: 45,
          duration: 0.5,
        })
        .from('#tabsID', {
          opacity: 0,
        });
    });

    return () => context.revert();
  }, [isMobile, isTablet]);

  return (
    <div ref={tabsContainerRef} className={styles.tabsContainer}>
      <div className={styles.tabsNav}>
        <div ref={tabsBG} className={styles.tabsBG}>
          {config.map((item, index) => (
            <div
              key={index}
              id='tabsID'
              className={`${styles.tab} ${
                activeTab === index && styles.tabActive
              }`}
              onClick={() => setActiveTab(index)}
            >
              {item.tab}
            </div>
          ))}
        </div>
      </div>
      <div>{config[activeTab]?.content}</div>
    </div>
  );
};

export default Tabs;

The Component below:

import { useState, useEffect, useRef } from 'react';
import AnimatedImage from 'components/AnimatedImage';
import gsap from 'gsap';
import { Link } from 'react-router-dom';

import useIsMobile from 'hooks/useIsMobile';

import styles from './styles.module.css';

const ProjectsSectionDesktop = ({ projectsData }) => {
  const [hover, setHover] = useState(false);
  const [animLength, setAnimLength] = useState(false);
  const [height, setHeight] = useState();

  const projectsContainerRef = useRef();
  const fadeInTl = useRef();
  const scrollTl = useRef();
  const trackRef = useRef();
  const titleRef = useRef();
  const pinContainerRef = useRef();

  const isMobile = useIsMobile(1200);

  useEffect(() => {
    setAnimLength(trackRef.current.offsetWidth);

    console.log(height);

    const context = gsap.context(() => {
      const projects = gsap.utils.toArray('#projectsContainer');

      gsap.set(projects, {
        xPercent: 50,
        opacity: 0,
      });

      fadeInTl.current = gsap.timeline({
        scrollTrigger: {
          trigger: pinContainerRef.current,
          start: 'top bottom',
          preventOverlaps: true,
          markers: true,
        },
      });

      fadeInTl.current
        .to(titleRef.current, {
          opacity: 0.1,
          duration: 2,
          ease: 'power1.out',
        })
        .to(
          projects,
          {
            xPercent: 0,
            opacity: 1,
            duration: 1,
            stagger: 0.25,
            ease: 'power2.out',
          },
          '<'
        );
    });

    return () => context.revert();
  }, [projectsData, isMobile]);

  useEffect(() => {
    setHeight(projectsContainerRef.current.getBoundingClientRect().top);

    const scrollContext = gsap.context(() => {
      scrollTl.current = gsap.timeline({
        scrollTrigger: {
          trigger: projectsContainerRef.current,
          start: 'top top',
          end: `+=${animLength}`,
          pin: true,
          scrub: 0.5,
          preventOverlaps: true,
        },
      });

      scrollTl.current
        .to(trackRef.current, {
          xPercent: -100,
          ease: 'none',
        })
        .to(
          titleRef.current,
          {
            xPercent: -50,
            ease: 'none',
          },
          '<'
        );
    });

    return () => scrollContext.revert();
  }, [projectsData, animLength, isMobile, height]);

  return (
    <div ref={pinContainerRef} style={{ border: '1px solid red' }}>
      <div ref={projectsContainerRef} className={styles.projectsContainer}>
        <h1 ref={titleRef} className={styles.title}>
          Projects
        </h1>
        <div ref={trackRef} className={styles.projectsTrack}>
          {projectsData?.map((project, index) => (
            <div
              id='projectsContainer'
              key={index}
              className={styles.projectContainer}
            >
              <Link to={`/projects/${project.id}`} className={styles.link}>
                <div
                  id='projectImageContainer'
                  className={styles.projectImageContainer}
                  onMouseEnter={() => setHover(true)}
                  onMouseLeave={() => setHover(false)}
                >
                  <AnimatedImage src={project.image} playAnim={hover} />
                </div>
              </Link>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default ProjectsSectionDesktop;

2023-05-1010_50_30-ReactApp.thumb.png.80a3ce608ed285e4f01b4338495a3e8f.png2023-05-1010_50_47-ReactApp.thumb.png.2b8c283328adbba75b896500c6cb527f.png

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 CodeSandbox that demonstrates the issue? 

 

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

 

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.

 

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 there,

 

ScrollTrigger.refresh refreshes all ScrollTriggers, so components shouldn't make a difference.

 

Quote

I've tried refreshing the ScrollTrigger on Complete

 

Unless you're just targeting one trigger and it's the wrong one?...

There's no refresh call in the code you provided so I can't really see what you're doing wrong and advise.
 

I also don't understand this sentence, sorry, maybe a typo?

Quote

start position doesn't update when a component before it changes height


ScrollTrigger has to be told to refresh after you make changes in the DOM, if you're setting height, I'd probably use a GSAP tween to do that and then use an onComplete to call ScrollTrigger.refresh()

Maybe this helps, sorry I can't advise any more than this. If you can get a demo together it's be much easier to see where you're going wrong.

Link to comment
Share on other sites

@GSAP Helper Hello, The templates were very nice and I manage to build this minimal demo. It behaves exactly like it does in my project.

 

@Cassie Hello Cassie, I have added ScrollTrigger.refresh() to the onComplete callback function of the last tween in the animation sequence, on the first component. There is a very high chance that I haven't done it right. I also apologize for the improper grammar.

I meant to say that if a component's height changes through any means, and there is a different component below it that uses ScrollTrigger, the start indicator that is visible with the markers: true option does not update to reflect the new position of the component. With the minimal demo that I've linked above it should be clear. :)

 

Thank you for your answer

Link to comment
Share on other sites

Thanks for the demo! I can't find that call but that's not where it needs to be.

 

ScrollTrigger.refresh is just a way to say "hey ScrollTrigger, I've changed something, can you update the positions" So in your case, as you're updating the height state, you need to react to that change.

Something like this, set up an effect that gets called when the height var changes and then refresh. 

const [height, setHeight] = useState(100);

useEffect(() => {
  console.log('height', height);
  ScrollTrigger.refresh();
}, [height]);

 

https://stackblitz.com/edit/react-lqtgoc?file=src%2Fcomponents%2FProjectsSectionDesktop%2Findex.jsx,src%2Fcomponents%2FSize%2Findex.jsx,src%2FApp.js

Link to comment
Share on other sites

Hi,

 

I've been looking around your las link for a bit and I can't find a simple solution for this. As @Cassie mentions this most likely has to do with the way you're handling state changes. You have multiple effect hooks in your components checking for different properties. Also you have this in a component:

const isMobile = 'sfddsf';

useEffect(() => {
  setAnimLength(trackRef.current.offsetWidth);

  console.log(height);

  const context = gsap.context(() => {
    
  });

  return () => context.revert();
}, [projectsData, isMobile]);

You're passing a constant as a dependency in the array of your hook. That doesn't make a lot of sense.

 

Also we always recommend using useLayoutEffect to ensure that the DOM has been changed and rendered when running the code. I created an ultra simplified example that shows that running ScrollTrigger.refresh() should have the effect you're looking for:

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

 

Unfortunately we don't have the time resources to comb through an entire codebase trying to find the problem. Even if you already reduced it to it's bare minimum, it's still a lot.

 

Sorry I can't be of more assistance. 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

@Rodrigo Hello, Thank you for your time. 

I am aware that the code is quite a lot, I was hoping this is something common with React and it has a simple solution. Regarding the useLayoutEffect, I had more issues using it than not. I will try to find a solution myself and If I will I'll let you know. 

Link to comment
Share on other sites

I believe I have found a viable solution.  I have implemented a short delay so that everything is invisible during the refresh process. Additionally, I have modified the animation based on this delay.

As for the dependency passed as a constant in the array, it is actually a custom hook that returns a boolean value depending on whether the user is on a mobile device or not. I didn't want to copy the entire hook, so I just set it to a random string to avoid any errors.

 

Thank you all very much for your help. 

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