Chuck Le Butt
Chuck Le Butt

Reputation: 48758

jQuery animate "complete" callback firing too soon

According to the jQuery documentation .animate has a parameter called 'Complete' that should only fire once the animation has finished. Unfortunately I'm finding that it fires before the animation has even started.

if ($('html').scrollTop()) {
    $('html').animate({ scrollTop: 0 }, callback);
    return;
}

JSFiddle showing the problem: http://jsfiddle.net/JohnnyWalkerDesign/5zu90ygz/

What am I missing?

Upvotes: 4

Views: 1880

Answers (2)

fdomn-m
fdomn-m

Reputation: 28611

The callback parameter should be a function pointer. It's not a term used very often these days, but it means that you pass the function itself, not the result of a function.

What does this mean?

Given the example:

function message(q) {
    alert(q);
}

Here, message is a function. To pass that function as a callback, use the name of the function, eg:

setTimeout(message, 100);
$("#id").click(message);

Note that there's no () after the name of the function.

In javascript terms, the function itself is a variable and you pass the variable, but as soon as you add () you are calling the function and passing the result, not the function itself.

If you use message() then the function will be executed and the result of the function passed as the callback, ie:

setTimeout(message("x"), 100);

is the equivalent of:

var x = message("x");
setTimeout(x, 100);

So you can see from this code why message runs immediately (more obvious with click event or longer timeout).

The format setTimeout(message, 100); isn't used very often as a) it looks like a typo (should that be message()?) and b) it doesn't allow you to pass a parameter, so this would frequently be written as:

setTimeout(function() { message("x") }, 100);

which uses an anonymous in-line function as the callback variable / function pointer.


Back the question:

In your original fiddle, all you needed to do was change the callback to a true callback without the need to add a separate parameter, ie:

$('#scroll').on('click', function(e) {
    e.preventDefault;
    scrollToTop(function() { message("Reached Top")});
});

Updated fiddle: http://jsfiddle.net/5zu90ygz/9/

Upvotes: 11

Tushar
Tushar

Reputation: 87203

You're calling the function message when registering it as callback in the statement scrollToTop(message('Reached Top'));. Here the function message will be called and the returned value will be passed to the scrollToTop function.

Updated Fiddle

You can pass an extra parameter to the function scrollToTop i.e. message to be displayed and then this message can be forwarded to the message function after animation completes.

// Scroll to the top and fire callback
function scrollToTop(callback, message) {
  // Send the message as the second parameter

  if ($('html').scrollTop()) {
    $('html').animate({
      scrollTop: 0
    }, function() {
      // Call the callback function with the message as param after completion of animate
      callback(message)
    });
    return;
  }

  if ($('body').scrollTop()) {
    $('body').animate({
      scrollTop: 0
    }, function() {
      // Call the callback function with the message as param after completion of animate
      callback(message)
    });
    return;
  }

  callback(message);
}

// Dummy callback function to fire
function message(q) {
  alert(q);
}

// On click event
$('#scroll').on('click', function(e) {
  e.preventDefault;
  
  // Add message as second parameter
  scrollToTop(message, "Reached Top! Yay!");
});
div {
  width: 100%;
  border: 1px solid black;
  height: 200px;
  margin: 10px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<a href="#" id="scroll">Scroll to top</a>

Upvotes: 6

Related Questions