How to trigger animation after user scroll to N points - jQuery waypoints with multip

I need your help!

Basic idea: I want when user scroll to one point (in the middle of the pages - let say it’s 500px from top) then we have some animation, of course I won’t ask how to do all the animation stuff, but I will need you to give me the basic idea about the callback

Callbacks that I mean: after 1st animation, we get 2nd animation, then 3rd animation. How I approach this?

SCENARIO:

Let say we have 4 boxes, and they have color red, blue, orange and pink.
After user scrolled to 500px from top - I want first red box fadein
2 seconds from that, I want the red box fadeOut, and blue box will fadein.
in my original idea, I will need them to rotate or some other animation - if you could do this too it will be great :slight_smile: , please ignore this point if you think I’m asking too much
TOOLS:

jQuery waypoints http://imakewebthings.com/jquery-waypoints/
maybe jQuery transit ? http://ricostacruz.com/jquery.transit/
+1 for the right solutions, as I always appreciate all solutions, I will vote it up if it’s works :slight_smile:

Thanks!

Hi there,

Welcome to the forums :slight_smile:

The first thing you need to do is to detect when the user has scrolled past a certain point. that’s easy:

$(window).on("scroll", function(){
  if($("body").scrollTop() === 500){
    $(window).off("scroll");
    // Do some stuff here ..
  }
}

This line:

$(window).off("scroll");

removes the scroll event handler, as I am assuming you only want this to fire once.

Displaying the boxes is also relatively easy.
If I was you I would create the boxes on your page, then hide them using CSS.
Then once you have reached the correct scroll point, you can fade them in one after another, using setTimeout and by passing a callback to the fadeIn function.

$(selector).fadeIn('slow', function() {
  // Animation complete
});

Something like this should work:

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Callback hell!</title>
    <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
    <style>
      body{height:3000px;}
      #box1{display:none; width:200px; height:200px; background:red; position: fixed; top:0; right: 0;}
      #box2{display:none; width:200px; height:200px; background:blue; position: fixed; top:0; right: 0;}
      #box3{display:none; width:200px; height:200px; background:orange; position: fixed; top:0; right: 0;}
      #box4{display:none; width:200px; height:200px; background:pink; position: fixed; top:0; right: 0;}
    </style>
  </head>

  <body>
    <h1>Some heading</h1>
    <p>Some content</p>
    <div id="box1"></div>
    <div id="box2"></div>
    <div id="box3"></div>
    <div id="box4"></div>
    <script>
      $(window).on("scroll", function(){
        if($("body").scrollTop() === 500){
          $(window).off("scroll");
          $("#box1").fadeIn('slow', function(){
            setTimeout(function(){
              $("#box1").fadeOut('slow');
              $("#box2").fadeIn('slow');
              setTimeout(function(){
                $("#box2").fadeOut('slow');
                $("#box3").fadeIn('slow');
                setTimeout(function(){
                  $("#box3").fadeOut('slow');
                  $("#box4").fadeIn('slow');
                }, 2000)
              }, 2000)
            }, 2000);
          });
        };
      });
    </script>
  </body>
</html>

I didn’t make the biggest effort with the boxes, as I’m not sure what kind of effect you are trying to achieve.
Also, if you need any more callbacks than this, I would start looking for a more elegant solution, as nested callbacks are nasty!

Nonetheless, this will hopefully give you something to go on.

Forgive the question from a beginner with jquery but doesn’t Firefox need $(“html”).scrollTop() as it registers only zero for $(“body”).scrollTop()? At least that was the problem I ran into recently and had to use something like:


 var theTop = $('html').scrollTop();
    if (theTop == 0) {
        var theTop = $('body').scrollTop();
    }

I also think that Firefox won’t recognise 500 as it seems to jump over it and you may need to say greater than. Of course I may be out of my depth here and doing it all wrong :slight_smile:

Hey Paul,

Oh my goodness, you are right on both accounts. What can I say? My shame knows no bounds :blush:

Yup. I did have a little snippet to address this which I found online (I can’t remember where, but I didn’t write it).

function getScrollTop(){
  if(typeof pageYOffset!= 'undefined'){
    return pageYOffset;  // Most browsers
  } else{
    var b = document.body; //IE 'quirks'
    var d = document.documentElement; //IE with doctype
    d = (d.clientHeight)? d : b;
    return d.scrollTop;
  }
}

Yes indeed. Good catch!

Of course I may be out of my depth here and doing it all wrong :slight_smile:

No comment :slight_smile:

Here’s the updated code:

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Callback hell!</title>
    <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
    <style>
      body{height:3000px;}
      #box1{display:none; width:200px; height:200px; background:red; position: fixed; top:0; right: 0;}
      #box2{display:none; width:200px; height:200px; background:blue; position: fixed; top:0; right: 0;}
      #box3{display:none; width:200px; height:200px; background:orange; position: fixed; top:0; right: 0;}
      #box4{display:none; width:200px; height:200px; background:pink; position: fixed; top:0; right: 0;}
    </style>
  </head>
  
  <body>
    <h1>Some heading</h1>
    <p>Some content</p>
    <div id="box1"></div>
    <div id="box2"></div>
    <div id="box3"></div>
    <div id="box4"></div>
    <script>
      function getScrollTop(){
        if(typeof pageYOffset!= 'undefined'){
          return pageYOffset;
        }
        else{
          var b = document.body; //IE 'quirks'
          var d = document.documentElement; //IE with doctype
          d = (d.clientHeight)? d : b;
          return d.scrollTop;
        }
      }    
          
      $(window).on("scroll", function(){
        if(getScrollTop() >= 500){
          $(window).off("scroll");
          $("#box1").fadeIn('slow', function(){
            setTimeout(function(){
              $("#box1").fadeOut('slow');
              $("#box2").fadeIn('slow');
              setTimeout(function(){
                $("#box2").fadeOut('slow');
                $("#box3").fadeIn('slow');
                setTimeout(function(){
                  $("#box3").fadeOut('slow');
                  $("#box4").fadeIn('slow');
                }, 2000)
              }, 2000)
            }, 2000);
          });
        };
      });
    </script>
  </body>
</html>

I found where I got the snippet from: http://stackoverflow.com/questions/871399/cross-browser-method-for-detecting-the-scrolltop-of-the-browser-window

lol - no worries - it was just a lucky guess on my part.

Thanks for the scrolltop snippet I’ll keep that safe somewhere:)

