3

The scroll events trigger way more often than I want them to. All it should do is give the .swap class to the next Sibling if you scroll down (followed by an animation) and get rid of the .swap class of the current Element. Same thing just for upwards. What happens now is that it just basically goes to the last element and the first one if scrolled down/up. It is a one-pager which just switches in between different divs.

I've tried Debouncing, Throttling, and requestAnimationFrame but haven't been successful so far. For example: https://css-tricks.com/debouncing-throttling-explained-examples/

Thanks a lot!

HTML:

<div class="div1 swap">
  <h1>Hello</h1>
</div>
<div class="div2">
  <h1>Stack</h1>
</div>
<div class="div3">
  <h1>Overflow</h1>
</div>
<div class="div4">
  <h1>Please</h1>
</div>
<div class="div5">
  <h1>Help</h1>
</div>

CSS:

.div1, .div2, .div3, .div4, .div5{
  visibility: hidden;
  position: fixed;
}
.swap{
  visibility: visible;
  -webkit-animation: slide-in-left .8s ease-out both;
          animation: slide-in-left .8s ease-out both;
}

JS:

window.onscroll = function(event) {
  var aktiv = document.querySelector('.swap');
  if(this.oldScroll > this.scrollY){
    console.log("Up");
    aktiv.previousElementSibling.classList.toggle('swap')
    aktiv.classList.toggle('swap')
  }
  else{
    console.log("Down");
    aktiv.nextElementSibling.classList.toggle('swap')
    aktiv.classList.toggle('swap')
  }
  this.oldScroll = this.scrollY;
}
  • 1
    AS you've asked about *debouncing* can you include your debouncing attempt? Essentially, debouncing is the same as what you've got, but if it receives an event multiple times within a timeframe, only the last is run - ie when it receives an event, it waits to see if another one is going to arrive before triggering the debounced action. https://stackoverflow.com/tags/debouncing/info – freedomn-m Oct 04 '19 at 08:00

2 Answers2

1

Add some threshold for scroll distance, as now even if you scroll 1px. you show next element.

window.onscroll = function(event) {
  var aktiv = document.querySelector('.swap');
  var diff = this.oldScroll - this.scrollY;
  var delta = 100; // Or some element height

  // If scolled too little - do nothing
  if (Match.abs(diff) <= delta) {
     return;
  }

  if(diff < 0){
    console.log("Up");
    aktiv.previousElementSibling.classList.toggle('swap')
    aktiv.classList.toggle('swap')
  }
  else{
    console.log("Down");
    aktiv.nextElementSibling.classList.toggle('swap')
    aktiv.classList.toggle('swap')
  }
  this.oldScroll = this.scrollY;
}

To debounce event use setTimeout:

var timer;

window.onscroll = function (event) {
    var self = this;
    clearTimeout(timer);
    timer = setTimeout(function () {
        [...rest of code...]
    }, 50)
}
Justinas
  • 41,402
  • 5
  • 66
  • 96
0

You should not use scroll event listener for this, this is best done with Intersection Observer (IO).

If you want to trigger animations on more elements than one, watching these multiple elements via scroll event listener can result in bad performance. See for example this question. disclaimer: This question also has my answer.

To solve this in a modern, performant way it's best to use Intersection Observer (IO) for this.

With IO you can watch one (or multiple) elements and react once they come into view or if they intersect each other.

To use IO you first have to first set the options for it, then define which element(s) to watch and last to define what exactly happens once the IO triggers.

Example (Taken from here), with one minimal modification: The author removed the IO even if the animation didn't happen yet. I moved the unobserve call inside the check if element is visible.

const SELECTOR = '.watched';
const ANIMATE_CLASS_NAME = 'animated';

const animate = element => (
  element.classList.add(ANIMATE_CLASS_NAME)
);

const isAnimated = element => (
  element.classList.contains(ANIMATE_CLASS_NAME)
);

const intersectionObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach((entry) => {
    
    // when element's is in viewport,
    // animate it!
    if (entry.intersectionRatio > 0) {
      animate(entry.target);
      // remove observer after animation
      observer.unobserve(entry.target);
    }
  });
});

// get only these elements,
// which are not animated yet
const elements = [].filter.call(
  document.querySelectorAll(SELECTOR),
  element => !isAnimated(element, ANIMATE_CLASS_NAME)
);
//console.log(elements);

// start observing your elements
elements.forEach((element) => intersectionObserver.observe(element));
.h100 {
height: 100vh;
}

.watched {
 opacity: 0;
 transition: opacity .5s;
}

.watched.animated {
opacity: 1;
}
<div class="watched hidden">
I'm watched and I'm in the viewport on page load!
</div>
<div class="h100">
scroll down
</div>
<div class="watched">
I'm watched
</div>
<div class="h100">
Another element, keep scrolling
</div>
<div class="watched">
I'm also watched
</div>
cloned
  • 6,346
  • 4
  • 26
  • 38
  • As far as I've understood is that it looks for when the element is in the viewport. The thing is that all elements are always in the viewport just hidden. It's a one-pager that uses a small scroll event to trigger the next element. Sorry for not specifying that data in the description. I'll add it. Thanks a lot for the help! – fenrirmercenary Oct 04 '19 at 08:53
  • for now my example checks if an element is in the viewport. and if i understand your question correctly you will have to tweak it quite a lot to get what you want. You can maybe have a look at this, maybe it helps you: https://stackoverflow.com/questions/46478202/how-do-i-know-the-intersectionobserver-scroll-direction – cloned Oct 04 '19 at 08:59
  • Thanks for your help and effort! :) – fenrirmercenary Oct 04 '19 at 09:14