Jump to content


GSAP Horizontal Scroll with Proxy Div

Moderator Tag

Recommended Posts

Hey All,

This is my first time plunging into GSAP and boy what a deep pool it is!

I am trying to develop a horizontal scroller that uses drag and scroll interaction. After finding some code online, I hacked together some code to get 80% of the way there.

Im building a next js app but have created the attached example in React as a close comparison. It's an image gallery where each image will be of a different size, so I need a way to dynamically calculate the width perhaps?

My other issue is on resize i'm not sure quite how to get the container to smoothly resize instead of changing after a drag event?

Sometime i also get into a gltichy state when scrolling / dragging to the end. Maybe i have missed something really simple, but i would love some advice on this. 


Much Appreciated!


Link to comment
Share on other sites

Hi @iamterryclark and welcome to the GreenSock forums!


Sorry about the delay 🙏


Draggable has an update method, but I don't know how helpful that would be given the fact that you're using a Proxy for dragging.


I believe in this case it would be better to use a combination of ScrollTrigger and the Observer Plugin as shown in this example, that should be enough for the responsive approach and you wouldn't have to trouble with updating the Draggable instance:

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


Also I notice that you're not using GSAP Context in your setup. When using GSAP in a React/Next/Gatsby project GSAP Context is your best friend!



I'd strongly recommend you to check these resources:



Finally regarding your snapping in the example you provided, right now you're using the number of panels and not the width of each panel. If you check the API for snapping you'll see that the value of snapping varies between 0 and 1, which is basically a percentage-based value of the instance. So you could check the width of each image, get the percentage of that image relative to the total width and feed an array that has all those values to the snap function. Another option is add a label for each image in the timeline and use either labels or labelsDirectional. From the docs:


Number | Array | Function | Object | "labels" | "labelsDirectional" - Allows you to snap to certain progress values (between 0 and 1) after the user stops scrolling. So snap: 0.1 would snap in increments of 0.1 (10%, 20%, 30%, etc.). snap: [0, 0.1, 0.5, 0.8, 1] would only let it come to rest on one of those specific progress values. It can be any of the following:
  • Number - snap: 0.1 snaps in increments of 0.1 (10%, 20%, 30%, etc.). If you have a certain number of sections, simply do 1 / (sections - 1).
  • Array - snap: [0, 0.1, 0.5, 0.8, 1] snaps to the closest progress value in the Array in the direction of the last scroll (unless you set directional: false).
  • Function - snap: (value) => Math.round(value / 0.2) * 0.2 feeds the natural destination value (based on velocity) into the function and uses whatever is returned as the final progress value (in this case increments of 0.2), so you can run whatever logic you want. These values should always be between 0 and 1 indicating the progress of the animation, so 0.5 would be in the middle.
  • "labels" - snap: "labels" snaps to the closest label in the timeline (animation must be a timeline with labels, of course)
  • "labelsDirectional" - snap: "labelsDirectional" snaps to the closest label in the timeline that's in the direction of the most recent scroll. So if you scroll a little bit toward the next label (and stop), even if the current scroll position is technically closest to the current/last label, it'll snap to the next one in that direction instead. This can make it feel more intuitive for users.
  • Object - Like snap: {snapTo: "labels", duration: 0.3, delay: 0.1, ease: "power1.inOut"}, fully customizable with any of the following properties (only "snapTo" is required):
    • snapTo [Number | Array | Function | "labels"] - determines the snapping logic (described above)
    • delay [Number] - the delay (in seconds) between the last scroll event and the start of the snapping animation. Default is half the scrub amount (or 0.1 if scrub isn't a number)
    • directional [Boolean] - by default (as of version 3.8.0), snapping is directional by default meaning it'll go in the direction the user last scrolled, but you can disable this by setting directional: false.
    • duration [Number | Object] - the duration of the snapping animation (in seconds). duration: 0.3 would always take 0.3 seconds, but you can also define a range as an object like duration: {min: 0.2, max: 3} to clamp it within the provided range, based on the velocity. That way, if the user stops scrolling close to a snapping point, it'd take less time to snap than if the natural stopping point is far from a snapping point.
    • ease [String | Function] - the ease that the snapping animation should use. The default is "power3".
    • inertia [Boolean] - to tell ScrollTrigger not to factor in the inertia, set inertia: false
    • onStart [Function] - a function that should be called when snapping starts
    • onInterrupt [Function] - a function that should be called when snapping gets interrupted (like if the user starts scrolling mid-snap)
    • onComplete [Function] - a function that should be called when snapping completes


Hopefully this helps.

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.