Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
AnarchyPancake

How to get ScrollTrigger to refire dynamic animation on a tabbed template

Recommended Posts

I am having trouble getting my ScrollTrigger animations to fire dynamic animations (those of which are housed in premade functions) that a user selects from a wysisyg on a tabbed template. My setup is using react in conjunction with the GSAP library and things are componentized so we don't really always know what's on the page so we need the component to be able to restart itself. The issue comes when I am trying to trigger the animation upon tab click. OnRefresh it would be great to key off the tab click to be able to restart the ScrollTrigger animation and have it actually only fire upon scrolling to the trigger start point. As it is now, it either restarts the animation upon tab click without waiting for me to scroll to the trigger as it should or like my codepen demo, it fires only once and none of the other elements (brightly colored boxes) animate after clicking the tab and scrolling to the trigger starting point.

See the Pen MWJEOYQ?editors=1111 by ashleigh (@ashleigh) on CodePen

Link to comment
Share on other sites

Hi and welcome to the GreenSock forums.

 

I'm not seeing any animation playing at any point. Also your sample doesn't have any scrolling available so we can't test that neither.

 

If you want to control the animation of the content of each tab, I'd suggest you to loop through those elements and create a Scroll Trigger for each one, also put each tab in it's own component in order to better control each one's Scroll Trigger instance.

 

Right now you're creating a scroll trigger for this selector: animation1('.show .box'), which is just the current active box and you're doing in it in a useEffect hook that runs on the main component's initial render.

 

As I said, move each tab content element to it's own component and create the scroll trigger instance in there. Finally create some scrolling space on each one in order to see what is and is not working and why.

 

Unfortunately I don't have time right now to create a working sample for you, so hopefully this helps in some way.

 

Happy Tweening!!!

  • Like 3
Link to comment
Share on other sites

16 hours ago, Rodrigo said:

Hi and welcome to the GreenSock forums.

 

I'm not seeing any animation playing at any point. Also your sample doesn't have any scrolling available so we can't test that neither.

 

If you want to control the animation of the content of each tab, I'd suggest you to loop through those elements and create a Scroll Trigger for each one, also put each tab in it's own component in order to better control each one's Scroll Trigger instance.

 

Right now you're creating a scroll trigger for this selector: animation1('.show .box'), which is just the current active box and you're doing in it in a useEffect hook that runs on the main component's initial render.

 

As I said, move each tab content element to it's own component and create the scroll trigger instance in there. Finally create some scrolling space on each one in order to see what is and is not working and why.

 

Unfortunately I don't have time right now to create a working sample for you, so hopefully this helps in some way.

 

Happy Tweening!!!

I made the suggested edits so that this will fire off on tab click but the onRefresh property, how can I restart/refresh the animation to fire again from a position of 0 upon the next tab click?

Link to comment
Share on other sites

Hi,

 

I saw that you updated the codepen sample. It was somehow helpful, but I'll get to that later ;)

 

The thing is that the setup of your app is quite unorthodox and a bit difficult to follow. The solution I came up using your code is to pause the animation of the currently visible tab and set it's time to 0, before changing the visible tab. Like that the next time a user goes back to that tab, the element's position will be what you expect and the animation will fire again. This code seems to do that:

 //refs
const containerRef = React.useRef(null);
// CREATE A REFERENCE FOR THE TAB ANIMATIONS
const tweenRefs = React.useRef([]);
// CREATE A REFERENCE FOR THE CURRENT TAB INDEX
const currentTab = React.useRef(0);

//state
const [tabclick, setTabclick] = React.useState();

React.useEffect(() => {
const tabs = document.querySelectorAll(".tabs .tab");
const items = containerRef.current.querySelectorAll(".tabbed-content .item");

//toggle tabs
tabs.forEach((tab) => {
  tab.addEventListener("click", (e)=>{
    // setTabclick(Math.random());
    
    tabs.forEach((tab) => {
        tab.classList.remove('active');
    });
    tab.classList.add('active');
       
    
    let tabIndex = tab.classList[1].substr(3);
    // PAUSE THE ANIMATION OF THE CURRENT TAB
    tweenRefs.current[tabIndex - 1].pause(0);
    // UPDATE THE REFERENCE OF THE CURRENT TAB INDEX
    currentTab.current = tabIndex;
    
    items.forEach(item => {
      let itemIndex = item.classList[1].substr(4);
      
      if(itemIndex === tabIndex){
        item.classList.add("show");
      } else {
        item.classList.remove("show");
      }    
    });
    
  });
});

//GSAP animation
function animation1(element){
  let tl = gsap.timeline();
  
  tl.to( element, {
    x: 500, 
    duration: 1, 
    ease: "elastic"
  });
  // CREATE A REFERENCE FOR THE ANIMATION
  tweenRefs.current.push(tl);
  return tl;
}
  
  //console.log(items);
  items.forEach(item => {
        
        //console.log(item.classList.contains("show"));
      if(item.classList.contains('show')){
        console.log("item::");
        console.log(item.querySelector('.box'));
      }
        
  });

//GSAP SCROLLTRIGGER
  ScrollTrigger.matchMedia({
    "(min-width: 768px)": function() {
      
     items.forEach((item, index) => { 
        ScrollTrigger.create({
          trigger: '.show .box',
          start: "center center-=100",
          end: "bottom top",
          id: "st-box-" +index,
          animation: animation1('.show .box'),//animation1(item.querySelector('.box')),
          markers: true,
          toggleActions: "play pause resume pause",
          onRefresh: self => {
            
              if (item.offsetHeight === 0) {
                //console.log(item.offsetHeight);
                self.animation.pause(0);
              } else {
                //console.log(item.offsetHeight);
                self.animation.progress(0);
              }
         
          }
        });
      });
      
    }
  });
  
}, []); // REMOVE DEPENDENCY IN ARRAY, NO NEED FOR THAT

