Jump to content
Search Community

infinite timeline iteration and scope

Jean-Tilapin test
Moderator Tag

Go to solution Solved by Cassie,

Recommended Posts

Hello there!

I'm updating an old code I wrote 3 years ago that used GSAP and ScrollMagic. Now that GSAP has a Scrolltrigger function, it's time to rethink the whole app. By the way, congratulations for your new GSAP version, it's awesome and there's a lot less confusion between tweens, tweenmax, tweenlite, etc.

Ok so there's my problem:

Context: in a Javascript Class with constructor, managing the homepage of an App, I have a carousel (see Codepen). It has hexagons in it but that's irrelevant now ; the idea is that after the first flip, the second image is visible (so far so good). The first image that was on display is now replaced by a third image. Next flip, the third image is shown, and the second one (now on backface) must be replaced by a fourth one. Next flip, the fourth image is visible and the first image is back on the backface to be shown with the next flip. See the mechanism here?

 

My problem: get the tl.iteration() out of the scope to pass it to the function that choses what image to put on the backface.

Could you please help me please? How can I simplify the process and make it work? Thank you.

export default class HomeApp {

    constructor() {

        this.initGrid(); //And a lot of other stuff

    }

    initGrid(){

        //Function that calculates how to generate the carousel hexagons then calls

        this._animateBackground();

    }

    _animateBackground(){
        var tl = gsap.timeline({repeat: -1, repeatDelay: 5, repeatRefresh: true});

        var progress = 0;

        tl.to(".hexagon-inner", {
            rotationY: '-=180',
            duration: 1,
            ease: "circ.inOut",
            stagger: 0.2,
            onStart: getProgress,
            onComplete: this.test,//This should be the function getting the iteration and chosing what image must be put on the backface for the next flip
            onCompleteParams: [progress]
        });            

        ScrollTrigger.create({ //Pauses the animation when not in viewport
            trigger: '.hexa-grid',
            end: 'bottom 50%',
            animation: tl,
            toggleActions: "play pause resume none",
        })
       
        function getProgress() {
            progress = tl.iteration();

            console.log('from inside: '+progress); //get the right iteration
        }

}

test(progress) {

    console.log('from outside: '+progress) //Stuck at 0

}

 

See the Pen vqYVRq by Jean-Tilapin (@Jean-Tilapin) on CodePen

Link to comment
Share on other sites

I think I see. Thank you.

The following code seems to work, but is this the best way? I easily admit that "this" scope is still tricky for me, sorry 😕

 

constructor() {

   this.iteration = 0; //Added

}

_animateBackground() {

    var tl = gsap.timeline({repeat: -1, repeatDelay: 5, repeatRefresh: true});

        tl.to(".hexagon-inner", {
            rotationY: '-=180',
            duration: 1,
            ease: "circ.inOut",
            stagger: 0.2,    
            onStart: updateIteration,   //onCompleteParams removed    
            onComplete: this.test,            
        });            

        ScrollTrigger.create({
            trigger: '.hexa-grid',
            end: 'bottom 50%',
            animation: tl,
            toggleActions: "play pause resume none",
        })

        function updateIteration() {
            this.iteration = tl.iteration();
        }
        
    }

    test() { //Edited in this post to remove "iteration" param
        console.log('from outside: '+this.iteration)
    }

 

Link to comment
Share on other sites

Hey if it works it works!

and yeah - 'this' is very confusing, it's not just you. Don't worry about it.

Another option is using an arrow function as arrow functions behave differently from regular functions.  "this" inside an arrow function is lexically bound, which basically just means its value is determined by the scope it's originally defined in. So in this case 'this' refers to the class, not the tween. 

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

Link to comment
Share on other sites

I re-open that topic because I might encounter another problem with updating the "onCompleteParams":

So far, I have this, and it's working...

_animateCarousel(images){
        var images = this._whichImagesAreTheFittest(); //Gets the right Images Format (mobile, tablet, portrait, landscape, etc.)

        var tl = gsap.timeline({repeat: -1, repeatDelay: 3, repeatRefresh: true});

        tl.to(".hexagon-inner", {
            rotationY: '-=180',
            duration: 1,
            ease: "circ.inOut",
            stagger: 0.2,    
            onStart: updateIteration,        
            onComplete: this._changeBackgroundImage,
            onCompleteParams: [images]            
        });            

        ScrollTrigger.create({
            trigger: '.hexa-grid',
            end: 'bottom 50%',
            animation: tl,
            toggleActions: "play pause resume none",
        })

        function updateIteration() {
            this.iteration = tl.iteration();
        }        
    }

 

