Jump to content
Search Community

GSAP Observer + Scroll Trigger

sarahholden test
Moderator Tag

Recommended Posts

Hello,

 

I'm attempting to recreate an effect from this codepen: 

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

 

My issue is that the section I want to add animations to on swipe is not at the top of the page, but further down. Is it possible to get this same functionality, but to have scrollable content above the section that uses Observer? I've attached a Codepen example - my issue is that when there is scrollable content above this section, it blows right past the section and gets weird. 

 

Designers I work with always want sections in the middle of the page where animations are tied to mouse events. For example, one swipe switches a panel or animates in text. 😅 I was hoping with observer there would be a cleaner way to do this.

 

Thanks in advance, really appreciate all your work answering forum questions. I don't often ask but I benefit from questions from others :)

 

Sarah

See the Pen RwyQeyG by sarahholden (@sarahholden) on CodePen

Edited by sarahholden
Clarify now that I can see where codepen examples get placed in post
Link to comment
Share on other sites

Hi Sarah,

 

This solves a few issues:

// handle the panel swipe animations
function gotoPanel(index, isScrollingDown) {
  animating = true;
  // return to normal scroll if we're at the end or back up to the start
  if (
    (index === swipePanels.length && isScrollingDown) ||
    (index === -1 && !isScrollingDown)
  ) {
    intentObserver.disable();
    animating = false;
    return;
  }

  //   target the second panel, last panel?
  let target = isScrollingDown ? swipePanels[index] : swipePanels[currentIndex];

  gsap.to(target, {
    xPercent: isScrollingDown ? 0 : 100,
    duration: 0.75,
    onComplete: () => {
      animating = false;
    }
  });
  currentIndex = index;
}

// pin swipe section and initiate observer
ScrollTrigger.create({
  trigger: ".swipe-section",
  pin: true,
  anticipatePin: true,
  start: "top top",
  end: "+=50%",
  onEnter: (self) => {
    intentObserver.enable();
    gotoPanel(currentIndex + 1, true);
  },
  onEnterBack: (self) => {
    intentObserver.enable();
    gotoPanel(currentIndex - 1, false);
  }
});

It adds some space to prevent the scroll event to move past the start/end points It's not perfect but hopefully is good enough for now.

 

We'll look into it and keep you posted on what we find about it.

 

Happy Tweening!

  • Like 2
Link to comment
Share on other sites

Welcome to the forums, @sarahholden! Congrats on stepping out and posting your first question (after lurking for a while). 💚

 

From what I can tell, the browser doesn't really honor the request to event.preventDefault() on wheel events when scrolling is in-progress, at least on some devices like my Mac. Very annoying, and I'd argue wildly incorrect behavior, but don't worry - we can work around it: 

 

let preventScroll = ScrollTrigger.observe({
			preventDefault: true,
			type: "wheel,scroll",
			allowClicks: true,
			onEnable: self => self.savedScroll = self.scrollY(), // save the scroll position
			onChangeY: self => self.scrollY(self.savedScroll)    // refuse to scroll
		});
preventScroll.disable();

Basically, we have to save the scroll position and revert it every time it tries to change. 

 

Here's the revised demo: 

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

 

Does that help? 

  • Like 4
Link to comment
Share on other sites

Wow, really appreciate the quick responses @Rodrigo and @GreenSock

 

This solution works perfectly for what I need.  :) 

 

Since I am using ScrollSmoother this morning I had experimented with pausing scroll smoother when that section is reached and then unpausing when the slides have ended, but I'm new to ScrollSmoother so I wasn't sure what unintended consequences that would have. 

 

Your solution seems much more foolproof. Really appreciate your help on this one, Observer is such a nice tool to have! I should be set with this solution now!

 

  • Like 3
Link to comment
Share on other sites

4 minutes ago, sarahholden said:

Since I am using ScrollSmoother this morning I had experimented with pausing scroll smoother when that section is reached and then unpausing when the slides have ended, but I'm new to ScrollSmoother so I wasn't sure what unintended consequences that would have. 

Ssshhh...don't tell anyone but that solution I listed above is basically pulled directly from the guts of ScrollSmoother's pause() method :)

 

