Jump to content
Search Community

Trigger animation by clicking button and reverse by scrolling up

katling test
Moderator Tag

Recommended Posts

Hi,

I'm quite frustrated, and I might did something wrong with my whole code.. I am trying to make a scrollytelling project, and I wanted to trigger certain animations by clicking a button, and make all the other elements of the rest of the website appear on the bottom part of the whole page, depending on which button got clicked (2 buttons, you can select from)

My problem here is, that you click the button, the whole thing doesn't fire directly

1. you need to be at the very end of the document, which makes sense to me, but the bigger issue here:,

2. if you wait more than 3 sec before you click on one of the buttons, the whole thing doesn't animate, it just appears.

3. I thought, I could make the other elements appear, by clicking the button, so by scrolling it behaves like the things before. Instead, it just fires everything at once. Or animate it without the need of scrolling.

 

I tried to use only gsap.to();, but the problem here is, that it doesn't revert by scrubbing.

 

Also by defining the ScrollTrigger with the scrubbing, it doesn't make any difference, which value I use and the markers don't show up either..

 

 

See the Pen yLrMoXo by Katling (@Katling) on CodePen

Link to comment
Share on other sites

Hi @katling and welcome to the GSAP Forums!

 

Sorry to hear about the frustrations, can relate that is no fun at all

 

I believe the issues here are mostly logic-related ones. You have a timeline that is controlled by ScrollTrigger and that is scrubbed, that being said the playhead of the timeline is controlled by the progress of the ScrollTrigger instance based on the start and end points you pass to ScrollTrigger's configuration. Then in your buttons you have this:

function selectEgg() {
  tl2
    .fromTo("#menu-card", { x: "25vw" }, { x: "150vw" })
    .fromTo(".menu-button", { opacity: 1 }, { opacity: 0 })
    .fromTo("#menu-msg", { opacity: 1 }, { opacity: 0 })
    .to("#menu-card", { display: "none" })
    .to("#choose-menu", { display: "none" })
    .fromTo(
      "#egg-dish",
      { display: "none", x: "100vw" },
      { display: "block", x: "25vw" }
    )
    .to("#egg-info", { display: "flex", opacity: 1 });
}

function selectFish() {
  tl2
    .fromTo("#menu-card", { x: "25vw" }, { x: "150vw" })
    .fromTo(".menu-button", { opacity: 1 }, { opacity: 0 })
    .fromTo("#menu-msg", { opacity: 1 }, { opacity: 0 })
    .to("#menu-card", { display: "none" })
    .to("#choose-menu", { display: "none" })
    .fromTo(
      "#fish-dish",
      { x: "100vw", delay: 5 },
      { display: "block", x: "25vw" }
    )
    .to("#fish-info", { display: "flex", opacity: 1 });
}
  1. You are adding instances to the SAME timeline that is being controlled by ScrollTrigger, even further you're adding them once you have scrolled past the end trigger of the ScrollTrigger config (because your buttons are visible after that particular point). This spells trouble to me because you're extending a timeline whose playhead is already at the end or being tweened there (because you have a numeric value in your scrub config and a large one as well - more on that later). That of course is going to generate a jump and some erratic behaviour and you have to add to it the fact that you are using fromTo instances, which will change the current value of the property you're animating immediately to the value in the from configuration object and then tween it to the value in the to configuration object.
  2. You are animating the same elements that are being animated by the timeline controlled by ScrollTrigger with a scrub value in it. If for any reason the user scrolls while those animations are running, things can get really messed up because you'll have two instances battling for control over the same elements. Definitely don't do that.
  3. You have a very large value for scrub (10) that means that it takes 10 seconds for the timeline to catch up with the scroll position and the progress of the ScrollTrigger instance. Any particular reason for a value that big?

 

The first thing you should do is avoid 1 and 2. Create completely different animations and don't animate the same elements that are being animated with the timeline that is controlled with ScrollTrigger.

 

If you keep having issues, please change your demo so instead of images that are not loaded, use just colored divs to show what you're trying to do.

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

13 hours ago, Rodrigo said:

Hi @katling and welcome to the GSAP Forums!

 

Sorry to hear about the frustrations, can relate that is no fun at all

 

