32

I have added CSS animations to various div elements on my html page.But all the animations played at the same time & i can't see the animations at the bottom of the page.How can i make them play while i scroll down the page ?

Roko C. Buljan
  • 164,703
  • 32
  • 260
  • 278
Ajith
  • 431
  • 2
  • 5
  • 10
  • 1
    Use JS, and if an element is *in Viewport* than add a class to all that elements. That class should trigger CSS3 animations. – Roko C. Buljan Dec 13 '14 at 18:41

3 Answers3

53

Using IntersectionObserver API

The IntersectionObserver API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

Here's an example that triggers a classList toggle when an Element is in viewport:

const inViewport = (entries, observer) => {
  entries.forEach(entry => {
    entry.target.classList.toggle("is-inViewport", entry.isIntersecting);
  });
};

const Obs = new IntersectionObserver(inViewport);
const obsOptions = {}; //See: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options

// Attach observer to every [data-inviewport] element:
const ELs_inViewport = document.querySelectorAll('[data-inviewport]');
ELs_inViewport.forEach(EL => {
  Obs.observe(EL, obsOptions);
});
[data-inviewport] { /* THIS DEMO ONLY */
  width:100px; height:100px; background:#0bf; margin: 150vh 0; 
}

/* inViewport */

[data-inviewport="scale-in"] { 
  transition: 2s;
  transform: scale(0.1);
}
[data-inviewport="scale-in"].is-inViewport { 
  transform: scale(1);
}

[data-inviewport="fade-rotate"] { 
  transition: 2s;
  opacity: 0;
}
[data-inviewport="fade-rotate"].is-inViewport { 
  transform: rotate(180deg);
  opacity: 1;
}
Scroll down...
<div data-inviewport="scale-in"></div>
<div data-inviewport="fade-rotate"></div>

Observer Options

To define another parent reference element use the root option inside the Observable options Object. At your disposition there's also rootMargin and the super useful threshold option

const obsOptions = {
  // Default is null (Browser viewport). Set a specific parent element:
  root: document.querySelector('#someSpecificParent'),
  // add 40px inner "margin" area at which the observer starts to calculate:
  rootMargin: '40px', 
  // Default is 0.0 meaning the callback is called as soon 1 pixel is inside the viewport.  
  // Set to 1.0 to trigger a callback when 100% of the target element is inside the viewport,   
  // or i.e: 0.5 when half of the target element is visible:
  threshold: 0.5, 
};

See another interesting use case that uses the IntersectionObserver API's threshold option.

Additional read:


Using the native IntersectionObserver API is the most performant way to tackle this problem.
If you want an idea on how we tackled similar needs in the past see this answer with a small custom plugin as an example.

Roko C. Buljan
  • 164,703
  • 32
  • 260
  • 278
  • Sorry, I created a .js file with the plugin above -> Added it to my index.html -> Edited my .css file by adding .triggeredCSS3 to the .example_animations -> BUT now none of the animations work !! @Roko C. Buljan – Ajith Dec 13 '14 at 19:17
  • 1
    Yeah,I included the plugin – Ajith Dec 13 '14 at 19:21
  • I tried those things,but still not working !! @Roko C. Buljan – Ajith Dec 13 '14 at 19:32
  • @Ajith I'm not sure what might go wrong, one last thought (without seeing your-end code) is that you might want to wrap the JS above in document.ready: `$(function(){ /*box inviewport code here*/ });` – Roko C. Buljan Dec 13 '14 at 19:33
  • @Ajith have you tried the new example? Also, can you create a jsBin demo so I can see what's wrong with your code? thanks – Roko C. Buljan Dec 13 '14 at 19:38
  • I don't know,but still not working.May be something went wrong somewhere.Any way thank you !! :) @ Roko C. Buljan – Ajith Dec 13 '14 at 19:39
  • Cannot help unless you show some code. No problem. Happy coding. – Roko C. Buljan Dec 13 '14 at 19:40
  • I am beginner,this is the link to my complete html folder,you will get the html ,css & js in that.Please do check & if you have enough time ,let me know what went wrong :) https://www.dropbox.com/s/6td9lylk1vak5bz/i%27m.rar?dl=0 @ Roko C. Buljan – Ajith Dec 13 '14 at 19:50
  • @Ajith you're targeting elements with class `$(".box")` but in your HTML you don't have such class defined. I've added those classes but no animation so far... have not the time to go trough your whole code... See again my examples and start from there. Try to figure out what's wrong. I think its' some positioning issue... but I'm not sure. – Roko C. Buljan Dec 14 '14 at 04:43
  • Thank You for having time to look at my page @Roko C. Buljan ,Yeah i'm trying to find out what's wrong.I'll let you know if i find it. – Ajith Dec 14 '14 at 04:54
  • 1
    @RokoC.Buljan Do you know how you would also remove the class so that when you scrolled back it would trigger the animation again? – probablybest Feb 17 '15 at 14:39
  • 1
    @probablybest simply use `$(this).toggleClass("triggeredCSS3", !!px );` http://jsfiddle.net/RokoCB/tw6g2oeu/16/ – Roko C. Buljan Feb 19 '15 at 13:33
  • On some web-browsers `$(window).height()` returns the document height instead of the viewport height unless you add a strict doctype as stated [here](http://stackoverflow.com/questions/12103208/jquery-window-height-is-returning-the-document-height#answer-12902057). Maybe this is helpful to someone, as it took me sometime to figure that out. – javijuol Mar 20 '15 at 10:38
  • @javijuol ` ` is all it takes and since HTML5 I've never seen anyone use anything else. – Roko C. Buljan Mar 20 '15 at 13:56
  • @RokoC.Buljan just saying that if whatever reason you forget to put any DOCTYPE your web browser will render in quirks mode and so it will not work properly. Anyway, you should always add the DOCTYPE... – javijuol Mar 20 '15 at 14:26
  • Wow! Nice functionality! Excited to try it! – tonejac May 07 '17 at 18:47
22

Still Javascript, but with this version you don't need to listen on scroll events. Speed and performace is much better than check each time if an object is in the viewport.

Check this: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

With Intersection Observer, you can define a callback when an element is visible.

Options:
root: null << Set to null if you want it inside your viewport (visible area)
threshold: 0.3 << means 30% visibility. If you set 0.3, the callback is called once when the visibility reach at least 30% and once it is visible for less than 30%.

function callbackFunc(entries, observer)
{
  entries.forEach(entry => {
    var txt = entry.target.id + " visibility: " + entry.isIntersecting;
    
    document.getElementById('log').appendChild(document.createTextNode(txt));
    document.getElementById('log').appendChild(document.createElement("br"));
  });
}

let options = {
    root: null,
    rootMargin: '0px',
    threshold: 0.3
  };

let observer = new IntersectionObserver(callbackFunc, options);

observer.observe(document.getElementById('firstBlock'));
observer.observe(document.getElementById('secondBlock'));
#firstBlock {
  width: 50vw;
  height: 80vh;
  background: red;
}

