How can I prevent two onscroll event listeners that call each other from making the resulting action jerky?

I have a faux scrollbar I’ve created with JS, and a table I want it to scroll. There’s two possible scenarios:

  1. I want the scrollbar to move when I scroll the table (using a finger on mobile or two-touch on touchpad)

  2. I want the table to move when I use the scrollbar

This is what I have so far.

tableContainerDiv.addEventListener('scroll', function(e)  {
    document.getElementById('faux-scrollbar').scrollLeft = this.scrollLeft;
});

fauxScrollbar.addEventListener('scroll', function(e) {
    document.getElementById('table-container-div').scrollLeft = this.scrollLeft;
});

It works perfectly, but is predictably a bit janky, as they fire each other off repeatedly. If I comment out either listener, the effect works smoothly, albeit only one way, not both ways.

I’m not sure how to fix this, but I assume it can be fixed…?

I’ve tried setting a variable in each event listener, that gets set on the first fire to identify the originator, and checked in each listener, but tied myself in knots… I also tried e.originalEvent, before discovering that was jQuery only, and it seems the vanilla option is isTrusted always says true, for some reason.

Thank you

Answer

I suggest a different approach, instead of listening to two scroll events that fire each other continuously, you may run a single function that checks the current position of both containers and when one of them is different copy the value to the other.

This way you’ll never fire a loop:

const tableContainerDiv = document.getElementById('table-container-div');
const fauxScrollbar = document.getElementById('faux-scrollbar');

function copyScroll() {
  if (
    fauxScrollbar.scrollLeft === fauxScrollbar.oldScrollLeft
    &&
    tableContainerDiv.scrollLeft !== tableContainerDiv.oldScrollLeft
  ) {
    /// only tableContainerDiv changed value
    fauxScrollbar.scrollLeft = tableContainerDiv.scrollLeft;
  } else if (
    fauxScrollbar.scrollLeft !== fauxScrollbar.oldScrollLeft
    &&
    tableContainerDiv.scrollLeft === tableContainerDiv.oldScrollLeft
  ) {
    /// only fauxScrollbar changed value
    tableContainerDiv.scrollLeft = fauxScrollbar.scrollLeft;
  }
  
  /// save values for next recursion
  fauxScrollbar.oldScrollLeft = fauxScrollbar.scrollLeft;
  tableContainerDiv.oldScrollLeft = tableContainerDiv.scrollLeft;
  
  window.requestAnimationFrame(copyScroll);
  
  
}

copyScroll();
.container {
  width: 100px;
  height: 100px;
  overflow: scroll;
}

.container:before {
  content: "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ";
  display: block;
  width: 2000px;
}
  <div class="container" id="faux-scrollbar"></div>
  <div class="container" id="table-container-div"></div>