Glitchy scrollTop script

#1

Hi there,

I’ve put together an animated navigation which on scroll shrinks down to a smaller size.
Unfortunately at a certain point in the scroll the animation begins to glitch (due to the resize of the header I believe it is offsetting the scroll amount and thus the scroll value gets stuck between pixels - see the console for details when viewing my example).

I fully understand that continually poling for the scroll value isn’t ideal so any pointers on how to rectify this as well would be appreciated.

I’ve setup a Codepen - see as follows:
https://codepen.io/Shoxt3r/pen/jRgwaY

Thanks in advance!

#2

Yes it’s a known bug/behaviour with position:sticky and the height of the sticky element seems to affect the scrollTop position unlike a fixed positioned header.

If instead of manipulating the height of the sticky you used transform:scale() instead then a reflow would not occur and the bug would most likely disappear and I believe there is a similar demo in that link above. Of course the effect is not as nice.

The other option would be to do as we used to do and add a fixed positioned header on scroll rather than position:sticky and avoid the bug. Of course it may not be as smooth as the position:sticky version and you get a content jump when the transition kicks in.

User a debounce function like this one.

1 Like
#3

Yes, you can use an intersection observer instead. If you don’t have an actual element to observe that defines the threshold of 40px, you might add a sentinel to the top of the body:

.navbar-sentinel {
  position: absolute;
  height: 40px;
  width: 1px;
}
<div class="navbar-sentinel"></div>

And then toggle the navbar--shrink class depending on whether the sentinel is currently intersecting the viewport or not (note that the callback only gets invoked when the intersection actually changes):

const navbar =  document.querySelector('.navbar')
const sentinel = document.querySelector('.navbar-sentinel')

const observer = new IntersectionObserver(([ entry ]) => {
  navbar.classList.toggle('navbar--shrink', !entry.isIntersecting)  
})

observer.observe(sentinel)

Unfortunately, this API is not supported by IE (no kidding LOL); so if you need to target IE users too, you can either include a polyfill or use a debounced scroll listener as fallback.

1 Like
#4

The IntersectionObserver method is still suffering from the same problem in that as soon as the position:sticky element changes size the sentinel element no longer ‘intersects’ and the glitch keeps repeating as the class is toggled on and off.

The only solution that I can see using position:sticky is to not remove the class until scrollTop is zero.

e.g. This seems works (but may still be buggy).

(function($, sr) {
    // debouncing function from John Hann
    // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
    var debounce = function(func, threshold, execAsap) {
        var timeout;

        return function debounced() {
            var obj = this,
                args = arguments;

            function delayed() {
                if (!execAsap)
                    func.apply(obj, args);
                timeout = null;
            };

            if (timeout)
                clearTimeout(timeout);
            else if (execAsap)
                func.apply(obj, args);

            timeout = setTimeout(delayed, threshold || 100);
        };
    }
    // smartresize 
    jQuery.fn[sr] = function(fn) {
        return fn ? this.bind('scroll', debounce(fn)) : this.trigger(sr);
    };

})(jQuery, 'smartresize');

// usage:
$(window).smartresize(function() {
    getScrollTop();
});

function getScrollTop() {
    var scrolled = $(document).scrollTop();

    if (scrolled > 40) {
        $(".navbar").addClass("navbar--shrink");
    }

    if (scrolled === 0) {
        $(".navbar").removeClass("navbar--shrink");
    }

}

(The debounce code borrowed from the link shown in the code so there may be a simpler solution.)

The main problem seems to be that when the position:sticky element changes size it affects the scroll top position and possibly the position of other elements although I see no movement apart from the scrollbar jumping. I guess it has something to do with the way that a position:sticky element is returned back into the flow as you do not get a jumping text with position:sticky.

1 Like
#5

Thanks for the replies both - seems I’m not alone in this then!
Aside from the debounce code my version on the pen below gives the same “snappy” effect.

https://codepen.io/Shoxt3r/pen/zQOKJv

EDIT: In fact, testing the relative/fixed position version vs the sticky version, especially when you get to the lower breakpoints and scroll directly to the bottom of the page, the effect is very choppy!

#6

That seems to be the same code as before?

#7

Sorry! I was using my relative/fixed version with your code, rather than having it set to sticky (been a long day!).

I’ve created a new pen using your JS with the sticky header below and all seems well I think?
https://codepen.io/Shoxt3r/pen/LoPbpj

#8

Ok let’s try that again…this time on a proper internet connection and actually saving the pen this time! haha.
https://codepen.io/Shoxt3r/pen/LoPbpj

This works but now I have a horizontal scroll as I’ve put the header in the main container and removed the padding for the main container so it goes full width…

#9

You can’t remove the padding from bootstrap elements because that is the gutter that rows fit into and they have negative margins that match the padding. You are supposed to put all content inside columns which are inside rows. If you need custom elements then make your own and don’t use the bootstrap ones.

You can fix your code by removing the padding:0 from the container and instead apply negative margins to the navbar.

.navbar{margin:0 -15px;}

Or you could probably add a class of .row to the navbar instead.

#10

Ah thanks Paul - was being stupid, makes perfect sense now haha.

One problem I’ve found with the script is that at a certain point the nav “bounces” between the animations (I’m guessing when it hits the 40px threshold?). Like you said before the script may still be a bit buggy.

Any further thoughts please?

https://codepen.io/Shoxt3r/pen/RmGaBz

#11

Doesn’t look too bad to me but you need to lose the negative margin-top on the h1 as that is dragging your content under the navbar and may effect how it looks when the navbar scales.

Animating properties like width, height padding, font-size etc is always going to be more choppy than using things like transform which do not affect reflow.

#12

Good spot - I was actually getting the header fixed and then going to move onto the rest of the content :slight_smile: I’ll post a link when I’ve got it into a state but it basically got to a point in the scroll where the navigation animation “bounced”.

I’ll have a look at using transform and see if that improves things.