If you're getting any odd behavior doing it through ScrollSmoother, let us know but it's totally fine to just use what I provided above. There's no NEED to go through ScrollSmoother that I can think of.

 

Enjoy! 

  • Like 1
Link to comment
Share on other sites

  • 2 months later...
On 9/29/2022 at 9:33 PM, GreenSock said:

 

let preventScroll = ScrollTrigger.observe({
			preventDefault: true,
			type: "wheel,scroll",
			allowClicks: true,
			onEnable: self => self.savedScroll = self.scrollY(), // save the scroll position
			onChangeY: self => self.scrollY(self.savedScroll)    // refuse to scroll
		});
preventScroll.disable();

Basically, we have to save the scroll position and revert it every time it tries to change. 

Quick side question: Is this the "official" way to add a preventDefault to the Observer events? This options seems to be documented nowhere.
What about stopPropagation? Does it work the same?

Link to comment
Share on other sites

Hi,

 

Indeed is not documented but you should rely on it for preventing the default action:

https://github.com/greensock/GSAP/blob/master/src/Observer.js#L147

 

3 hours ago, trych said:

What about stopPropagation? Does it work the same?

Nope, the Observer Plugin callbacks have a single parameter passed to them which is the Observer instance itself. That object contains, among other stuff, an event property which is the actual event, that contains all the prototypes for events, which includes the stopPropagation method:

https://greensock.com/docs/v3/Plugins/Observer/event

 

let preventScroll = ScrollTrigger.observe({
  preventDefault: true,
  type: "wheel,scroll",
  allowClicks: true,
  onEnable: self => self.savedScroll = self.scrollY(), // save the scroll position
  onChangeY: self => {
    self.event.stopPropagation();
    self.scrollY(self.savedScroll);    // refuse to scroll
  }
});

Hopefully this clear things up. Let us know if you have more questions.

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

1 hour ago, Rodrigo said:

Indeed is not documented but you should rely on it for preventing the default action:

https://github.com/greensock/GSAP/blob/master/src/Observer.js#L147

No, it is not documented for a reason. It was mostly intended for internal use. It is not meant to be a guarantee to always call .preventDefault() on all events related to the Observer. For example, onPress won't do it because of specific edge cases like in iOS that would prevent horizontal scrolling when the ScrollTrigger.normalizeScroll() feature is enabled. Same for onRelease.

 

If you only need to preventDefault on wheel events and drag (mousemove/pointermove/touchmove) and click, it's fine to use the built-in feature. Otherwise, I'd recommend calling .preventDefault() on your events wherever you need to. Also beware that by default, debounce is set to true, so strategize accordingly. In other words, you may need to disable debouncing if you want to call .preventDefault() on the event, otherwise the default action may execute before preventDefault() is called. 

  • Like 1
Link to comment
Share on other sites

17 hours ago, GreenSock said:

Otherwise, I'd recommend calling .preventDefault() on your events wherever you need to.

How would I do that? Like this?

  onLeft: self => {
    self.event.preventDefault();
  }

 

17 hours ago, GreenSock said:

Also beware that by default, debounce is set to true, so strategize accordingly. In other words, you may need to disable debouncing if you want to call .preventDefault() on the event, otherwise the default action may execute before preventDefault() is called. 

I'm not quite sure I understand. Is this because the preventDefault would only be part of the debounced action? Would it then make sense for expensive operations to create two Observers? One that only prevents the default and is not debounced and one that executes the actual action and is debounced? Or would that not work?

 

Another question: the `allowClicks` property seems to be another undocumented property. Does this one take care of preventing click events on touch/drag style events? Because that is what I was actually trying to prevent and that might be helpful for me in my current project.
 

Link to comment
Share on other sites

Hi,

3 hours ago, trych said:

How would I do that? Like this?

  onLeft: self => {
    self.event.preventDefault();
  }

 

You could do it like that but not every event will be prevented because of the debounce feature. That means that if you're observing for click and the debounce is say, 100ms and you click twice in a span of 50ms, the first click will be prevented by Observer but not the second, the default action will be triggered there.

 