Oh man, thank you very much for the code, I think I should try it first.
Is it the best practices using a lot of nested code? Because in my plan, I will do around 5-8 animations, so if nested all of them, I’m afraid maybe it’s not good?

I know it should works, but is there any better way?
Found this on stackoverflow here, the very first answer (voted)
Could you help me to understand this code?

I’m totally new in jQuery. sorry :slight_smile:

Hi,

There’s nothing wrong with it, other than it is a nightmare to read, maintain and keep track of what is happening where.
Apart from that, nested callbacks are great :slight_smile:

That’s much nicer.

What that code does, is use jQuery’s implementation of the JavaScript Promise Pattern.

This might be a bit tricky to get your head round if you are new to JavaScript, but a Promise is an object that represents a one-time event, typically the outcome of an async task like an AJAX call, or as in this case, an animation. You can attach callbacks to the Promise, which will fire when the Promise is resolved or rejected (i.e. when the operation completes or fails). This allows you to write clearer, shorter callbacks.

If you’re interested in finding out more, read this: http://net.tutsplus.com/tutorials/javascript-ajax/wrangle-async-tasks-with-jquery-promises/

Anyway, I’ve updated the code:

<!DOCTYPE HTML>
<html>
  <head>
    <!--http://www.sitepoint.com/forums/showthread.php?1052208-How-to-trigger-animation-after-user-scroll-to-N-points-jQuery-waypoints-with-multip-->
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Callback hell!</title>
    <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
    <style>
      body{height:3000px;}
      #box1{display:none; width:200px; height:200px; background:red; position: fixed; top:0; right: 0;}
      #box2{display:none; width:200px; height:200px; background:blue; position: fixed; top:0; right: 0;}
      #box3{display:none; width:200px; height:200px; background:orange; position: fixed; top:0; right: 0;}
      #box4{display:none; width:200px; height:200px; background:pink; position: fixed; top:0; right: 0;}
    </style>
  </head>
  
  <body>
    <h1>Some heading</h1>
    <p>Some content</p>
    <div id="box1"></div>
    <div id="box2"></div>
    <div id="box3"></div>
    <div id="box4"></div>
    <script>
      $.chain = function() {
        var promise = $.Deferred().resolve().promise();
        jQuery.each( arguments, function() {
          promise = promise.pipe( this );
        });
        return promise;
      };  
        
      function scrollTop(){
        if(typeof pageYOffset!= 'undefined'){
          return pageYOffset;
        }
        else{
          var b = document.body; //IE 'quirks'
          var d = document.documentElement; //IE with doctype
          d = (d.clientHeight)? d : b;
          return d.scrollTop;
        }
      }    
          
      $(window).on("scroll", function(){
        if(scrollTop() >= 500){
        $(window).off("scroll");
          var animations = $.chain(function() {
            return $('#box1').fadeIn('slow').delay(2000).fadeOut('slow');
          }, function() {
            return $('#box2').fadeIn('slow').delay(2000).fadeOut('slow');
          }, function() {
            return $('#box3').fadeIn('slow').delay(2000).fadeOut('slow');
          }, function() {
            return $('#box4').fadeIn('slow');
          });
        };
      });
    </script>
  </body>
