Jump to content
Search Community

I'm encountering an issue where some animations complete early due to the horizontal scroll pinning using scrolltrigger.

Yashwanth S C test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

Hi GSAP Community,

     I have built a react web app, These are the component list is:
1) Landing page
2) About Page
3) Traction
4) Services
5) Ecosystem

6) Partners

I have used GSAP in all pages for their title reveal.

In traction component i have added horizontal scroll.
Now after this the scroll animations after that coponent are all altered.
I saw that two pairs of markers were visible for the Partner component title reveal even though there was only one scroll trigger.

So help me sort out this issue.

Tractions.jsx
 

import React, { useRef, useEffect } from 'react';
import './Traction.css';
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';
import NorthEastIcon from '@mui/icons-material/NorthEast';
import { isMobile } from 'react-device-detect';
gsap.registerPlugin(ScrollTrigger);
 
export default function Traction() {
  useEffect(() => {
    gsap.set('.cursor', { xPercent: -50, yPercent: -50 });
 
    let cursor = document.querySelector('.cursor');
    let hand = document.querySelector('.hand');
    let subPanels = document.querySelectorAll('.sub-panel');
 
    let mouseX;
    let mouseY;
 
    window.addEventListener('mousemove', e => {
      mouseX = e.clientX;
      mouseY = e.clientY;
 
      gsap.to(cursor, 0.5, { x: mouseX, y: mouseY });
    });
 
    subPanels.forEach(subPanel => {
      subPanel.addEventListener('mouseenter', () => {
        gsap.to(hand, 1, {
          scale: 1,
          opacity: 1,
          top: '-75px',
          left: '-75px',
          rotate: 0,
          ease: 'elastic.out(1, 0.3)',
        });
      });
 
      subPanel.addEventListener('mousemove', () => {
        gsap.to(hand, 1, {
          x: mouseX,
          y: mouseY,
        });
      });
 
      subPanel.addEventListener('mouseleave', () => {
        gsap.to(hand, 0.2, {
          scale: 0,
          opacity: 0,
          top: '10',
          left: '40',
          rotate: 45,
        });
      });
    });
  }, []);
 
  const component = useRef();
  const slider = useRef();
 
  if (!isMobile) {
    console.log("Not a mobile")
    useEffect(() => {
      let ctx = gsap.context(() => {
        let panels = gsap.utils.toArray(".panel");
        gsap.to(panels, {
          xPercent: -100 * (panels.length - 1),
          ease: "none",
          scrollTrigger: {
            trigger: slider.current,
            pin: true,
            scrub: 1,
            snap: 1 / (panels.length - 1),
            end: () => "+=" + slider.current.offsetWidth,
          }
        });
      }, component.current);
      return () => ctx.revert();
    },[]);
  }
 
  return (
    <div className='pin'>
    <div className="traction" ref={component}>
      <div ref={slider} className="container">
        <div className="cursor"></div>
        <div className="hand">View</div>
        <div className="panel">
          <div className="sub-panel">
            <div className='traction-content'>
            <h3>
              <span>21+ Universities</span> connected together in one community with equal opportunities to learn, connect and grow.
            </h3>
            <h4>
              View stats <NorthEastIcon className="arrow" />
              </h4>
              </div>
          </div>
          <div className="sub-panel">
          <div className='traction-content'>
            <h3>
              <span>21+ Comrades</span> working together to make a difference.
            </h3>
            <h4>
              View stats <NorthEastIcon className="arrow" />
              </h4>
              </div>
          </div>
        </div>
        <div className="panel">
          <div className="sub-panel">
          <div className='traction-content'>
            <h3>
              <span>10+ Events</span> conducted to connect students, developers, job seekers to enrich their knowledge and skills.
            </h3>
            <h4>
              View stats <NorthEastIcon className="arrow" />
              </h4>
              </div>
          </div>
          <div className="sub-panel">
          <div className='traction-content'>
            <h3>
              <span>4+ States</span> united together to establish blockchain clubs in universities, fostering web3 culture.
            </h3>
            <h4>
              View stats <NorthEastIcon className="arrow" />
              </h4>
              </div>
          </div>
        </div>
        <div className="panel">
          <div className="sub-panel">
          <div className='traction-content'>
            <h3>
              <span>1000+ Students</span> working together to build a effective web3 community.
            </h3>
            <h4>
              View stats <NorthEastIcon className="arrow" />
              </h4>
              </div>
          </div>
          <div className="sub-panel">
          <div className='traction-content'>
            <h3>
              <span>Immense Expansion</span> of the Web3 ecosystem is ongoing...
            </h3>
            <h4>
              View stats <NorthEastIcon className="arrow" />
              </h4>
              </div>
          </div>
        </div>
      </div>
      </div>
      </div>
  );
}

