Jump to content
Search Community

Timeline pause and resume based on pinned container progress

Nick.Ls test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

Greetings magnificent forum!

 

Firstly, let me explain my setup. I have a container that is pinning for a large amount of time. During the pinning of that section I have a secondary (paused) timeline where I have created the total sequence I would like to be animated in parts, breaking each part with an .addPause(). The pinned timeline's onUpdate function is calling out the play(), reverse() functions in the secondary timeline based on its progress.

What I trying to achieve is to create a timeline that will play / pause / resume and reverse based on my scrolling direction without overlapping or breaking the animation.

 

My code as follows:

let sec1Step_2_skipper = false;
let sec1Step_3_skipper = false;
let sec1Step_4_skipper = false;
let sec1Step_5_skipper = false;
let sec1Step_6_skipper = false;

// STEP #1
let sec1Step_1 = gsap.timeline({
    scrollTrigger: {
        trigger: "#section1",
        start: "top top+=1%",
    },
    ease: "none",
});

sec1Step_1
.from(sec1BG, { duration: 1, scale: 1.1 })
.to(overlay_1, { duration: 1, yPercent: -100 }, 0.25)
.to(splitter_1, { duration: 1.5, "--translate-positive": "0%", "--translate-negative": "0%" }, 0)
.to(splitterText_1, { duration: 1.5, clipPath: "inset(0% 0%)" }, 1.4)
.to(splitter_1, { duration: 1, "--left-positive": "110%", "--left-negative": "-10%"}, 1.5)
.from(iraklisLogo, { duration: 1, opacity: 0, yPercent: 110 }, 2.5)
.to(".mouse", { duration: 1, opacity: 1 });

// STEP #2
let sec1Step_2 = gsap.timeline({
    paused: true,
    ease: "none",
});

sec1Step_2
.addLabel("label2")
.to(iraklisLogo, { duration: 0.5, opacity: 0, yPercent: 110 })
.to(".mouse", { duration: 0.5, autoAlpha: 0 }, "<")
.to(splitterText_1, { duration: 0.5, clipPath: "inset(0 100%)" })
.to(splitter_1, { duration: 0.5, "--left-positive": "50%", "--left-negative": "50%" }, "<")
.to(splitter_1, { duration: 1.5, "--left-positive": "110%", "--left-negative": "-10%"})
.to(splitterText_2, { duration: 1.45, clipPath: "inset(0% 0%)" }, "<")
.addPause()
.addLabel("label3")
.to(splitterText_2, { duration: 0.5, clipPath: "inset(0 100%)" })
.to(splitter_1, { duration: 0.5, "--left-positive": "50%", "--left-negative": "50%" }, "<")
.to(splitter_1, { duration: 0.5, opacity: 0 })
.set("#section1 .spacer", { display: "block" })
.addPause()
.addLabel("label4")
.to(splitter_2, { duration: 0.5, autoAlpha: 1 })
.to(splitter_2, { duration: 1.5, "--translate-positive": "0%", "--translate-negative": "0%" }, "<")
.to(splitter_2, { duration: 1.5, "--left-positive": "110%", "--left-negative": "-10%"})
.to(splitterText_3, { duration: 1.5, clipPath: "inset(0% 0%)" }, "<")
.addPause()
.addLabel("label5")
.to(splitter_2, { duration: 0.5, "--left-positive": "50%", "--left-negative": "50%" })
.to(splitterText_3, { duration: 0.45, clipPath: "inset(0 100%)" }, "<")
.to(splitter_2, { duration: 1.5, "--left-positive": "110%", "--left-negative": "-10%"})
.to(splitterText_4, { duration: 1.45, clipPath: "inset(0% 0%)" }, "<")
.addPause()
.addLabel("label6")
.to(splitter_2, { duration: 0.5, "--left-positive": "50%", "--left-negative": "50%" })
.to(splitterText_4, { duration: 0.45, clipPath: "inset(0 100%)" }, "<")
.to(splitter_2, { duration: 0.5, "--splitter-opacity": 0, "--splitter-visibility": "hidden" });

