I want to make it so that elements with the class fade
fade in when they are first visible on the screen then fade out when they leave the screen.
I want this animation to only happen once.
Below is what I have tried.
.fade {
/* transition: opacity 0.9s ease-in;*/
opacity: 0;
}
.fade.visible {
transition: opacity 0.9s ease-in;
opacity: 1;
}
window.addEventListener('scroll', fade)
function fade()
{
let animation=document.querySelectorAll('.fade');
for (let i=0; i<animation.length; i )
{
let windowheight=window.innerHeight;
let top=animation[i].getBoundingClientRect().top;
if (top < windowheight)
{
animation[i].classList.add('visible');
}
else
{
animation[i].classList.remove('visible');
}
}
}
CodePudding user response:
Use the IntersectionObserver API instead of expensive scroll listeners!
Here's an example that triggers a classList change when an Element is in viewport - based on this answer, with the only difference that this one uses classList.add
instead of classList.toggle
:
const inViewport = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("is-inviewport");
}
});
};
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] { /* FOR THIS DEMO ONLY */
width:100px; height:100px; background:#0bf; margin: 150vh 0;
}
/* inViewport */
[data-inviewport="fade"] {
transition: opacity 2s;
opacity: 0;
}
[data-inviewport="fade"].is-inviewport {
transform: rotate(180deg);
opacity: 1;
}
Scroll down...
<div data-inviewport="fade"></div>
<div data-inviewport="fade"></div>
CodePudding user response:
Other than removing the event listener (which seems like doesn’t work for you for some reason) a simple change would be to add a global boolean flag which you toggle once after performing the animation. If the flag is true, just return early from the fade function
Something like this should work or at least be pretty close
let animationHappened = false; // flag to keep track of animation
function fade() {
// only add the visible class if the animation hasn't happened yet, else return early
if (animationHappened) return;
let animation = document.querySelectorAll('.fade');
for (let i = 0; i < animation.length; i ) {
let windowheight = window.innerHeight;
let top = animation[i].getBoundingClientRect().top;
if (top < windowheight) {
animation[i].classList.add('visible');
} else {
animation[i].classList.remove('visible');
}
}
animationHappened = true;
}
This way fade
should only animate once so long as animationHappened
isn’t set to false
again anywhere else in your code.
CodePudding user response:
First things first, it's pretty dang inefficient to do querySelectorAll
on every scroll, so assuming all those elements are always there, let's just grab them all right away.
We can turn the node list into an array so we can easily remove items as we go. When an element is visible, we just add the visible class and remove it from the array. Then when the array is empty just remove the listener.
To make the items invisible again, we can push the visible items to a second array and perform a similar process. We only remove that listener after all items have faded in then faded out.
const fadeInElements = Array.from(document.querySelectorAll('.fade'));
const makeInvisible = [];
let fadeInEmpty = false;
window.addEventListener('scroll', fadeIn);
window.addEventListener('scroll', fadeOut);
function fadeIn() {
const windowheight = window.innerHeight;
for (let i = 0; i < fadeInElements.length; i ) {
const element = fadeInElements[i];
const topOfElement = element.getBoundingClientRect().top;
if (topOfElement < windowheight) {
element.classList.add('visible');
makeInvisible.push(element);
fadeInElements.splice(i, 1);
if (fadeInElements.length === 0) {
window.removeEventListener('scroll', fadeIn);
fadeInEmpty = true;
}
}
}
}
function fadeOut() {
const windowheight = window.innerHeight;
for (let i = 0; i < makeInvisible.length; i ) {
const element = makeInvisible[i];
const topOfElement = element.getBoundingClientRect().top;
if (topOfElement >= windowheight) {
element.classList.remove('visible');
makeInvisible.splice(i, 1);
if (makeInvisible.length === 0 && fadeInEmpty)
window.removeEventListener('scroll', fadeOut);
}
}
}
div {
margin-top: 100px;
}
.fade {
opacity: 0;
}
.visible {
transition: opacity 0.9s ease-in;
opacity: 1;
}
<div>1</div>
<div >2</div>
<div >3</div>
<div >4</div>
<div >5</div>
<div >6</div>
<div >7</div>
<div >8</div>
<div >9</div>
<div >10</div>