Finally, I strongly recommend you to get your feet wet in React, right now it shows that you're just starting with it since you're basically writing vanilla JS inside a React set up and, while it works, you're not taking advantage of some features and strengths the framework has to offer in order to make your life easier as a developer. I don't have time to go through everything that you're not doing in the most adequate way since it would take quite a while. Go to the react documentation and take a look at it, also you could benefit from some free video tutorials that should be quite helpful.

 

This is a short one by Brad Traversy, while it might not cover everything you need to know to work in React (spoiler alert no course does) Brad has a very good and calm teaching approach:

https://www.youtube.com/watch?v=w7ejDZ8SWv8

 

This is a bit longer and perhaps covers more features than Brad's:

https://www.youtube.com/watch?v=4UZrsTqkcW4

 

Happy Tweening!!!

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

On 4/10/2021 at 11:20 AM, Rodrigo said:

Hi,

 

I saw that you updated the codepen sample. It was somehow helpful, but I'll get to that later ;)

 

The thing is that the setup of your app is quite unorthodox and a bit difficult to follow. The solution I came up using your code is to pause the animation of the currently visible tab and set it's time to 0, before changing the visible tab. Like that the next time a user goes back to that tab, the element's position will be what you expect and the animation will fire again. This code seems to do that:



 //refs
const containerRef = React.useRef(null);
// CREATE A REFERENCE FOR THE TAB ANIMATIONS
const tweenRefs = React.useRef([]);
// CREATE A REFERENCE FOR THE CURRENT TAB INDEX
const currentTab = React.useRef(0);

//state
const [tabclick, setTabclick] = React.useState();

React.useEffect(() => {
const tabs = document.querySelectorAll(".tabs .tab");
const items = containerRef.current.querySelectorAll(".tabbed-content .item");

//toggle tabs
tabs.forEach((tab) => {
  tab.addEventListener("click", (e)=>{
    // setTabclick(Math.random());
    
    tabs.forEach((tab) => {
        tab.classList.remove('active');
    });
    tab.classList.add('active');
       
    
    let tabIndex = tab.classList[1].substr(3);
    // PAUSE THE ANIMATION OF THE CURRENT TAB
    tweenRefs.current[tabIndex - 1].pause(0);
    // UPDATE THE REFERENCE OF THE CURRENT TAB INDEX
    currentTab.current = tabIndex;
    
    items.forEach(item => {
      let itemIndex = item.classList[1].substr(4);
      
      if(itemIndex === tabIndex){
        item.classList.add("show");
      } else {
        item.classList.remove("show");
      }    
    });
    
  });
});

//GSAP animation
function animation1(element){
  let tl = gsap.timeline();
  
  tl.to( element, {
    x: 500, 
    duration: 1, 
    ease: "elastic"
  });
  // CREATE A REFERENCE FOR THE ANIMATION
  tweenRefs.current.push(tl);
  return tl;
}
  
  //console.log(items);
  items.forEach(item => {
        
        //console.log(item.classList.contains("show"));
      if(item.classList.contains('show')){
        console.log("item::");
        console.log(item.querySelector('.box'));
      }
        
  });

//GSAP SCROLLTRIGGER
  ScrollTrigger.matchMedia({
    "(min-width: 768px)": function() {
      
     items.forEach((item, index) => { 
        ScrollTrigger.create({
          trigger: '.show .box',
          start: "center center-=100",
          end: "bottom top",
          id: "st-box-" +index,
          animation: animation1('.show .box'),//animation1(item.querySelector('.box')),
          markers: true,
          toggleActions: "play pause resume pause",
          onRefresh: self => {
            
              if (item.offsetHeight === 0) {
                //console.log(item.offsetHeight);
                self.animation.pause(0);
              } else {
                //console.log(item.offsetHeight);
                self.animation.progress(0);
              }
         
          }
        });
      });
      
    }
  });
  
}, []); // REMOVE DEPENDENCY IN ARRAY, NO NEED FOR THAT

Finally, I strongly recommend you to get your feet wet in React, right now it shows that you're just starting with it since you're basically writing vanilla JS inside a React set up and, while it works, you're not taking advantage of some features and strengths the framework has to offer in order to make your life easier as a developer. I don't have time to go through everything that you're not doing in the most adequate way since it would take quite a while. Go to the react documentation and take a look at it, also you could benefit from some free video tutorials that should be quite helpful.

 

This is a short one by Brad Traversy, while it might not cover everything you need to know to work in React (spoiler alert no course does) Brad has a very good and calm teaching approach:

https://www.youtube.com/watch?v=w7ejDZ8SWv8

 

This is a bit longer and perhaps covers more features than Brad's:

https://www.youtube.com/watch?v=4UZrsTqkcW4

 

Happy Tweening!!!

Thank you for your advice, the only stitch in my actual setup as opposed to my codepen demo is that my tabs component is actually a completely separate app from the animation component. We are using a traditional multi-page CMS where these individual items are being rendered as separate apps, as per how our build works with the CMS. Is there a way to use the useref for the tabs app in the animation app to start and stop the animations as per your suggestion? Also this logic would need to live in the animation app due to our set up.

Link to comment
Share on other sites

I'm not sure I completely understand what you're setup looks like, but hopefully this codepen is somehow close to that:

See the Pen dyNJREb by rhernando (@rhernando) on CodePen

 

Happy Tweening!!!

  • Like 2
  • Thanks 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.

×