I believe the issues here are mostly logic-related ones. You have a timeline that is controlled by ScrollTrigger and that is scrubbed, that being said the playhead of the timeline is controlled by the progress of the ScrollTrigger instance based on the start and end points you pass to ScrollTrigger's configuration. Then in your buttons you have this:

function selectEgg() {
  tl2
    .fromTo("#menu-card", { x: "25vw" }, { x: "150vw" })
    .fromTo(".menu-button", { opacity: 1 }, { opacity: 0 })
    .fromTo("#menu-msg", { opacity: 1 }, { opacity: 0 })
    .to("#menu-card", { display: "none" })
    .to("#choose-menu", { display: "none" })
    .fromTo(
      "#egg-dish",
      { display: "none", x: "100vw" },
      { display: "block", x: "25vw" }
    )
    .to("#egg-info", { display: "flex", opacity: 1 });
}

function selectFish() {
  tl2
    .fromTo("#menu-card", { x: "25vw" }, { x: "150vw" })
    .fromTo(".menu-button", { opacity: 1 }, { opacity: 0 })
    .fromTo("#menu-msg", { opacity: 1 }, { opacity: 0 })
    .to("#menu-card", { display: "none" })
    .to("#choose-menu", { display: "none" })
    .fromTo(
      "#fish-dish",
      { x: "100vw", delay: 5 },
      { display: "block", x: "25vw" }
    )
    .to("#fish-info", { display: "flex", opacity: 1 });
}
  1. You are adding instances to the SAME timeline that is being controlled by ScrollTrigger, even further you're adding them once you have scrolled past the end trigger of the ScrollTrigger config (because your buttons are visible after that particular point). This spells trouble to me because you're extending a timeline whose playhead is already at the end or being tweened there (because you have a numeric value in your scrub config and a large one as well - more on that later). That of course is going to generate a jump and some erratic behaviour and you have to add to it the fact that you are using fromTo instances, which will change the current value of the property you're animating immediately to the value in the from configuration object and then tween it to the value in the to configuration object.
  2. You are animating the same elements that are being animated by the timeline controlled by ScrollTrigger with a scrub value in it. If for any reason the user scrolls while those animations are running, things can get really messed up because you'll have two instances battling for control over the same elements. Definitely don't do that.
  3. You have a very large value for scrub (10) that means that it takes 10 seconds for the timeline to catch up with the scroll position and the progress of the ScrollTrigger instance. Any particular reason for a value that big?

 

The first thing you should do is avoid 1 and 2. Create completely different animations and don't animate the same elements that are being animated with the timeline that is controlled with ScrollTrigger.

 

If you keep having issues, please change your demo so instead of images that are not loaded, use just colored divs to show what you're trying to do.

 

Hopefully this helps.

Happy Tweening!

Hello @Rodrigo,

 

thank you very much for the fast response! I'm quite a beginner with programming and GSAP in general, so sorry for asking again.

 

I didn't know that, I shouldn't add all the animations in one timeline. So as I understood your explaination correctly, I should just make a new animation (not timeline?) for every animation, right? So then I need to create always a new scrollTrigger, and and make it reverse if it comes to toggleActions, so that it plays backwards when scrolled back up again?

I thought I need to create a timeline, since I pinned everything to a specific background and wanted to have the scrubbing.

 

For the scrubbing I took a high value, because it didn't seem to change anything anyway, but maybe I just missed something out (like the markers, that don't show for some reason).

 

I changed the demo code, and made everything colored.

 

 

 

 

 

Link to comment
Share on other sites

No, is totally fine to keep everything in a single timeline if you're carefully. In this particular case is better to move some animations out of the timeline. Is not a rule carved in stone is a case-by-case thing. Every project has it's own peculiarities and you should plan and develop them as you go based on the requirements of the project.

 

In this case you can keep your timeline with the scrub in it just, instead of adding the menu part at the end, remove it and make that a different timeline, then you can use the onLeave and onEnterBack callbacks to play/reverse that timeline and then you can also reverse that timeline when clicking the buttons as well in order to hide that menu:

// Timeline just for the menu elements
const menuTimeline = gsap.timeline({
  paused: true,
});
// Add animations to the menu and buttons timeline

const tl2 = gsap.timeline({
  scrollTrigger: {
    // other configs
    scrub: 1,
    // use callbacks to play/reverse the menu timeline
    onLeave: () => menuTimeline.play(),
    onEnterBack: () => menuTimeline.reverse(),
    markers: true // shows the markers
  }
});