#secondBlock {
  width: 50vw;
  height: 80vh;
  background: blue;
}

#log {
  width: 200px;
  height: 80vh;
  position: fixed;
  right: 0px;
  top: 10px;
  overflow: auto;
}
First Block:
<div id='firstBlock'> </div>
<br><br><br>
Second Block:
<div id='secondBlock'> </div>
<div id='log'>Log: </div>
Adriano
  • 1,075
  • 10
  • 23
  • 5
    I feel like this should be accepted answer right now. The older example still works but is pretty outdated. – Maroš Beťko Jul 10 '20 at 09:49
  • 1
    This should be the correct answer and shows good practices, avoids executing functions thousands of times. – Jackal Aug 07 '20 at 17:00
  • What about deleting the second block visibility text when you scroll back to the first block? Seems like it shouldn't be too difficult... – F. Certainly. Aug 09 '20 at 20:34
0

Another approach is using a scroll event listener

document.addEventListener("DOMContentLoaded", function(event) {
    document.addEventListener("scroll", function(event) {
        const animatedBoxes = document.getElementsByClassName("animated-box");
        const windowOffsetTop = window.innerHeight + window.scrollY;

        Array.prototype.forEach.call(animatedBoxes, (animatedBox) => {
            const animatedBoxOffsetTop = animatedBox.offsetTop;

            if (windowOffsetTop >= animatedBoxOffsetTop) {
                addClass(animatedBox, "fade-in");
            }
        });
    });
});

function addClass(element, className) {
    const arrayClasses = element.className.split(" ");
    if (arrayClasses.indexOf(className) === -1) {
        element.className += " " + className;
    }
}
.animated-box {
  width: 150px;
  height: 150px;
  margin-top: 100vh;
  background: blue;
}

.fade-in {
    -webkit-animation: fade-in 1.2s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
            animation: fade-in 1.2s cubic-bezier(0.390, 0.575, 0.565, 1.000) both;
}

 @-webkit-keyframes fade-in {
  0% {
    -webkit-transform: translateY(50px);
            transform: translateY(50px);
    opacity: 0;
  }
  100% {
    -webkit-transform: translateY(0);
            transform: translateY(0);
    opacity: 1;
  }
}
@keyframes fade-in {
  0% {
    -webkit-transform: translateY(50px);
            transform: translateY(50px);
    opacity: 0;
  }
  100% {
    -webkit-transform: translateY(0);
            transform: translateY(0);
    opacity: 1;
  }
}
<div>
  Start scrolling down...
  
  <div class="animated-box">

  </div>
        
  <div class="animated-box">
        
  </div>

  <div class="animated-box">
  
  </div>
  
  <div class="animated-box">
  
  </div>
  
  <div class="animated-box">
  
  </div>
</div>
Luca Pizzini
  • 1,805
  • 1
  • 3
  • 14