Jump to content
Search Community

Loving ScrollTrigger, but can it do this to pins.

Codetipi test
Moderator Tag

Recommended Posts

Thanks again, but not quite :P  It's a bit tricky to explain it, but essentially yes, when scrolling downwards that is the desired behaviour.


However, when you scroll back up, the bottom is no longer fixed, it's only when you scroll past the top of the sticky element that it becomes fixed again from the top. And the sticky element doesn't actually move at all if you're in the middle of the page scrolling up and down without hitting an edge of the element itself.


Try scrolling in this demo again but ensure it's the "Scrollable Sticky Element" example:



Scroll to the bottom, then back up and notice the sticky sidebar only being fixed in connection of scrolling direction, and even if the sidebar is stuck in the middle of the page, you can scroll up and down and it won't move any more (until you pass the top/bottom edge).


Thanks so much for looking into this, really hoping ScrollTrigger is able to do this.  

Link to comment
Share on other sites

Ah, I see what you mean now. Yeah that isn't really the type of thing ScrollTrigger was built for but I was able to make a few minor changes internally for the next release (3.4.1) that should make it possible using two ScrollTriggers. Here's a demo that uses a preview of the upcoming release: 

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


I wrapped the functionality into a helper function, as you can see, so you can simply feed in two elements and it does everything for you. 


The preview of 3.4.1 can be found at https://assets.codepen.io/16327/ScrollTrigger.min.js


Is that the behavior you want? 

  • Like 3
Link to comment
Share on other sites

Oh wow that's incredible of you. Thank you for your efforts.


It's 90% there, there is only one small thing missing to make the functionality perfect for sticky tall sidebars, and that is that when you scroll half way down the page, the sidebar should stay frozen and be "scrollable". As in, if you scroll down to the middle of the page, and then scroll upwards again, the whole viewport scrolls but the sidebar stays until you pass the top edge again and it becomes sticky at that point again. And if instead of reaching the top edge of the sticky element, you start scrolling downwards again, it still stays frozen until you hit that bottom edge and at that point continues being sticky.


You can see this happen in the sticky sidebar script example I shared - this functionality is the only reason theme developers like me use popular scripts like Sticky Sidebar, HC Sticky, Sticky Kit, etc instead of just using "position: sticky". If ScrollTrigger can do this, I'm sure everyone needing proper sticky sidebars would flock to using it.


Hope my explanation makes sense and thanks again :)

Link to comment
Share on other sites

P.s do you guys do a premium/paid for support service or anything similar that you could forward me to? As I would happily pay extra for your time in helping explore if this sticky sidebar functionality is fully doable with ScrollTrigger :)

Link to comment
Share on other sites

For anyone interested, here's the helper function I came up with:

function stickyBothDirections(element, vars) {
  vars = vars || {};
  element = gsap.utils.toArray(element)[0];
  let keywords = {top: "0", center: "50%", bottom: "100%"},
      overlap, topOffset,
      updateOverlap = () => {
        topOffset = ((typeof(vars.start) === "function" ? vars.start() : vars.start || "0 0") + "").split(" ")[1] || "0";
        topOffset = keywords[topOffset] || topOffset;
        topOffset = ~topOffset.indexOf("%") ? parseFloat(topOffset) / 100 * window.innerHeight : parseFloat(topOffset) || 0;
        overlap = Math.max(0, element.offsetHeight - window.innerHeight + topOffset);
      {onUpdate, onRefresh} = vars,
      offset = 0,
      lastY = 0,
      pin = (value, bottom) => {
        pinned = value;
        if (pinned) {
          let bounds = element.getBoundingClientRect();
          gsap.set(element, {
            position: "fixed",
            left: bounds.left,
            width: bounds.width,
            boxSizing: "border-box",
            y: 0,
            top: bottom ? topOffset - overlap : topOffset
        } else {
          gsap.set(element, {
            position: "relative",
            clearProps: "left,top,width,boxSizing",
            y: offset
  vars.trigger = element;
  vars.start = "start" in vars ? vars.start : "top top";
  vars.onRefresh = self => {
    onRefresh && onRefresh(self);
  vars.onUpdate = self => {
    let {progress, start, end, isActive} = self,
        y = progress * (end - start),
        delta = y - lastY,
        exceedsBottom = y + Math.max(0, delta) >= overlap + offset;
    if ((exceedsBottom || y + Math.min(0, delta) < offset) && isActive) {
      offset += exceedsBottom ? y - overlap - offset : y - offset;
      pinned || pin(true, exceedsBottom);
    // uncomment if you want to prioritize the top staying in view
    //} else if (!exceedsBottom && y + offset < overlap && isActive) {
    //  offset += y - offset;
    //  pinned || pin(true, false);
    } else if (pinned || !isActive) {
      isActive || (offset = y ? self.end - self.start - overlap : 0);
    lastY = y;
    onUpdate && onUpdate(self);
  self = ScrollTrigger.create(vars);
  return self;

And here it is in action in various scenarios: 



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


With a sticky page header

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


With a sticky sidebar header

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


Sample usage: 

stickyBothDirections("#sidebar", {
	start: "top top",
	endTrigger: "#content",
	// dynamically adjust the end in case the trigger is shorter than the window
	end: self => "bottom bottom" + (window.innerHeight > self.trigger.offsetHeight ? "-=" + (window.innerHeight - self.trigger.offsetHeight) : ""),
	markers: true



  • Like 4
Link to comment
Share on other sites

  • 4 months later...
On 7/20/2020 at 8:40 PM, GreenSock said:

For anyone interested, here's the helper function I came up with:








What if the sidebar is shorter? Like here:

See the Pen GRjmMZv by knalle (@knalle) on CodePen


How do I get the bottom of the sidebar to end with the bottom of the content. Tried messing with the end trigger, but I couldn't wrap my head around it :D  

Link to comment
Share on other sites

Hey @knalle


Do you mean like this?


See the Pen 33742a0920c0d24ee63edaabb4d7e8da by akapowl (@akapowl) on CodePen



This would be the end for that ScrollTrigger:


end: "bottom bottom-=" + ( window.innerHeight - document.querySelector('#sidebar').offsetHeight )


Edit: Whoops!

I totally missed the purpose of the helper function, so don't mind my suggestion - it doesn't work once the window's height is less than the height of the sidebar (if it has absolute values like 500px for example). Sorry for the quick shot.



  • Like 3
Link to comment
Share on other sites


Ha, I had a similar idea with this here


end: () => window.innerHeight > document.querySelector('#sidebar').offsetHeight ? "bottom bottom-=" + ( window.innerHeight - document.querySelector('#sidebar').offsetHeight ) : "bottom bottom",


I found it to work with regard to stopping at the right point, but I hesitated to post my suggestion, because if the sidebar is initially position out of viewport to the bottom, it wont' pin to the bottom of the window, like it would if the sidebars content was taller than window-height.


This is what I mean:


Content taller than window height;  sidebar's bottom pins to bottom of window


See the Pen 505224b5e6a6b51ce468acea3e1a85cd by akapowl (@akapowl) on CodePen




Content smaller than window height;  sidebar's top pins to top of window


See the Pen 193b666b7222e7ea588d7e41c1ac2778 by akapowl (@akapowl) on CodePen

  • Like 1
Link to comment
Share on other sites

Hm, I guess I would expect it to pin to the top of the window as you scroll down, like this (I tweaked the helper function): 

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


Better? I suppose it could be tweaked further to pin at the bottom of the window but I thought the goal was to let the user scroll in either direction freely, and only pin when content is going to go BEYOND the visible area, but we also need to keep it from extending beyond the end point in the other direction. And again, I did improve the helper function to better accommodate the first scenario you pointed out (it wasn't pinning at the top when that may have been more intuitive). 

  • Like 1
Link to comment
Share on other sites


🙈 I think I might have caused confusion with my post.


Actually I thought, the way it behaves in the first of my two pens (with the content being taller than the window height), pinning to the bottom when scrolling down and pinning to the top when scrolling up was the intention of the helper function.


With my second pen I just wanted to point out, that it doesn't do that, if the content is less tall than the window is.

In this case it will always pin to the top, wether you scroll up or down.


Maybe that was just a misunderstanding.


Please don't feel the urge to change anything about that helper function based on what I say though, because I actually won't be using it.

I just thought that was, what @knalle was intending with his initial question. But that could also be a misundestanding on my end.


Sorry for any confusion caused.


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

  • 9 months later...

Hi, great solution for a sticky sidebar, just what I was looking for!

Sorry for commenting on an older topic, but if anyone would be able to help with my issue, I'm kind of stuck finding the solution.

I am using the code from the previous example:

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


Now this works fine, although I have expandable/callapsable items in the sidebar. So the sidebars height changes. This makes the triggers positions incorrect. What would be the best way to update the scrolltrigger in this case?
I've tried .refresh, .kill, .disable and calling the stickyBothDirections function again to fix it. But it doesn't fix it correctly.

Any help would be great!

Link to comment
Share on other sites

Welcome to the forums, @Martincode


That sounds like a tricky scenario that'd require a very custom solution. It's not the kind of consulting we can do for free in these forums, but we're happy to answer any GSAP-specific questions. If you'd like to explore paid consulting options (and I'm sure we could come up with a solution for you with enough time), you could either contact us or post in the "Jobs & Freelance" forum. 


Happy tweening!

Link to comment
Share on other sites

  • 3 months later...

hey jack @GreenSock.


Thanks for your attention and for helping the community. I'm much more eager to work with GSAP when I see your support.


I used your code for my sticky sidebar and it was perfect. but there is a problem. I work with vue & nuxt and I used this sticky for my products grid. As any online shop, products grid have filters sidebar. so some action on filters can change the height of my sidebar or content section.

Eg: imagine that there is collapsible box in filter that in the render time they are closed and the user during filtering decides to open some of them or close the others, and that means height changes on sidebar or the result of filters may have some changes in quantity and that also changes the height of content box.




I think the problem is when the height changes, ScrollTrigger needs to be refreshed, but I’m not able to detect for example when the collapsible transition has been finished to refresh the ScrollTrigger. I need some solution to detect the finish time of all transition or rendering dom elements and then refresh the ScrollTrigger or make the ScrollTrigger that smart to detect height changes and refresh itself. 


I tried to work with ResizeObserver API but I couldn't do that.


here is my pen that explain my case:

See the Pen GRMQYyv by shayandrg (@shayandrg) on CodePen


If you look at my pen you can see I tried to simulates the collapsible by adding the action button.

Link to comment
Share on other sites

I believe GreenSock is talking about that scenario in the post right above yours.



That sounds like a tricky scenario that'd require a very custom solution. It's not the kind of consulting we can do for free in these forums, but we're happy to answer any GSAP-specific questions. If you'd like to explore paid consulting options (and I'm sure we could come up with a solution for you with enough time), you could either contact us or post in the "Jobs & Freelance" forum. 


Link to comment
Share on other sites

On 12/31/2021 at 10:04 AM, GreenSock said:

Yeah, you'd need to kill the old ScrollTrigger and recreate it based on the new sizing. There's some extra logic necessary too which I've added here:




Is that what you're looking for? 


thanks a lot for helping. but actually no!
you called updateSidebar() in the action function! and it work just for this pen!

in real cases there are over 50 actions in page that can change the height of sticky side bar and its content section! and we can't call updateSidebar() on everywhere in the real usage.

It needs to detect height changes and update itself accordingly

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