Jump to content
Search Community

Combining Draggable with ScrollTrigger and fixed screen size

modulareverything test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

I have a Draggable property that lets you scroll infinitely through a list of items. I have this functionality working nicely, but it's relying on Draggable to control the animation.

 

I'm looking to add to or replace Draggable with ScrollTrigger (question: does ScrollTrigger work on touch devices? If so, I probably don't need draggable at all), but as the page is a fixed height there's actually no "scrolling" going on at all, I'm unsure on how to capture the scroll data.

 

I've tried this but I can't actually get it to even run the console.log, I guess because the page is a fixed height:

ScrollTrigger.create({
animation,
scrub: true,
trigger: nav.current,
markers: true,
start: "top top",
end: "bottom 100%",
onUpdate: () => console.log("test")
});

 

Here's my codesandbox:

https://codesandbox.io/s/musing-rgb-k7vkvc

Link to comment
Share on other sites

Hi,

 

If you have no scrolling then ScrollTrigger is not going to be super useful, right?

 

What you need is the Observer Plugin:

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

 

Here is an example:

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

 

You can plug the Observer callbacks in order to update the progress of the animation and the Draggable instance.

 

Also you can use the Vertical Seamless Loop helper function:

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

 

Here is a working example in a React app:

https://stackblitz.com/edit/vitejs-vite-nuat5p

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

That's really useful @Rodrigo, thanks

 

You're right, if there's no scroll then ScrollTrigger won't do much, and it was Observer I needed in the end. I also used the verticalLoop helper you shared which is an improvement on what I had.

 

I still haven't had that penny-drop moment with GSAP (it's coming I think!) so I'm unsure of how I could control the timeline created by the verticalLoop helper using the Observer. I've managed to get it to play and/or reverse depending on the scroll, but obviously that's not really what I'm looking for

 

Observer.create({
  target: nav.current,
  type: "wheel,touch",
  onUp: () => loop.play(),
  onDown: () => loop.reverse(),
  onChange: (self) => console.log(self),
});

I would guess you need to take in things like the deltaY to somehow control the speed of the timeline. Maybe someone could nudge me in the right direction here. I feel like we're getting pretty close!

Link to comment
Share on other sites

Hi,

 

The comments before the loop function definition have some tips about it's usage:

 

- The returned timeline will have the following methods added to it:
   - next() - animates to the next element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc.
   - previous() - animates to the previous element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc.
   - toIndex() - pass in a zero-based index value of the element that it should animate to, and optionally pass in a vars object to control duration, easing, etc. Always goes in the shortest direction
   - current() - returns the current index (if an animation is in-progress, it reflects the final index)
   - times - an Array of the times on the timeline where each element hits the "starting" spot.
   - elements - an Array of the elements that are being controlled by the timeline

 

So you can use the Observer callbacks to use those. As for playing reversing you can use the timeline's timescale:

https://greensock.com/docs/v3/GSAP/Timeline/timeScale()

 

You can easily do something like this:

let loop = verticalLoop(/* config here */);

gsap.to(loop, {timeScale: -1});// goes backward with a nice easing
gsap.to(loop, {timeScale: 1});// goes forward with a nice easing

You can factor the delta value in to make the timescale more/less than +/-1 and then tween it back to +/-1. I know there are a few examples of that here in the forums, but I can't find them right now. I'll give it a try later so you can see the concept in action.

 

Happy Tweening!

  • Like 3
Link to comment
Share on other sites

Hi,

 

I knew there was an example created with the Observer Plugin somewhere, here you go:

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

 

You can play with it and make it fit your needs. This uses the horizontal loop helper function instead of the vertical one, but the principle behind both is the same, so you shouldn't have any issues using that bit of code.

 

Hopefully this helps.

Happy Tweening!

  • Thanks 1
Link to comment
Share on other sites

Thanks for your help, Rodrigo. I feel like you've provided me with all the clues, but unfortunately, I'm still really not getting this. I've been trying to work it out for the past couple of hours and I'm afraid I'm still stuck.

 