// PINNING TL
let steps = [
    { start: 0.15, end: 0.30, label: "label2", skipper: sec1Step_2_skipper },
    { start: 0.30, end: 0.35, label: "label3", skipper: sec1Step_3_skipper },
    { start: 0.35, end: 0.50, label: "label4", skipper: sec1Step_4_skipper },
    { start: 0.50, end: 0.80, label: "label5", skipper: sec1Step_5_skipper },
    { start: 0.80, end: 0.87, label: "label6", skipper: sec1Step_6_skipper },
];

let sec1Pin = gsap.timeline({
    scrollTrigger: {
        trigger: "#section1",
        start: "top+=5% top+=1%",
        end: "+=500%",
        pin: true,
        anticipatePin: 1,
        scrub: 2,
        fastScrollEnd: true,
        onUpdate: (self) => {
            const progress = self.progress.toFixed(2);
            let direction = self.direction;

            for (let step of steps) {
                if (progress >= step.start && progress <= step.end) {
                    if (direction === 1 && !step.skipper && !sec1Step_2.isActive()) {
                        sec1Step_2.pause();
                        sec1Step_2.play(step.label);
                        step.skipper = true;
                    } else if (direction === -1 && step.skipper && sec1Step_2.isActive()) {
                        sec1Step_2.pause();
                        sec1Step_2.seek(step.label);
                        sec1Step_2.reverse();
                        step.skipper = false;
                    }
                }
            }
        },
    },
    ease: "none",
});

sec1Pin
// STEP #1
.fromTo(sec1BG, { rotate: 90, scale: 2 }, { rotate: 0, scale: 2.4 })
// STEP #2
.to(sec1BG, { scale: 1 }, "+=25%")
// STEP #3
.to(sec1BG, { clipPath: "circle(12% at 50% 50%)" }, "60%")
.to(".shape.shape-g", { x: "0%", scale: 3.5}, "60%")
.to(".shape.shape-p", { x: "0%", scale: 3.5}, "60%")
// STEP #4
.to(sec1BG, { clipPath: "circle(0% at 50% 50%)" }, "78%")
.to(".shape.shape-g", { x: "-50%", scale: 1}, "78%")
.to(".shape.shape-p", { x: "-50%", scale: 1}, "78%")
// STEP #5
.to(".shape.shape-g", { x: "-40%", y: "-60%" }, "90%")
.to(".shape.shape-p", { x: "-65%", y: "-39%" }, "90%")
.to("#section1 .logoText", { opacity: 1, x: "0%" }, "90.1%")
.to(".shape.shape-g", { x: "-80%", y: "-60%" }, "90.1%")
.to(".shape.shape-p", { x: "-105%", y: "-39%" }, "90.1%")
// STEP #6
.to("#section1 .logoText #logo_S", { fill: gsap.getProperty("html", "--color-green") }, "91%")
.to("#section1 .logoText #logo_P", { fill: gsap.getProperty("html", "--color-green") }, "91%")
.to("#section1 .logoText #logo_T", { fill: gsap.getProperty("html", "--color-green") }, "91%")
// STEP #7
.to("#section1 .text-gradient", { clipPath: "inset(0% 0% 0% 0%)" }, "91%")

At this point is working mostly as intended with the difference that if I fast scroll forwards the timeline will pause or break at some point without completing.

 

Also, I've tried to create multiple timelines and call each one of them based on pin progress but the same problem occurs more or less...

 

Any suggestions on how to manipulate a long timeline like this?

Thank you!

See the Pen mdvvqeJ by Nick_Ls (@Nick_Ls) on CodePen

Edited by Nick.Ls
Codepen Added
Link to comment
Share on other sites

It's pretty tough to troubleshoot without a minimal demo - the issue could be caused by CSS, markup, a third party library, your browser, an external script that's totally unrelated to GSAP, etc. Would you please provide a very simple CodePen or Stackblitz that demonstrates the issue? 

 

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. If not, incrementally add code bit by bit until it breaks. Usually people solve their own issues during this process! If not, then at least we have a reduced test case which greatly increases your chances of getting a relevant answer.

 

Here's a starter CodePen that loads all the plugins. Just click "fork" at the bottom right and make your minimal demo

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

 

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. 

 

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