Traction.css:
.container {
    width: 300vw;
    height: 100vh;
    display: flex;
    flex-wrap: wrap;
}
 
.lastContainer {
    display: flex;
    height: 100vh;
    background-color: black;
    margin: 0;
}
 
.panel {
    width: 100vw;
    height: 100vh;
    color: var(--bg-color);
    background-color: black;
    display: flex;
    align-items: center;
    justify-content: space-around;
}
 
.sub-panel {
    height: 60%;
    width: 30%;
    border-radius: 30px;
    background-color: #2A2A39;
    padding: 4rem;
    color: #9DA7C4;
    cursor: pointer;
}
 
.sub-panel h3 {
    font-size: 1.4rem;
    line-height: 1.5;
}
 
.sub-panel h4 {
    font-size: 16px;
    margin: 1.33rem 0px;
    font-weight: bold;
}
 
.sub-panel span {
    color: white;
}
 
svg {
    vertical-align: 0;
}
 
.arrow {
    margin-bottom: -0.4rem;
}
 
.cursor,
.hand {
    position: fixed;
    left: 0;
    border-radius: 50%;
    pointer-events: none;
    transition: transform .1s;
}
 
.cursor {
    z-index: 999;
}
 
.hand {
    background: rgb(155, 0, 255);
    top: 50%;
    width: 80px;
    height: 80px;
    z-index: 9999;
    display: grid;
    place-content: center;
    transform: rotate(45deg);
    opacity: 0;
}
 
@media screen and (max-width:800px) {
    .traction {
        margin-top: -0.1rem;
    }
 
    .container {
        width: 100vw;
        height: 300vh;
        display: flex;
        flex-direction: column;
        flex-wrap: nowrap;
    }
 
    .panel {
        display: flex;
        flex-direction: column;
        width: 100%;
        height: 100vh;
    }
 
    .sub-panel {
        padding: 1rem;
        width: 100%;
        height: 50vh;
        padding: 0;
        margin-bottom: 2rem;
    }
 
    .traction-content {
        padding: 2rem;
    }
 
    .cursor,
    .hand {
        display: none;
    }
}

Partner.jsx:
import React, { useEffect, useRef } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import './Partner.css';
import push from '../assets/img/push.png';
import shardeum from '../assets/img/shardeum.png';
import buildbear from '../assets/img/buildbear.svg';
gsap.registerPlugin(ScrollTrigger);
 
const Partner = () => {
    const partnerRef = useRef(null);
 
        useEffect(() => {
            const partner = partnerRef.current
            gsap.to(partner.children, {
                y: 0,
                stagger: 0.05,
                delay: 0.5,
                duration: 0.5,
              ease: "back.out",
              scrollTrigger: {
                  trigger: partner,
                },
                onComplete: () => {
                  console.log("Partners animation")
              }
            });
        }, []);
 
       
 
    return (
        <div className='partner-container container-fluid d-flex'>
            <div className='partner-title col-md-5 d-flex align-item-center my-auto'>
                <h1 className='mx-auto d-flex flex-column' ref={partnerRef}>
                {Array.from("PARTNERS").map((letter, index) => (
                    <div key={index} className="letter">{letter}</div>
                ))}
                    </h1>
            </div>
            <div className='partner-content col-md-7 d-flex my-auto'>
                <span>
                    <img src={push} alt='push-protocol' />
                    <p className='partner-text mt-5'>Push Protocol</p>
                </span>
                <span>
                    <img src={shardeum} alt='shardeum' />
                    <p className='partner-text mt-5'>Shardeum</p>
                </span>
                <span>
                    <img src={buildbear} alt="build bear" />
                    <p className='partner-text mt-5'>Build Bear</p>
                </span>
            </div>
        </div>
    );
}
 