I've tried a load of different methods and the one that felt closest was this:

 

// pause it. setting it to paused by default
// seemed to prevent the timeScale from doing anything
// so setting to 0 here seemed to fix that
loop.timeScale(0);

Observer.create({
  target: window,
  type: "wheel,touch",
  onChange: (self) => {
    console.log(self.velocityY * 0.5); // I also tried deltaY

    gsap.to(loop, {
      duration: 2,
      timeScale: self.velocityY * 0.5, // I also tried things like '/ 100' here to get a lower number
    });
  },
  onStop: () => {
    console.log("stopped");

    // my thought here was to tween the timeScale back to 0 when the user has stopped scrolling
    gsap.to(loop, {
      duration: 5,
      timeScale: 0,
    });
  },
});

 

The problem is this really doesn't feel like a native user scroll at all, it feels like you only really have to nudge the mouse wheel or trackpad and the thing just starts spinning, or else I lower the values too much and even a huge velocity scroll only nudges it a bit. This does feel really quite close to being the right approach, but the control isn't quite hitting the mark yet.

 

I'm going to keep trying, but hopefully yourself or someone else might have some clues or can impart some knowledge which might help me get it feeling more natural.

 

My sandbox link is still the same and I updated the code to reflect the changes I've made.
https://codesandbox.io/s/musing-rgb-k7vkvc?file=/src/Navigation.jsx

 

Edit:

A little more playing around and I feel like it's starting to get somewhere. One thing I found is that with using an MX Master 3 mouse, the mouse wheel can really be thrown which just sends you flying. With a trackpad, or switching the mouse to the 'notched' wheel mode, it's much less unruly.

 

That, in conjunction with the `wheelSpeed` setting I found, it seemed to be working okay. I still don't think it's perfect, and if anyone has any further input on how to make it feel more naturally I'm all ears, but I think it's in a place where I can actually move on from this :D

 

Observer.create({
  target: window,
  type: "wheel,touch",
  wheelSpeed: 0.25,
  onChange: (self) => {
    gsap.to(loop, {
      timeScale: self.deltaY,
      ease: "none",
    });
  },
  onStop: () => {
    gsap.to(loop, {
      timeScale: 0,
      ease: "none",
    });
  },
});

 

Link to comment
Share on other sites

  • Solution

Hi,

 

This approach gives a result that I really like, it's a personal preference of course:

useLayoutEffect(() => {
  const ctx = gsap.context(() => {
    const loop = verticalLoop("ul li", {
      repeat: -1,
      center: true,
    });
    loop.timeScale(0);
    let factor = 2.5;

    Observer.create({
      target: window,
      type: "wheel,touch",
      onChange: (self) => {
        console.log(self.deltaY)
        if (self.deltaY > 0) {
          factor = -2.5;
        } else {
          factor = 2.5;
        }

        gsap.to(loop, {
          timeScale: factor,
          onComplete: () => {
            gsap.to(loop, { timeScale: 0, duration: 2, ease: "none" });
          }
        });
      },
    });
  }, nav);
  return () => ctx.revert();
});

Hopefully this helps.

Happy Tweening!

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Thanks, Rodrigo, super helpful. I like the idea of using the 'force' that the user hit the trackpad or wheel to calculate the timeScale rather than tweeting to a fixed value. Just preference like you say though.

 

My last question on this... is there a reason why on a touch device the scroll feels inverted? Swiping down sends the list up and vice-versa. Is there a way to flip the axis on touch?

 

I saw that you could set scrollSpeed to something like -1 to invert the scroll on desktop or something?

Link to comment
Share on other sites

1 hour ago, modulareverything said:

My last question on this... is there a reason why on a touch device the scroll feels inverted? Swiping down sends the list up and vice-versa. Is there a way to flip the axis on touch?

 

I saw that you could set scrollSpeed to something like -1 to invert the scroll on desktop or something?

No, scrollSpeed is for "scroll" events, but you're only listening for wheel and touch. The way you'd handle this is to just invert your calculations and then set wheelSpeed: -1. 

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