</html>

I’m sure you’ll agree, that’s a lot more readable.

Just looking at the code from SO and realized that deferred.pipe() is deprecated as of jQuery 1.8.

It might be a better idea to use deferred.then() instead:

promise = promise.then(this);

Hi, thanks for you help
will try to implement it

but what is

$.chain = function() {
var promise = $.Deferred().resolve().promise();
jQuery.each( arguments, function() {
promise = promise.pipe( this );
});
return promise;
};

?

Maybe it’ll help to remove the $.chain function for a minute.

Another way of writing the same thing without this function would be:

$('#box1').fadeIn('slow').delay(2000).fadeOut('slow').promise()
.then(function(){ return $('#box2').fadeIn('slow').delay(2000).fadeOut('slow'); })
.then(function(){ return $('#box3').fadeIn('slow').delay(2000).fadeOut('slow'); })
.then(function(){ return $('#box4').fadeIn('slow'); });

What we are doing here is selecting the <div> element with an id of “box1”, fading it in, waiting two seconds then fading it out:

$('#box1').fadeIn('slow').delay(2000).fadeOut('slow')

Then we are using the promise() method to return a dynamically generated Promise that is resolved as soon as the fadeOut is complete.

$('#box1').fadeIn('slow').delay(2000).fadeOut('slow').promise()

We then use the .then() method to add more handlers to be called once the Promise has been resolved (immediately in this case).

This means that the animations are carried out one after the other without the need for deeply nested callbacks we saw earlier.

The $.chain() function from the SO post was just a means to tidy this code up a little further.

Using this function, the code would become:

$.chain(
  function(){ return $('#box1').fadeIn('slow').delay(2000).fadeOut('slow'); },
  function(){ return $('#box2').fadeIn('slow').delay(2000).fadeOut('slow'); },
  function(){ return $('#box3').fadeIn('slow').delay(2000).fadeOut('slow'); },
  function(){ return $('#box4').fadeIn('slow'); }
);

Removing the need for the .promise() method call and the .then() method calls.

The way the $.chain() function works is as follows:

var promise = $.Deferred().resolve().promise();

This creates a new Deferred object, resolves it immediately, calls its promise() method and assigns the resultant Promise to a variable of the same name.

Then we iterate over the arguments passed to the function using arguments, an array-like object which in out case will contain the four functions we passed in.

As of jQuery 1.8 .pipe() is deprecated and we should use .then():

jQuery.each( arguments, function() {
  promise = promise.then( this );
});

Here all we are doing is adding handlers to the Promise we created previously, before finally returning it.

What I overlooked previously, was that the code on SO assigns the result of the call to $.chain() to a variable (animations), so that a final callback can be invoked once all of the animations have taken place (using $.when()).

I’ve updated the code to reflect this:

<!DOCTYPE HTML>
<html>
  <head>
    <!--http://www.sitepoint.com/forums/showthread.php?1052208-How-to-trigger-animation-after-user-scroll-to-N-points-jQuery-waypoints-with-multip-->
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Callback hell!</title>
    <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
    <style>
      body{height:3000px;}
      #box1{display:none; width:200px; height:200px; background:red; position: fixed; top:0; right: 0;}
      #box2{display:none; width:200px; height:200px; background:blue; position: fixed; top:0; right: 0;}
      #box3{display:none; width:200px; height:200px; background:orange; position: fixed; top:0; right: 0;}
      #box4{display:none; width:200px; height:200px; background:pink; position: fixed; top:0; right: 0;}
    </style>
  </head>
  
  <body>
    <h1>Some heading</h1>
    <p>Some content</p>
    <div id="box1"></div>
    <div id="box2"></div>
    <div id="box3"></div>
    <div id="box4"></div>
    <script>
      $.chain = function() {
        var promise = $.Deferred().resolve().promise();
        jQuery.each( arguments, function() {
          promise = promise.pipe( this );
        });
        return promise;
      };  
        
      function scrollTop(){
        if(typeof pageYOffset!= 'undefined'){
          return pageYOffset;
        }
        else{
          var b = document.body; //IE 'quirks'
          var d = document.documentElement; //IE with doctype
          d = (d.clientHeight)? d : b;
          return d.scrollTop;
        }
      }    
          
      $(window).on("scroll", function(){
        if(scrollTop() >= 500){
        $(window).off("scroll");
          var animations = 
            $.chain(
              function(){ return $('#box1').fadeIn('slow').delay(2000).fadeOut('slow'); }, 
              function(){ return $('#box2').fadeIn('slow').delay(2000).fadeOut('slow'); }, 
              function(){ return $('#box3').fadeIn('slow').delay(2000).fadeOut('slow'); }, 
              function(){ return $('#box4').fadeIn('slow'); }
            );
          
          $.when( animations ).done(function() {
            alert("All done!");
          });           
        };
      });
    </script>
  </body>
</html>

Hey, I very appreciate your answer and help!
How can I say thanks in this forum? I can’t find anything to voteup your reputation or etc.

Okay, about the code. It’s almost there!!! Without you, I couldn’t make it man!
But, now my HTML markup looks nasty with a lot of classes added
Because I .addClass(‘FadeUp’); and etc to them, so how could I remove the class after they are animated?

I’m afraid this is not currently possible.
There is some talk of a system whereby people can “thank” each other, but this didn’t get implemented yet.
Thank you for asking, though.

Shouldn’t be too difficult.
You could try jQuery’s removeClass() method.

Alternatively, post the code you have and maybe there’s another way to go about things.

Ok, here is my code



var howSection = $('div#how'),
      appIcon = $('img.m1'),
      motionOne = appIcon.width() / 2,
      motionTwo = $('.m2').width() / 2,
      pieTimer = $('#pietimer'),
      animationCollet1= $('.animation-collection-1')
      controlLeft = $('div.space-control-left'),
      controlRight = $('div.space-control-right'),
      fixCircle = $('.animate-fix-circle'),
      stepOne = $('.step-1'),
      stepTwo = $('.step-2'),
      stepThree = $('.step-3'),
      creditCard = $('.credit-card');

// ............. start the animation and chaining

var animation =
        $.chain(
          function(){ return  stepOne.addClass('animated anim05 fadeInLeft').delay(600); }, 
          function(){ return  pieTimer.polartimer('start').delay(2510); },  
          function(){ return  appIcon.addClass('animated anim1 bounce').delay(1000); }, 
          function(){ return  appIcon.addClass('fadeOutLeft').delay(800) }, 
          function(){ return  fixCircle.addClass('animated anim05 fadeOut').delay(200) },
          function(){ return  $('.animation-collection-1').addClass('animated anim1 fadeOut').delay(500) },
          function(){ return  stepOne.removeClass('active').delay(500) },
          function(){ return  stepTwo.addClass('animated anim05 fadeInRight active').delay(600); }, 
          function(){ return  $('.iphone-potrait').addClass('animated anim1 fadeInUp').delay(1100) },
          function(){ return  creditCard.addClass('animated anim1 fadeIn').delay(1200) },
          function(){ return  creditCard.addClass('rotate').delay(1200) },
          function(){ return  stepTwo.removeClass('active').delay(500) },
          function(){ return  stepThree.addClass('animated anim05 fadeInLeft active').delay(600); },
          function(){ return  stepThree.removeClass('active').delay(500) },
          function(){ return  $('.step-4').addClass('animated anim05 fadeInRight active').delay(600); }
          
          
        );

Sorry, I meant the HTML, too.
That way I can run it on my computer and see if any improvements can be made, for example to the way in which the script selects the elements to animate, which in turn would mean tidier HTML with less class names.

What would be perfect would be if you could either post a link to a demo page, or use this template:


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
	
<style>

[COLOR="#FF0000"][I]your CSS here[/I][/COLOR]

</style>
	
</head>
<body>

[COLOR="#FF0000"][I]your HTML here[/I][/COLOR]

</body>

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script>

[COLOR="#FF0000"][I]your JS here[/I][/COLOR]

</script
</html>

will let you know when the website is up