// Then in your menu buttons
btn.addEventListener("click", () => {
  menuTimeline.reverse();
  // Then create other animations as well
});

From the ScrollTrigger docs:
 

onLeave
Function - A callback for when the scroll position moves forward past the "end" (typically when the trigger is scrolled out of view).

onEnterBack
Function - A callback for when the scroll position moves backward past the "end" (typically when the trigger is scrolled back into view).

 

Right now I'm on my way out so I can't take a good look to your demo, but hopefully this helps.

Happy Tweening!

  • Like 2
Link to comment
Share on other sites

On 3/21/2024 at 2:08 PM, Rodrigo said:

No, is totally fine to keep everything in a single timeline if you're carefully. In this particular case is better to move some animations out of the timeline. Is not a rule carved in stone is a case-by-case thing. Every project has it's own peculiarities and you should plan and develop them as you go based on the requirements of the project.

 

In this case you can keep your timeline with the scrub in it just, instead of adding the menu part at the end, remove it and make that a different timeline, then you can use the onLeave and onEnterBack callbacks to play/reverse that timeline and then you can also reverse that timeline when clicking the buttons as well in order to hide that menu:

// Timeline just for the menu elements
const menuTimeline = gsap.timeline({
  paused: true,
});
// Add animations to the menu and buttons timeline

const tl2 = gsap.timeline({
  scrollTrigger: {
    // other configs
    scrub: 1,
    // use callbacks to play/reverse the menu timeline
    onLeave: () => menuTimeline.play(),
    onEnterBack: () => menuTimeline.reverse(),
    markers: true // shows the markers
  }
});

// Then in your menu buttons
btn.addEventListener("click", () => {
  menuTimeline.reverse();
  // Then create other animations as well
});

From the ScrollTrigger docs:
 

onLeave
Function - A callback for when the scroll position moves forward past the "end" (typically when the trigger is scrolled out of view).

onEnterBack
Function - A callback for when the scroll position moves backward past the "end" (typically when the trigger is scrolled back into view).

 

Right now I'm on my way out so I can't take a good look to your demo, but hopefully this helps.

Happy Tweening!

Hi @Rodrigo

thanks for the quick response, I tried it out, and it helped a lot! And I understand and reread the most common mistakes again!
Still I have one question left, since I dont know how to fix it.

 

I separated it from the other timeline, but I'd like to make it an animation without the need to scroll, so that it just happens after clicking the button AND if you scroll back it reverses. I tried out to store the animation into a variable, and use the reverse(); function but I dont know how to link it to the scroll back interaction.. since this is linked to the scrollTrigger, which wouldn’t be defined when I would just use gsap.to();.
Also the whole elements of the menutimeline just appears by scrolling down again, which confuses me (in codependent demo it doesn’t show again, just in my browser).
Sorry for asking so much and if making any trouble..
Im really thankful for your support.

I have the code here (button only for the OMELETTE, fishfunction not defined yet)


See the Pen GRLWzrr by Katling (@Katling) on CodePen

 

Link to comment
Share on other sites

Hi,

 

In the SrollTrigger config is markers (plural) not marker:

// Wrong
scrollTrigger: {
  marker: true,
},

// Right
scrollTrigger: {
  markers: true,
},

Also once again you're making the same mistake. Sooner or later this will become a logical issue:

const menuTimeline = gsap.timeline({
  scrollTrigger: {
    trigger: "#pin-container-2",
    pin: "#pin-container-2",
    pinnedContainer: "#pin-container-2",
    start: "top top",
    // YOU HAVE SCRUB HERE
    scrub: true,
    markers: { indent: 140 },
    id: "menu"
  }
});

Then you have this:

function selectEgg() {
  menuTimeline.reverse();
}

function selectFish() {
  menuTimeline.reverse();
}

Your menu timeline's playhead position is controlled by the scroll position when using scrub, then you want to reverse it using a button, which can be done, but as soon as the user scrolls ScrollTrigger will update the same timeline's playhead position based on the scroll position which will make the menu elements visible again and behave in an erratic fashion. You cannot have a scrubbed animation play/reverse using other event handlers unless you remove the ScrollTrigger instance associated with it, in which case you no longer will be able to scrub that GSAP instance.

 

