22

I'm trying to get a series of elements to fade in on scroll down when they are fully visible in the window. If I keep scrolling down, I do not want them to fade out, but if I scroll up, I do want them to fade out.

This is the closest jsfiddle I've found. http://jsfiddle.net/tcloninger/e5qaD/

$(document).ready(function() {

/* Every time the window is scrolled ... */
$(window).scroll( function(){

    /* Check the location of each desired element */
    $('.hideme').each( function(i){

        var bottom_of_object = $(this).position().top + $(this).outerHeight();
        var bottom_of_window = $(window).scrollTop() + $(window).height();

        /* If the object is completely visible in the window, fade it it */
        if( bottom_of_window > bottom_of_object ){

            $(this).animate({'opacity':'1'},500);

        }    
    }); 
}); 
});

It does exactly what I want on scroll down, but I also want the elements to fade out if I scroll up past them.

I tried this with no luck.

            if( bottom_of_window > bottom_of_object ){

                $(this).animate({'opacity':'1'},500);  

            } else {

               $(this).animate({'opacity':'0'},500); }

Thanks so much for taking the time to look at this.

minimographite
  • 333
  • 1
  • 3
  • 7

4 Answers4

70

The reason your attempt wasn't working, is because the two animations (fade-in and fade-out) were working against each other.

Right before an object became visible, it was still invisible and so the animation for fading-out would run. Then, the fraction of a second later when that same object had become visible, the fade-in animation would try to run, but the fade-out was still running. So they would work against each other and you would see nothing.

Eventually the object would become visible (most of the time), but it would take a while. And if you would scroll down by using the arrow-button at the button of the scrollbar, the animation would sort of work, because you would scroll using bigger increments, creating less scroll-events.


Enough explanation, the solution (JS, CSS, HTML):

