Jump to content
Search Community

Update timeline variables when refreshed

Sacha test
Moderator Tag

Recommended Posts

Hey,

I'm looking for an easy way to update variables used in a tween. Take this example, where the duration is a variable that has to update when the window is resized. To avoid creating a new resize eventlistener, I'm looking for a way to update this variable using the integrated listener from GSAP.

let time = myFunction();

timeline.current.to(myRef.current, {
	scaleY: 2,
    duration: time[0],
}, 0)

In this example, using variables and functions inside a scrollTrigger, it will automatically update them, when the scrollTrigger gets refreshed (which happens on window resizes if I understand it correctly.) Here I have another question; which way it the right one? Using a arrow function like in the start property, or a normal function call like on the snapTo property (the start property works great, but using a normal function call inside the snapTo only fires once. Using an arrow function here doesn't seem to work, it breaks the snapTo and always scrolls to the top of the page when triggered.)?

tl1.current = gsap.timeline({
        scrollTrigger: {
            trigger: myRef.current,
            start: () => "0% 50%+=" + (myRef.current !== null ? myRef.current.clientHeight : 100),
            end: "100% 50%",
            snapTo: myFunction(),
        }
})

 

Since I'm working with React, is there a way to say for example

useEffect(() => {
	console.log("update");
}, [timeline.current.scrollTrigger])

which would call everytime the scrollTrigger of timeline updates / refreshes?

Link to comment
Share on other sites

Hey @Sacha,

 

You can use either normal function syntax or arrow functions - you will need to use invalidateOnRefresh too though
 

invalidateOnRefresh Boolean - If true, the animation associated with the ScrollTrigger will have its invalidate() method called whenever a refresh() occurs (typically on resize). This flushes out any internally-recorded starting values.


You also mentioned the snap value breaking - I think this may just be because you're using snapTo instead of snap. snapTo is an option available inside the snap object

 

snap Object - snap: {snapTo: "labels", duration: 0.3, delay: 0.1, ease: "power1.inOut"}

 

You can use an onUpdate or onRefresh callback inside your scrollTrigger too - it's not a react solution for your last question but maybe it helps?

 

Hope this pen helps to clear things up a little.

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

  • Like 1
Link to comment
Share on other sites

Yep, Cassie's right (I believe). Also, if you want to run a function every time ScrollTrigger refreshes, you can do: 

ScrollTrigger.addEventListener("refresh", yourFunction);

If you want to run a function after a particular ScrollTrigger instance refreshes, use the onRefresh technique Cassie showed. 

 

As for snapping, I assume your myFunction is the actual one that you want to handle the snapping, right? It doesn't return a function that does the snapping? If that assumption is correct, then here's your problem: 

// BAD
snap: myFunction() // INVOKES function, assigns snap to whatever is returned

// GOOD
snap: myFunction

I hope that helps. 

  • Like 1
Link to comment
Share on other sites

Thank you for your quick replies and help! 

I used the snapTo inside the snap object, but wrote it wrong here because I deleted some unnecessary code and missed that.

Setting invalidateOnRefresh to true inside the scrolltrigger didn't help. My start arrow function updates automatically every time the scrolltrigger refreshes, but doing the same for snapTo wont work. @GreenSock myFunction returns an array. So setting snapTo: myFunction() works like it should (like @Cassie demonstrated in the sandbox), but I can't find a way to make it refresh its array when the scrollTrigger refreshes (Taking Cassie's example; change the random snap value every time the scrollTrigger refreshes).

Link to comment
Share on other sites

17 minutes ago, Sacha said:

myFunction returns an array. So setting snapTo: myFunction() works like it should (like @Cassie demonstrated in the sandbox)

No it doesn't. That function is returning a NUMBER (a randomly-picked one from the Array). So your code is effectively doing something like this: 

snap: 0.3

Which tells it to snap to the closest increment of 0.3 in this example.

 

7 minutes ago, Sacha said:

Setting invalidateOnRefresh to true inside the scrolltrigger didn't help.

That's just for refreshing the TWEEN-related values on refresh. If you're trying to use dynamically-changing snap values then you should use a function-based one, like: 

let snapper;

function refreshSnapper() {
  // refresh your snapping function here...
  snapper = gsap.utils.random([0, 0.2, 0.5, 0.8, 1], true);
}

gsap.to(..., 
  scrollTrigger: {
    ...,
    onRefresh: refreshSnapper,
    snap: (value) => snapper(value) // return a randomly-selected value from the Array every time snapping occurs
  }
});

Is that what you're looking for? 

  • Like 1
Link to comment
Share on other sites

function myFunction() {

    let heights = [10, 6];
    let totalHeight = heights[0] + heights[1];
    let snapValues = [];

    let prev = 0;
    snapValues = heights.map((height, i) => {  
        let current = (prev + height / 2) / totalHeight;
        prev += height;
        return current;
    });
    return snapValues;
  }

Hmm okay, I'm not sure if you didn't understand what I meant or I don't what you mean.

Here's my example for the myFunction function. This would return an array of [5, 13], so I'd assume that this is what happens. Correct me if I'm wrong.

snapTo: myFunction()
// would be
snapTo: [5, 13]

 

I don't understand you code suggestion

snap: (value) => snapper(value)

Shouldn't snapper be a function here?

But no matter the code, I'm still having trouble using an arrow function inside snap

No matter if I do something simple like using an actual array for the snap value, it still jumps back to the top of the page as soon as I enter the scrollTrigger element..

let someArray = [1,2,3,4]
snap: () => someArray

 

Link to comment
Share on other sites

Just jumping in!

The progress values should be between 0 and 1 so [1,2,3,4] wouldn't be valid.

 

Have you tried something like [0, 0.25, 0.5, 0.75, 1]

Number | Array | Function | Object | "labels" | "labelsDirectional" - Allows you to snap to certain progress values (between 0 and 1) after the user stops scrolling. So snap: 0.1 would snap in increments of 0.1 (10%, 20%, 30%, etc.). snap: [0, 0.1, 0.5, 0.8, 1] would only let it come to rest on one of those specific progress values. It can be any of the following:

Link to comment
Share on other sites

1 hour ago, Sacha said:

Hmm okay, I'm not sure if you didn't understand what I meant or I don't what you mean.

Here's my example for the myFunction function. This would return an array of [5, 13], so I'd assume that this is what happens. Correct me if I'm wrong.

 

I was referencing the function Cassie had in her demo - that would return a number. You never shared your function so I assumed it was similar to what Cassie showed. Now that you shared your function, yes, I see that it returns an Array. And the strategy I shared with you still applies: 

 

function myFunction() {
    let heights = [10, 6];
    let totalHeight = heights[0] + heights[1];
    let snapValues = [];
    let prev = 0;
    snapValues = heights.map((height, i) => {  
        let current = (prev + height / 2) / totalHeight;
        prev += height;
        return current;
    });
    return snapValues;
}

let snapper;

function refreshSnapper() {
  // refresh your snapping function here...
  snapper = gsap.utils.snap(myFunction());
}

gsap.to(..., 
  scrollTrigger: {
    ...,
    onRefresh: refreshSnapper,
    snap: (value) => snapper(value)
  }
});