3 hours ago, trych said:

Would it then make sense for expensive operations to create two Observers? One that only prevents the default and is not debounced and one that executes the actual action and is debounced? Or would that not work?

Yeah, I think that is a good strategy IMHO and the direction I'd choose.  I can't see why that wouldn't work to be honest and I can't see it causing any performance or logic issue that I can think of.

 

3 hours ago, trych said:

Another question: the `allowClicks` property seems to be another undocumented property. Does this one take care of preventing click events on touch/drag style events? Because that is what I was actually trying to prevent and that might be helpful for me in my current project.

I'll ping @GreenSock for that. I was wrong about the preventDefault one so I could be wrong again on this one :D so is better that Jack, who knows all the ins and outs of GSAP, to answer that. Please stand by.

 

Let us know if you have more questions.

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

14 hours ago, Rodrigo said:
18 hours ago, trych said:

How would I do that? Like this?

  onLeft: self => {
    self.event.preventDefault();
  }

 

You could do it like that but not every event will be prevented because of the debounce feature. That means that if you're observing for click and the debounce is say, 100ms and you click twice in a span of 50ms, the first click will be prevented by Observer but not the second, the default action will be triggered there.

It'd actually be the opposite - debouncing means that the first click's event would trigger its default action, and then by the time the Observer's callback is triggered, self.event would refer to the SECOND event, thus when you call .preventDefault(), it would be effective on the second event (not the first).

 

So yeah, you could create a second non-debounced Observer that just does the preventDefault() stuff if you'd like. 

18 hours ago, trych said:

Another question: the `allowClicks` property seems to be another undocumented property. Does this one take care of preventing click events on touch/drag style events? Because that is what I was actually trying to prevent and that might be helpful for me in my current project.

No, allowClicks is for internal use related to ScrollTrigger.normalizeScroll() where we must call preventDefault() on the various touchstart/touchmove/touchend events for scroll-related stuff but we want to allow native clicks (which wouldn't work if you call preventDefault()). Like if a user taps on a button, we want that to actually work :)

  • Like 1
Link to comment
Share on other sites

  • 1 year later...
3 hours ago, richiCoder said:

@GreenSock just a question, aren't you creating too many tweens or timelines calling gsap.to or gsap.timeline into the onUp or onDown callbacks? better to handle from only on instance?

What code are you talking about? Do you have a minimal demo? If you're redirecting a particular value a lot, it's usually best to use a gsap.quickTo() instead of creating a new tween instance every time. But in order to offer you the best feedback, I'd need to know what you're talking about and see the code. 

Link to comment
Share on other sites

1 hour ago, GreenSock said:

What code are you talking about? Do you have a minimal demo? If you're redirecting a particular value a lot, it's usually best to use a gsap.quickTo() instead of creating a new tween instance every time. But in order to offer you the best feedback, I'd need to know what you're talking about and see the code. 

 

Simple version of the code you wrote above. Here, goToPanel function is called with onEnter or onEnterBack and it creates a new tween every time the ScrollTrigger gets into the ".swipe-section". 

function gotoPanel(index, isScrollingDown) {
  ...// 
  gsap.to(target, {
    xPercent: isScrollingDown ? 0 : 100,
    duration: 0.75,
    onComplete: () => {
      animating = false;
    }
  });
  ...//
}


ScrollTrigger.create({
  trigger: ".swipe-section",
  pin: true,
  anticipatePin: true,
  start: "top top",
  end: "+=50%",
  onEnter: (self) => {
    intentObserver.enable();
    gotoPanel(currentIndex + 1, true);
  },
  onEnterBack: (self) => {
    intentObserver.enable();
    gotoPanel(currentIndex - 1, false);
  }
});

 

Link to comment
Share on other sites

Hi,

 

Strictly speaking that will happen only twice when the ScrollTrigger callbacks are triggered and on every event the Observer is watching if a specific condition is met (normally a boolean after the animation is completed) so is not like creating a new GSAP instance on every mouse move event.

 

Is pretty much like this demo:

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

 

Hopefully this clear things up, if you have more questions please remember to create a minimal demo.

Happy Tweening!

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