$(window).on("load",function() {
  $(window).scroll(function() {
    var windowBottom = $(this).scrollTop() + $(this).innerHeight();
    $(".fade").each(function() {
      /* Check the location of each desired element */
      var objectBottom = $(this).offset().top + $(this).outerHeight();
      
      /* If the element is completely within bounds of the window, fade it in */
      if (objectBottom < windowBottom) { //object comes into view (scrolling down)
        if ($(this).css("opacity")==0) {$(this).fadeTo(500,1);}
      } else { //object goes out of view (scrolling up)
        if ($(this).css("opacity")==1) {$(this).fadeTo(500,0);}
      }
    });
  }).scroll(); //invoke scroll-handler on page-load
});
.fade {
  margin: 50px;
  padding: 50px;
  background-color: lightgreen;
  opacity: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

<div>
  <div class="fade">Fade In 01</div>
  <div class="fade">Fade In 02</div>
  <div class="fade">Fade In 03</div>
  <div class="fade">Fade In 04</div>
  <div class="fade">Fade In 05</div>
  <div class="fade">Fade In 06</div>
  <div class="fade">Fade In 07</div>
  <div class="fade">Fade In 08</div>
  <div class="fade">Fade In 09</div>
  <div class="fade">Fade In 10</div>
</div>
(fiddle: http://jsfiddle.net/eLwex993/2/)
  • I wrapped the fade-codeline in an if-clause: if ($(this).css("opacity")==0) {...}. This makes sure the object is only faded in when the opacity is 0. Same goes for fading out. And this prevents the fade-in and fade-out from working against each other, because now there's ever only one of the two running at one time on an object.
  • I changed .animate() to .fadeTo(). It's jQuery's specialized function for opacity, a lot shorter to write and probably lighter than animate.
  • I changed .position() to .offset(). This always calculates relative to the body, whereas position is relative to the parent. For your case I believe offset is the way to go.
  • I changed $(window).height() to $(window).innerHeight(). The latter is more reliable in my experience.
  • Directly after the scroll-handler, I invoke that handler once on page-load with $(window).scroll();. Now you can give all desired objects on the page the .fade class, and objects that should be invisible at page-load, will be faded out immediately.
  • I removed #container from both HTML and CSS, because (at least for this answer) it isn't necessary. (I thought maybe you needed the height:2000px because you used .position() instead of .offset(), otherwise I don't know. Feel free of course to leave it in your code.)

UPDATE

If you want opacity values other than 0 and 1, use the following code:

$(window).on("load",function() {
  function fade(pageLoad) {
    var windowBottom = $(window).scrollTop() + $(window).innerHeight();
    var min = 0.3;
    var max = 0.7;
    var threshold = 0.01;
    
    $(".fade").each(function() {
      /* Check the location of each desired element */
      var objectBottom = $(this).offset().top + $(this).outerHeight();
      
      /* If the element is completely within bounds of the window, fade it in */
      if (objectBottom < windowBottom) { //object comes into view (scrolling down)
        if ($(this).css("opacity")<=min+threshold || pageLoad) {$(this).fadeTo(500,max);}
      } else { //object goes out of view (scrolling up)
        if ($(this).css("opacity")>=max-threshold || pageLoad) {$(this).fadeTo(500,min);}
      }
    });
  } fade(true); //fade elements on page-load
  $(window).scroll(function(){fade(false);}); //fade elements on scroll
});
.fade {
  margin: 50px;
  padding: 50px;
  background-color: lightgreen;
  opacity: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

<div>
  <div class="fade">Fade In 01</div>
  <div class="fade">Fade In 02</div>
  <div class="fade">Fade In 03</div>
  <div class="fade">Fade In 04</div>
  <div class="fade">Fade In 05</div>
  <div class="fade">Fade In 06</div>
  <div class="fade">Fade In 07</div>
  <div class="fade">Fade In 08</div>
  <div class="fade">Fade In 09</div>
  <div class="fade">Fade In 10</div>
</div>
(fiddle: http://jsfiddle.net/eLwex993/3/)
  • I added a threshold to the if-clause, see explanation below.
  • I created variables for the threshold and for min/max at the start of the function. In the rest of the function these variables are referenced. This way, if you ever want to change the values again, you only have to do it in one place.
  • I also added || pageLoad to the if-clause. This was necessary to make sure all objects are faded to the correct opacity on page-load. pageLoad is a boolean that is send along as an argument when fade() is invoked.
    I had to put the fade-code inside the extra function fade() {...}, in order to be able to send along the pageLoad boolean when the scroll-handler is invoked.
    I did't see any other way to do this, if anyone else does, please leave a comment.

Explanation:
The reason the code in your fiddle didn't work, is because the actual opacity values are always a little off from the value you set it to. So if you set the opacity to 0.3, the actual value (in this case) is 0.300000011920929. That's just one of those little bugs you have to learn along the way by trail and error. That's why this if-clause won't work: if ($(this).css("opacity") == 0.3) {...}.

I added a threshold, to take that difference into account: == 0.3 becomes <= 0.31.
(I've set the threshold to 0.01, this can be changed of course, just as long as the actual opacity will fall between the set value and this threshold.)

The operators are now changed from == to <= and >=.


UPDATE 2:

If you want to fade the elements based on their visible percentage, use the following code:

$(window).on("load",function() {
  function fade(pageLoad) {
    var windowTop=$(window).scrollTop(), windowBottom=windowTop+$(window).innerHeight();
    var min=0.3, max=0.7, threshold=0.01;
    
    $(".fade").each(function() {
      /* Check the location of each desired element */
      var objectHeight=$(this).outerHeight(), objectTop=$(this).offset().top, objectBottom=$(this).offset().top+objectHeight;
      
      /* Fade element in/out based on its visible percentage */
      if (objectTop < windowTop) {
        if (objectBottom > windowTop) {$(this).fadeTo(0,min+((max-min)*((objectBottom-windowTop)/objectHeight)));}
        else if ($(this).css("opacity")>=min+threshold || pageLoad) {$(this).fadeTo(0,min);}
      } else if (objectBottom > windowBottom) {
        if (objectTop < windowBottom) {$(this).fadeTo(0,min+((max-min)*((windowBottom-objectTop)/objectHeight)));}
        else if ($(this).css("opacity")>=min+threshold || pageLoad) {$(this).fadeTo(0,min);}
      } else if ($(this).css("opacity")<=max-threshold || pageLoad) {$(this).fadeTo(0,max);}
    });
  } fade(true); //fade elements on page-load
  $(window).scroll(function(){fade(false);}); //fade elements on scroll
});
.fade {
  margin: 50px;
  padding: 50px;
  background-color: lightgreen;
  opacity: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

<div>
  <div class="fade">Fade In 01</div>
  <div class="fade">Fade In 02</div>
  <div class="fade">Fade In 03</div>
  <div class="fade">Fade In 04</div>
  <div class="fade">Fade In 05</div>
  <div class="fade">Fade In 06</div>
  <div class="fade">Fade In 07</div>
  <div class="fade">Fade In 08</div>
  <div class="fade">Fade In 09</div>
  <div class="fade">Fade In 10</div>
</div>
(fiddle: http://jsfiddle.net/eLwex993/5/)
myfunkyside
  • 3,890
  • 1
  • 17
  • 32
  • @minimographite - I added an update to my answer for other opacity values than `0` or `1`. And thanks for your appreciation, that always makes a poster happy! – myfunkyside Nov 02 '14 at 21:32
  • How would this be modified so you could fade each element within each row 1 by 1 from left to right instead of just the full row all at once when the row comes into visibility? – Camrin Parnell Sep 16 '15 at 15:04
  • @CamrinParnell - I think I would put (the ID of) every element of the row into an array, and then go over every element in that array in a for-loop, using the `fadeTo()` line on every element, but with a shorter fade-duration. – myfunkyside Sep 19 '15 at 22:04
  • Is this working on the footer? I have tried, this works perfectly but not on the footer – sm.ali Apr 02 '20 at 10:57
  • 1
    @sm.ali there may be two things preventing your code from working. **1)** Be sure to give your footer the `fade` class, this code only works on elements with that class. **2)** Change `if (objectBottom < windowBottom) {` to `if (objectBottom <= windowBottom) {`; because your footer is (probably) stuck to the bottom of the page, the footer-bottom will never be less than the window-bottom. By changing `<` to `<=`, the code will also work when the footer-bottom is equal to the window-bottom. Alternatively, you could work with `objectTop` to maybe create a better effect, I'll leave that up to you. – myfunkyside Apr 03 '20 at 14:54
  • Hey I am having an issue where on page load, everything is full opacity, but as soon as I scroll, the correct fade occurs. Would you have any suggestions? – irahorecka May 17 '20 at 20:01
  • @IraH. The `.scroll(); //invoke scroll-handler on page-load` at the end of the function should take care of that.. No, sorry, no other suggestions at this time. – myfunkyside May 18 '20 at 15:11
  • 1
    @myfunkyside Update 2 is a very great answer! I thought I could point out that in the 3rd line of code you can add "padding" to your window borders, e.g., windowTop+=150, windowBottom-=300. This will delay the fadeIn until the .fade element is more toward the middle of the screen. – Jim22150 Dec 15 '21 at 13:39
6

I tweaked your code a bit and made it more robust. In terms of progressive enhancement it's probaly better to have all the fade-in-out logic in JavaScript. In the example of myfunksyde any user without JavaScript sees nothing because of the opacity: 0;.

    $(window).on("load",function() {
    function fade() {
        var animation_height = $(window).innerHeight() * 0.25;
        var ratio = Math.round( (1 / animation_height) * 10000 ) / 10000;

        $('.fade').each(function() {

            var objectTop = $(this).offset().top;
            var windowBottom = $(window).scrollTop() + $(window).innerHeight();

            if ( objectTop < windowBottom ) {
                if ( objectTop < windowBottom - animation_height ) {
                    $(this).html( 'fully visible' );
                    $(this).css( {
                        transition: 'opacity 0.1s linear',
                        opacity: 1
                    } );

                } else {
                    $(this).html( 'fading in/out' );
                    $(this).css( {
                        transition: 'opacity 0.25s linear',
                        opacity: (windowBottom - objectTop) * ratio
                    } );
                }
            } else {
                $(this).html( 'not visible' );
                $(this).css( 'opacity', 0 );
            }
        });
    }
    $('.fade').css( 'opacity', 0 );
    fade();
    $(window).scroll(function() {fade();});
});

See it here: http://jsfiddle.net/78xjLnu1/16/

Cheers, Martin

hobbeshunter
  • 629
  • 6
  • 6
4

I know it's late, but I take the original code and change some stuff to control easily the css. So I made a code with the addClass() and the removeClass()

Here the full code : http://jsfiddle.net/e5qaD/4837/

        if( bottom_of_window > bottom_of_object ){
            $(this).addClass('showme');
       }
        if( bottom_of_window < bottom_of_object ){
            $(this).removeClass('showme');
  • I like this option is work better!! How can change the bottom_of_object to have it on the top of the bottom? – Marco Romano Jan 25 '17 at 17:01
  • little code and works perfect. I added to on my portfolio website to fade in out element. well it's becomes better if you use transition property to smooth fade in on scroll. – Chetan Nada Sep 29 '21 at 18:22
0

Sorry this is and old thread but some people would still need this I guess,

Note: I achieved this using Animate.css library for animating the fade.

I used your code and just added .hidden class (using bootstrap's hidden class) but you can still just define .hidden { opacity: 0; }

$(document).ready(function() {

/* Every time the window is scrolled ... */

$(window).scroll( function(){

/* Check the location of each desired element */
$('.hideme').each( function(i){

    var bottom_of_object = $(this).position().top + $(this).outerHeight();
    var bottom_of_window = $(window).scrollTop() + $(window).height();


    /* If the object is completely visible in the window, fade it it */
    if( bottom_of_window > bottom_of_object ){

        $(this).removeClass('hidden');
        $(this).addClass('animated fadeInUp');
    }    else {
            $(this).addClass('hidden');
               }

}); 
}); 
});

Another Note: Applying this to containers might cause it to be glitchy.

Valumin
  • 13
  • 3