export default Partner;

partner.css:
.partner-container {
    background-color: black;
    color: white;
    height: 70vh;
    width: 100%;
    display: flex;
    align-items: center;
    flex-direction: column;
}
 
.partner-title {
    align-self: flex-start;
    padding-left: 3.5rem;
}
 
.partner-title h1 {
    font-size: 7rem;
    width: 100%;
    font-weight: bold;
    display: flex;
    clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
 
.partner-title h1 div {
    transform: translateY(140px);
    transition: transform .5s;
}
 
.partner-content {
    display: flex;
    align-items: center;
    justify-content: space-evenly;
    width: 100%;
}
 
.partner-content img {
    width: auto;
    height: 150px;
    margin: 0px 50px;
    padding: 0px 0px;
}
 
.partner-text {
    font-size: 1.2rem;
    font-weight: 600;
    padding: 0;
    justify-content: center;
    text-align: center;
    width: 100%;
}
 
/* .partner-title h1 .char{
    transform: translateY(140px);
    transition: transform .5s;
} */
 
@media screen and (max-width:800px) {
    .partner-title h1 {
        font-size: 3.5rem;
    }
 
    .partner-title {
        padding-left: 2rem;
    }
 
    .partner-content {
        flex-direction: column;
        gap: 3rem;
    }
 
    .partner-content img {
        margin: 0;
        height: 100px;
    }
}
I am ready to provide other details please check
Link to comment
Share on other sites

Without a minimal demo, it's very difficult to troubleshoot; the issue could be caused by CSS, markup, a third party library, a 3rd party script, etc. Would you please provide a very simple CodePen or Stackblitz that illustrates the issue? 

 

Using a framework/library like React, Vue, Next, etc.? 

CodePen isn't always ideal for these tools, so here are some Stackblitz starter templates that you can fork and import the gsap-trial NPM package for using any of the bonus plugins: 

 

Please share the StackBlitz link directly to the file in question (where you've put the GSAP code) so we don't need to hunt through all the files. 

 

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

 

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

  • Solution

Hi,

 

We understand that creating a demo from a repo is quite simple and quick, but we can't comb through an entire project trying to find what the issues are and where they reside in your codebase, that's why we ask for a minimal demo (emphasis on the minimal part).

 

For what I can see in that particular file (traction.jsx) you're not doing cleanup in your effect hook. Proper cleanup is very important with frameworks, but especially with React. React 18 runs in strict mode locally by default which causes your useEffect() and useLayoutEffect() to get called TWICE.

 

Since GSAP 3.12, we have the useGSAP() hook (the NPM package is here) that simplifies creating and cleaning up animations in React (including Next, Remix, etc). It's a drop-in replacement for useEffect()/useLayoutEffect(). All the GSAP-related objects (animations, ScrollTriggers, etc.) created while the function executes get collected and then reverted when the hook gets torn down.

 

Here is how it works:

const container = useRef(); // the root level element of your component (for scoping selector text which is optional)

useGSAP(() => {
  // gsap code here...
}, { dependencies: [endX], scope: container }); // config object offers maximum flexibility

Or if you prefer, you can use the same method signature as useEffect():

useGSAP(() => {
  // gsap code here...
}, [endX]); // simple dependency Array setup like useEffect()

This pattern follows React's best practices.

 

We strongly recommend reading the React guide we've put together at https://gsap.com/resources/React/

 

If you still need help, here's a React starter template that you can fork to create a minimal demo illustrating whatever issue you're running into. Post a link to your fork back here and we'd be happy to take a peek and answer any GSAP-related questions you have. Just use simple colored <div> elements in your demo; no need to recreate your whole project with client artwork, etc. The simpler the better.

 

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