   _changeBackgroundImage(images) {    
        //We divide this.iteration by 4 and look at the last digits
        console.log('dans _changeBackgroundImage: ', images);

 

...but when I resize the window, the _animateCarousel() functions is triggered again and the last console.log is triggered twice, with two different values: the new correct set of images, but also the old one.

 

I'm not sure that its a GSAP problem but I'm scratching my head figuring it out. Any idea why it uses two sets of values? I should update onCompleteParams, I guess, but haw can I do that?

 

Thank you.

Link to comment
Share on other sites

It's difficult to say without seeing a minimal demo, but it sounds like you may be re-creating tweens/ScrollTriggers on resize but you forgot to kill() the old ones, so BOTH are running. Make sure you kill() the old ones if you're recreating them. 

 

Again, if you still need help please make sure you provide a minimal demo with only the absolutely essential code to see the problem (some simple colored <div> elements would be fine). 

Link to comment
Share on other sites

Edit: Codepen on first Post is now up-to-date and functioning => well, it shows that on resize, it bugs.

 

So, now, I may ask: at what point should the gsap be killed and restarted? Documentation is quite unclear about it.

And side quest: how can I get the right images once and eventually a second time on resize?

 

Thank you.

Link to comment
Share on other sites

That codepen contains a lot to look through -  as Jack says, it's best to create a small minimal demo if you have additional questions.

I'm not certain what you mean about the image side quest, but you're calling the _animateCarousel function every time you create a new grid on resize. This is creating new animations and scrollTriggers every time.

I've tweaked it for you so it only creates the animation the first time and then refreshes it after the new grid's been created.

 

See the Pen KKQeQZo?editors=0010 by GreenSock (@GreenSock) on CodePen

Link to comment
Share on other sites

Thank you for your intervention!

I've created that "full" codepen because I took quite some time to recreate that weird image behavior and couldn't succeed. So here's the (almost) full one. Sorry.

But you can see it on your own codepen: try to resize a few times the result space and you still can see that unwanted image change.

 

About that "side quest" I mentionned: now, "changeBackground" is called at the end of every flip (onComplete). The only way I found to get the right images, is to call "whichImagesAreTheFittest" inside "changeBackground", so every 3 seconds. It seems totally unnecessary so I'm looking for another way to set (then access) the right images once when the homepage is hit, and another time on resize.

Link to comment
Share on other sites

Ah I see. I'm not sure how much more I can help here - We love helping with questions related to the GSAP API, but unfortunately we just don't have the resources to provide free general consulting and logic troubleshooting.

Of course anyone else is welcome to post an answer if they'd like - I just want to manage expectations.  

 

If you're struggling - you can post in the "Jobs & Freelance" forum for paid consulting, or contact us directly. 

Link to comment
Share on other sites

Ok. Thank you Cassie.

I took the time to write a new Codepen with only the flawed logic. Change a few time the size of the "result area" and you can see my problem: as Jack said, it seems that new timelines add up each time we resize and finally causes that weird effect I'm trying to avoid.

So, as Jack said, I could try to kill the timeline each time the resize event is triggered, but again, I don't get how that killing process must be controlled. If anyone can guide me through that process, or has any another solution to submit, I would be really glad.

Thank you and have a nice week-end :)

 

See the Pen yLvEqyG?editors=1111 by Jean-Tilapin (@Jean-Tilapin) on CodePen

  • Like 1
Link to comment
Share on other sites

On 6/3/2022 at 5:05 PM, Jean-Tilapin said:

as Jack said, I could try to kill the timeline each time the resize event is triggered, but again, I don't get how that killing process must be controlled. If anyone can guide me through that process, or has any another solution to submit, I would be really glad.

 

Hey again - thanks for putting together this pen. But this bit of your issue I understand and already solved for in that pen -  see that I added an iteration count? If it's the first iteration the timeline gets created, otherwise it just gets refreshed and the internally recorded starting values invalidate. Then you aren't creating new timelines each time so you don't have to kill them.

 

I'm not certain about how to fix the other things you've mentioned 1) because I don't quite grasp the request and 2) it's not directly GSAP related.
 

class HomeApp {

  initGrid() {
   let iteration = 1;
   this._animateCarousel(iteration);
   iteration++;
  }

  _animateCarousel(iteration) {
    if (iteration > 1) {
      ScrollTrigger.refresh();
      return;
    }
    var tl = gsap.timeline({ repeat: -1, repeatDelay: 3, repeatRefresh: true });

    tl.to(".hexagon-inner", {
      rotationY: "-=180",
      duration: 1,
      ease: "circ.inOut",
      delay: 3,
    });
    
    ScrollTrigger.create({
     trigger: '.hexa-grid',
     end: 'bottom 50%',
     animation: tl,
     toggleActions: "play pause resume none",
     invalidateOnRefresh: true
  })
  }
}

let app = new HomeApp();

 

Link to comment
Share on other sites

There's so much I don't understand, that's sooo annoying. And I'm also bothering you, I'm very sorry. That was obviously not my goal at all.

- I don't get why in your code the iteration variable should not be equal to 1 each time (in fact, I console logged it, and I only get 1, whatever I do)

- I don't get why ScrollTrigger could be the source of that stutter problem you can witness when changing the orientation of your phone. ScrollTrigger isn't even used on that blue/red rectangle pen.

- I've changed the blue/red rectangle pen to use an incrementing iteration var, and used tl.kill() instead of scrolltrigger.refresh(), without fully understanding what I was trying. And it seems to be working, the stutter disappeared, even when changing hundreds of times the windows size: but it doesn't work on the real animation, so I guess it's just pure luck. (you can comment out lines 24 to 27 to see how that kill() seems to  fix the problem)

 

Anyway, I give up, no more time to waste on understanding gsap and javascript. Back to my cave and my servers. I guess I'll replace the animation on phone with some css slideshow, it will be good enough.

Thank you for your patience Cassie.

  • Like 1
Link to comment
Share on other sites

Heya,
 

You're right, I thought it was fixed but I hadn't realised that the init grid function was being called each time. Sorry about that. The purpose on the iteration was just to stop more tweens being created each time and scrollTrigger.refresh() was just to refresh the scrollTrigger positions when the grid got recreated.

I can take another look tomorrow (bedtime here now) 

Link to comment
Share on other sites

  • 2 weeks later...

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