Your function-based snap should handle the actual snapping and return a number, not an Array. 

 

If you're still having trouble, please provide a minimal demo with only the absolutely essential code to reproducing the snapping issue. 

Link to comment
Share on other sites

Thank you, I now understood your function. Sorry for the misunderstanding. 

Is there a way to update the duration on a timeline in a similar way? Here would be an example. The snap values from myFunction are the two calculated vertical centers of different sized sections inside an element. So the element is the parent, with two child sections, each with a different height. The first value from myFunction is the center of the first section, the second one the center of the second (based on the total height of the parent element) .

Here I need to achieve the same thing. So if scrollTrigger refreshes (which changes the snapTo values), the duration (which is based on the snap values) should also be updated. Is this possible?

let snapValues = myFunction(); // returns [0.2, 0.7] as example
let durationFirstHalf = snapValues[0]; // returns 0.2
let durationSecondHalf = snapValues[1] - 2 * snapValues[0]; // returns 0.3

// so the first section would have a total relative height of 0.4, the second one 0.6.

.to(firstTarget.current, {
        scaleY: 2,
        duration: durationFirstHalf,
    }, 'start1')
	// first animation goes from 0 to 0.2
    .to(firstTarget.current, {
        scaleY: 1,
        duration: durationFirstHalf,
    }, 'end1')
	// first animation goes from 0.2 to 0.4
    .to(secondTarget.current, {
        scaleY: 2,
        duration: durationSecondHalf,
    }, 'start2')
	// second animation goes from 0.4 to 0.7
    .to(secondTarget.current, {
        scaleY: 1,
        duration: durationSecondHalf,
    }, 'end2')
	// second animation goes from 0.7 to 1

 

Link to comment
Share on other sites

If you're asking whether or not you can change the duration of a timeline after creation, no, not really because a timeline's duration is completely dependent on its children. Think of it like a wrapper. If its duration is 2 seconds and then you append a 1-second animation, the new duration will be 3 seconds. If you remove a child, it might change the duration too. Again, it's just a wrapper. When you set the duration() of a timeline via the setter method, all it does is alter the timeScale accordingly but that's just a convenience. 

 

But you CAN change the duration of individual tweens (inside the timeline or wherever). 

 

If you're totally changing everything in that timeline whenever there's a resize, it may be best to just .kill() it and re-create the instance(s) from scratch. 

Link to comment
Share on other sites

Is it neither possible if the timeline is used in a scrollTrigger? Because here the duration doesn't really declare a time in seconds, but more a fraction of the timeline, which itself keeps its total duration. So changing the duration wouldn't affect the whole timeline's constant duration. (Just speculative thoughts about the timeline inside a scrollTrigger, I have no idea how it actually works, but I hope you understand my way of thinking.)

 

1 hour ago, GreenSock said:

But you CAN change the duration of individual tweens (inside the timeline or wherever). 

tl1.current
    .to(ref.current, {
        scaleY: 3,
        duration: values[0],
    }, 'start1')
    .to(ref2.current, {
        scaleY: 1,
        duration: values[1],
    }, 'start1')

Are these two individual tweens inside a timeline? Or what do you mean by that?

Link to comment
Share on other sites

Sure, you should be able to dynamically update things inside of the timeline - the ScrollTrigger simply scrubs the progress. 

 

24 minutes ago, Sacha said:
tl1.current
    .to(ref.current, {
        scaleY: 3,
        duration: values[0],
    }, 'start1')
    .to(ref2.current, {
        scaleY: 1,
        duration: values[1],
    }, 'start1')

Are these two individual tweens inside a timeline? Or what do you mean by that?

Yep, those are two tweens getting inserted into a timeline. Correct. 

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