Jump to content
Search Community

Combining scrubbed and non-scrubbed ScrollTrigger animations

NickPish test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

Hi, in my current project I'm using ScrollTrigger to create sliding panels that include elements that animate automatically in a timeline when they enter the viewport, as well as those that are controlled by scrubbing. If you take a look at my demo CodePen, you'll see that I've created two separate ScrollTrigger instances w/ corresponding timelines to handle these different animations: the first simply plays out a succession of elements in a (non-scrubbed) timeline, and the second allows the user to reveal another text element, an overlay and finally a slide-out blockquote controlled by scrolling (i.e. via scrubbing.) While what I've set up works, it feels overly complex/verbose (i.e. w/ separate timelines and ScrollTriggers) and I'm wondering if there's a way to combine these behaviors in a more efficient way, say, in the form of a single timeline or some other method?

 

Essentially, I just want to have certain elements animate automatically upon entering the viewport (such as the growing SVG circle and lead-in header), and others that require scrolling to progress, such as the additional text block and slide-out blockquote. I'm relatively new to GSAP/ScrollTrigger, so would appreciate any suggestions on how to make this code more efficient- thank you!

See the Pen PoRwEeJ by nickpish (@nickpish) on CodePen

Link to comment
Share on other sites

Hey, if it works it works. :)

 

I don't think there's anything "wrong" with the approach you described. You could use a scrubbed timeline for everything, but just embed callbacks at certain places on that timeline that fire off non-scrubbed animations (in the callback). But I don't really think that'd be "easier" or necessarily "better". Just a different approach. A lot of this comes down to building things in the way that makes sense in the author's brain. 

 

Happy tweening/scrolling!

Link to comment
Share on other sites

Thank you for the reply, Jack! That's interesting re the callback approach- so would this take the form of a separate function that plays out the non-scrubbed portion of the timeline? That could be useful as I plan to have several panels like this one on a given page, so that might modularize things a bit.

Link to comment
Share on other sites

  • Solution
4 hours ago, NickPish said:

so would this take the form of a separate function that plays out the non-scrubbed portion of the timeline?

Think of it sorta like this:

let tl = gsap.timeline({ scrollTrigger: {scrub: true, ...}});
tl.to(...);
tl.add(() => {
  gsap.to(...); // non-scrubbed. Just a regular animation that's triggered at a certain spot in the timeline
});
tl.to(...);
tl.add(() => {
  //maybe you need to go to different values if it's going backwards...
  if (tl.scrollTrigger.direction === -1) {
    gsap.to(...); // backwards
  } else {
    gsap.to(...); // forwards
  }
});

I hope that helps.

  • Like 1
Link to comment
Share on other sites

Thanks again, Jack- so, I've updated my CodePen using your structure and I'm wondering if you could take a look. I'm getting closer, but it's not working quite right. I essentially just want the circle to expand and lead-in text appear in a non-scrubbed fashion, and then the rest to be controlled by scrubbing. There seem to be two issues w/ my current setup: first, if you scroll very quickly the elements will overlap and, secondly, on scrolling through the entire animation, back up and down again, I'd need the non-scrubbed elements to re-animate rather than persist (the two text elements also sometimes overlap when scrolling back and forth) - is this feasible? Thanks for your help!

 

Link to comment
Share on other sites

We love helping with GSAP-related questions, but unfortunately we just don't have the resources to provide free general consulting, logic troubleshooting, or "here are my project requirements: ____, ____, ____. Please show me how to do it" tutorials. Of course anyone else is welcome to post an answer if they'd like - we just want to manage expectations.  

 

I will quickly mention a few problems I noticed in your CodePen: 

 

  1. You've got logic issues because you've got a scrubbed timeline that's controlling "#timeline-02 .text-1" opacity and you've ALSO got a non-scrubbed animation that's simultaneously animating it to a totally different value. That can't work logically. 
  2. You didn't add any logic in there to handle directionality. For example, if you scroll backward past the callback, it sounds like you want it to animate back to the start. The callback is getting triggered, but your code is always animating to the end values. You could just create a paused timeline outside of the callback and then just play()/reverse() that based on the tl.scrollTrigger.direction, as I explained in my previous post. 
  3. You've got a function-based value in one of your tweens, but you forgot to set invalidateOnRefresh: true on your ScrollTrigger, so that won't get refreshed on every resize. 
  4. I would encourage you to look into the position parameter. I noticed you're using empty spacer tweens like .to({}, {duration: 1}) which is fine, but there's really no need - you can just use the position parameter on the following animation to place it 1 second later, "+=1". 

 

If you need consulting services beyond GSAP-specific questions, you are welcome to post in the "Jobs & Freelance" forum for paid consulting. 

 

Good luck!

Link to comment
Share on other sites

Thanks so much, Jack- I realize you can't dole out limitless support here, so I very much appreciate the feedback. I will look into everything you've noted. I'm assuming for that function-based value you mentioned in one of the tweens, you're referring to the following portion?

 

.to(hscroll2, { x: () => -(hscroll2.scrollWidth - document.documentElement.clientWidth) + "px" }

I will look into that invalidateOnRefresh property.

Link to comment
Share on other sites

8 hours ago, NickPish said:

I'm assuming for that function-based value you mentioned in one of the tweens, you're referring to the following portion?

 

.to(hscroll2, { x: () => -(hscroll2.scrollWidth - document.documentElement.clientWidth) + "px" }

Exactamundo. 👍

 

(by the way, you don't need to append "px" because that's the default)

  • Like 1
Link to comment
Share on other sites

  • 3 months later...

 

On 7/1/2022 at 8:58 PM, GreenSock said:

Think of it sorta like this:

let tl = gsap.timeline({ scrollTrigger: {scrub: true, ...}});
tl.to(...);
tl.add(() => {
  gsap.to(...); // non-scrubbed. Just a regular animation that's triggered at a certain spot in the timeline
});
tl.to(...);
tl.add(() => {
  //maybe you need to go to different values if it's going backwards...
  if (tl.scrollTrigger.direction === -1) {
    gsap.to(...); // backwards
  } else {
    gsap.to(...); // forwards
  }
});

I hope that helps.

Sorry to hijack this thread, but I tried doing this example. However, it seems like the tl.scrollTrigger.direction is only triggering on the way down. I first thought it had to do with the point in the timeline not being detected on the way up when placing it at the end of the timeline. But placing it in the beginning or in the middle doesn't seem to make any difference. If I log tl.scrollTrigger.direction as part of the add() callback, it only outputs a value when scrolling downwards.

Any pitfall I've missed?

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