hannebaumsaway
hannebaumsaway

Reputation: 2744

animate() not firing until scrolling stops

The goal:

I want my fixed header element to slide up off the screen (effectively hide) when the user scrolls down, and move back down when the user scrolls up.

The problem:

The actual animation and logic is working properly, but the animation doesn't begin until the user stops scrolling, as opposed to beginning while the scrolling is still going like it should.

This is replicated on Chrome and Firefox. However, with the exact same code, the solution seems to work properly in a JSFiddle:

https://jsfiddle.net/8cj7v6n5/

Javascript:

var lastScroll = 0;
var scrollDelta = 10;
var animating = false;

$(window).on('scroll', function() {
    //if animation isn't currently taking place, begin animation
    if (!animating) hasScrolled();
});

function hasScrolled() {
    var st = $(window).scrollTop();

    //if user hasn't scrolled past scrollDelta, do not animate yet
    if(Math.abs(lastScroll - st) <= scrollDelta)
        return;

    animating = true;

    if (st > lastScroll && st >= 0){    // Scroll Down
        $('header').animate( { top: -80 }, { queue: false, duration: 500, complete: function() { animating = false; } });
    }
    else {  // Scroll Up
        $('header').animate( { top: 0 }, { queue: false, duration: 500, complete: function() { animating = false } });
    }
    lastScroll = st;
}

Upvotes: 2

Views: 1013

Answers (2)

user1839708
user1839708

Reputation:

The problem is the value you use for the lastscroll is only updated when the animation happens. It should be updated always because you can scroll the screen while the animation is running and the lastscroll value will be different than the real top scroll value, then you compare the values again and the system thinks is is higher or lower than it really is.

Example:

  1. Window scrolltop is 0 You scroll down -> first animation fires,Window scrolltop at this time is 100 and it is assigned to the variable st.
  2. You keep scrolling down -> flag is on, animation does not fire, Window scrolltop at this time is 200 but it is not asigned.
  3. First animation ends -> st is assigned to lastscroll (100)
  4. Next action you scroll up, lastscroll is 100, real scroll top is actually 200. New scroll top is 190 but when you compare to lastscroll the systems believes you are scrolling down.

This is the corrected code:

var lastScroll = 0;
var scrollDelta = 10;
var animating = false;

$(window).on('scroll', function() {
    //if animation isn't currently taking place, begin animation
    if (!animating) {hasScrolled()}else{lastScroll = $(window).scrollTop();};
});

function hasScrolled() {
    var st = $(window).scrollTop();

    //if user hasn't scrolled past scrollDelta, do not animate yet
    if(Math.abs(lastScroll - st) <= scrollDelta)
        return;

    animating = true;

    if (st > lastScroll && st >= 0){    // Scroll Down
        $('header').animate( { top: -80 }, { queue: false, duration: 500, complete: function() { animating = false; } });
    }
    else {  // Scroll Up
        $('header').animate( { top: 0 }, { queue: false, duration: 500, complete: function() { animating = false } });
    }
    lastScroll = st;
}

This is the fiddle I used to debug with the console logs: https://jsfiddle.net/8cj7v6n5/18/

Edit: This code works for me outside fiddle, try it if it works then the problem is in another component of the page not published here. Maybe it is another line of code or library overriding the behavior of .animate() or the scroll event.

<html>
  <head>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <script type="text/javascript" charset="utf-8">
var lastScroll = 0;
var scrollDelta = 10;
var animating = false;

var lastScroll = 0;
var scrollDelta = 10;
var animating = false;

$(window).on('scroll', function() {
    //if animation isn't currently taking place, begin animation
    if (!animating) {hasScrolled()}else{lastScroll = $(window).scrollTop();};
});

function hasScrolled() {
    var st = $(window).scrollTop();

    //if user hasn't scrolled past scrollDelta, do not animate yet
    if(Math.abs(lastScroll - st) <= scrollDelta)
        return;

    animating = true;

    if (st > lastScroll && st >= 0){    // Scroll Down
        $('header').animate( { top: -80 }, { queue: false, duration: 500, complete: function() { animating = false; } });
    }
    else {  // Scroll Up
        $('header').animate( { top: 0 }, { queue: false, duration: 500, complete: function() { animating = false } });
    }
    lastScroll = st;
}
  </script>
  <style type="text/css">
  body {
    height: 2000px;
    width: 100%;
    background-image: url(http://static.tumblr.com/cc2l8yc/qa8nbkd68/2.jpg);
    background-repeat: repeat;
  }

  header {
      height: 80px;
      width: 100%;
      background-color: red;
      position: fixed;
      top: 0;
      left: 0;
  }
  </style>
  </head>
  <body>
    <header></header>
  </body>
</html>

Upvotes: 1

Chitrang
Chitrang

Reputation: 2114

Instead of using animating flag, you can just queue the animations and use stop() with animate() to run the last animation in queue.

EDIT: you can use the flag for direction to detect if there is change in direction of scrolling.

JavaScript:

var lastScroll = 0;
var scrollDelta = 10;
var animationDirection = 0; // 0 for up direction, 1 for down direction

$(window).on('scroll', function () {
    hasScrolled();
});

function hasScrolled() {
    var st = $(window).scrollTop();
    if (Math.abs(lastScroll - st) <= scrollDelta)
        return;

    if (st > lastScroll && st >= 0 && animationDirection == 0) {    // Scroll Down                                    
        $('header').stop().animate({ top: -80 }, 500);
        animationDirection = 1;
    }
    else if (st <= lastScroll && st >= 0 && animationDirection == 1) {  // Scroll Up
        animationDirection = 0;
        $('header').stop().animate({ top: 0 }, 500);
    }
    lastScroll = st;
}

JSFiddle: https://jsfiddle.net/8cj7v6n5/27/

Upvotes: 2

Related Questions