Hi @Nick.Ls now that I see your code in action, are you sure you're looking for ScrollTrigger? ScrollTrigger is build for adding animations to the scrollbar of the current page. It seems like you just want to watch for the scroll event of the vistor and then do some logic based on that, this sounds like the perfect use case for the Observer plugin https://gsap.com/docs/v3/Plugins/Observer/

 

Check out this demo, it will do an animation when you scroll and it locks the animation until it is done.

 

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

 

I see you're using labels, I had made an example for another user on the forum that uses the same logic as above, but that tweens to next and previous labels. 

 

See the Pen GRzrPPy by mvaneijgen (@mvaneijgen) on CodePen

 

And I think you're going to ask this next, here is a demo that mixes the Observer plugin with normal scrolling. 

 

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

 

Is this what you're looking for? 

  • Like 1
Link to comment
Share on other sites

Looks like observer is great for repeatable sections but in my case basically what I am trying to achieve is to create scrolltriggers of different timelines inside

the pinned scrolltrigger.

 

The scenario is I have a pinned section where a scrub timeline animates through scrolling whilst a bunch of other timelines need to be animated at specific positions of the pinned section's progress.

 

I hope I gave you a better view of my total sequence and what I try to achieve.

 

Any other suggestions are welcome!

Link to comment
Share on other sites

Is it similar to this topic? 

 

It has one timeline that tweens to the next label based on multiple ScrollTrigger onEnter and it used .tweenTo() https://gsap.com/docs/v3/GSAP/Timeline/tweenTo()/ with which you get onComplete callbacks, so that you can stop certain things if something else is still animating. 

7 hours ago, Nick.Ls said:

What I trying to achieve is to create a timeline that will play / pause / resume and reverse based on my scrolling direction without overlapping or breaking the animation.

This will be the most difficult part, because you have to do all the checks yourself and make sure that when something is animating you don't overlap the next animation, but you already get more control if you use tweenTo() of tweenFromTo()

 

See the Pen wvNNxwW by mvaneijgen (@mvaneijgen) on CodePen

Link to comment
Share on other sites

Yeah kind it is @mvaneijgen

 

Either creating scrolltriggers withing the pinned scrolltrigger or timelines that will trigger based of the progress onUpdate() of the pinned scrolltrigger, there has to be a manual calculation of the timings and the entry / exit points. PreventOveralps and fastscrollEnd is also a must eitherwise animation will still stuck up on each other and break.

I am still trying to figure it out... if I find a solution I'll let you know...

Link to comment
Share on other sites

Codepen updated with solution (as it seems so far).

 

It looks like the additional scrolltriggers that I want to autoplay can be triggered (with lots of manual calculation of percentages), using the

pinnedContainer: true option with PreventOveralps and fastscrollEnd to prevent overlapping of animations (in a most complicated scenario of course).

The issue I am facing now is that enabling scrollsmoother the scrolltriggers don't fire. Any ideas why?

Link to comment
Share on other sites

Hello forum!

 

I can't see any configuration at this point that can work for my setup... Can't find any set of options that won't break the scrolltriggers once the scrollsmoother is enabled?

 

Any suggestions? Why do scrolltriggers lose their trigger position with scrollsmoother enabled?

Link to comment
Share on other sites

  • Solution

I noticed several problems with your setup: 

  1. You were creating the ScrollTriggers in the wrong order. It should always go top-to-bottom, or you can use refreshPriority to control it. 
  2. You set a pinnedContainer to the same element that's being pinned - an element can't be its own descendent. There's no need for pinnedContainer. 
  3. There's no such thing as setting an "ease" on a timeline's vars object.
  4. There's no onEnter callback on a timeline. I think you meant onStart?
  5. You set the start and end positions to be identical on one of your ScrollTriggers, so it literally takes up no space whatsoever. I'd recommend that it take up at least 1 pixel so that there can be a clear distinction between onEnter, onLeave, onEnterBack, and onLeaveBack. 
  6. You didn't have ScrollSmoother set up nor the proper markup (wrapper and content <div> elements)

Are you looking for something more like this?: 

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

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