IMHO it should be like this:

const menuTimeline = gsap.timeline({
  scrollTrigger: {
    trigger: "#pin-container-2",
    pin: "#pin-container-2",
    pinnedContainer: "#pin-container-2",
    start: "top top",
    toggleActions: "play none none reverse",
    markers: { indent: 140 },
    id: "menu"
  }
});

Finally I think this is not a good approach as well:

function selectEgg() {
  menuTimeline.reverse();
  const eggTimeline = gsap.timeline({
    scrollTrigger: {
      trigger: "#pin-container-2",
      pin: "#pin-container-2",
      pinnedContainer: "#pin-container-2",
      start: "top top",
      scrub: true,
      markers: { indent: 300 },
      id: "btn"
    }
  });

  eggTimeline
    .to("#besteck-2", { display: "block", x: "-20vw" })
    .to("#egg-dish", { display: "block", x: "-60vw" });
}

I wouldn't create a GSAP instance controlled by ScrollTrigger on an event handler. What happens if the user opens the menu and clicks on that button again? The same instance will be created again, but the previous one is already there and the same element is already pinned.

 

Finally I think you are overcomplicating this quite a bit and because of that your app has several logic and architectural issues. I think the best approach right now is to decide what exactly you want to do and start by creating an HTML and CSS structure that looks the way you want. Then start adding animations one by one, go section by section and then also start adding complexity to this. It seems to me that you are a bit over your head right now, so that's why going back to basics should be a solid first step.

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

14 hours ago, Rodrigo said:

Hi,

 

In the SrollTrigger config is markers (plural) not marker:

// Wrong
scrollTrigger: {
  marker: true,
},

// Right
scrollTrigger: {
  markers: true,
},

Also once again you're making the same mistake. Sooner or later this will become a logical issue:

const menuTimeline = gsap.timeline({
  scrollTrigger: {
    trigger: "#pin-container-2",
    pin: "#pin-container-2",
    pinnedContainer: "#pin-container-2",
    start: "top top",
    // YOU HAVE SCRUB HERE
    scrub: true,
    markers: { indent: 140 },
    id: "menu"
  }
});

Then you have this:

function selectEgg() {
  menuTimeline.reverse();
}

function selectFish() {
  menuTimeline.reverse();
}

Your menu timeline's playhead position is controlled by the scroll position when using scrub, then you want to reverse it using a button, which can be done, but as soon as the user scrolls ScrollTrigger will update the same timeline's playhead position based on the scroll position which will make the menu elements visible again and behave in an erratic fashion. You cannot have a scrubbed animation play/reverse using other event handlers unless you remove the ScrollTrigger instance associated with it, in which case you no longer will be able to scrub that GSAP instance.

 

IMHO it should be like this:

const menuTimeline = gsap.timeline({
  scrollTrigger: {
    trigger: "#pin-container-2",
    pin: "#pin-container-2",
    pinnedContainer: "#pin-container-2",
    start: "top top",
    toggleActions: "play none none reverse",
    markers: { indent: 140 },
    id: "menu"
  }
});

Finally I think this is not a good approach as well:

function selectEgg() {
  menuTimeline.reverse();
  const eggTimeline = gsap.timeline({
    scrollTrigger: {
      trigger: "#pin-container-2",
      pin: "#pin-container-2",
      pinnedContainer: "#pin-container-2",
      start: "top top",
      scrub: true,
      markers: { indent: 300 },
      id: "btn"
    }
  });

  eggTimeline
    .to("#besteck-2", { display: "block", x: "-20vw" })
    .to("#egg-dish", { display: "block", x: "-60vw" });
}

I wouldn't create a GSAP instance controlled by ScrollTrigger on an event handler. What happens if the user opens the menu and clicks on that button again? The same instance will be created again, but the previous one is already there and the same element is already pinned.

 

Finally I think you are overcomplicating this quite a bit and because of that your app has several logic and architectural issues. I think the best approach right now is to decide what exactly you want to do and start by creating an HTML and CSS structure that looks the way you want. Then start adding animations one by one, go section by section and then also start adding complexity to this. It seems to me that you are a bit over your head right now, so that's why going back to basics should be a solid first step.

 

Hopefully this helps.

Happy Tweening!

Hi, thank you very much, I will try to restructure everything again.

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