<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
  </head>

  <body>
    
    <div>
      <div id="viewport">
        <div class="inner">scroll me!</div>
      </div>
      
      <div>
        Mode<br>
        <input type="radio" name="mode" value="0" checked> - normal<br>
        <input type="radio" name="mode" value="1"> - limited (dhilt)<br>
        <input type="radio" name="mode" value="2"> - limited (user650881)<br>
        <input type="radio" name="mode" value="3"> - limited (willem-dhaeseleer)<br>
        <input type="radio" name="mode" value="4"> - limited (lodash debounce + leading)<br>
        <input type="radio" name="mode" value="5"> - limited (Mikk3lRo)<br>
      </div>
    </div>
    
    <div style="clear: both;"></div>
    
    <div id="info">Scroll event handling<br></div>
    
    <script src="dhilt.js"></script>
    <script src="user650881.js"></script>
    <script src="willem-dhaeseleer.js"></script>
    <script src="Mikk3lRo.js"></script>
    <script src="_script.js"></script>
  </body>

</html>

const viewportElement = document.getElementById('viewport');
const infoElement = document.getElementById('info');

let count = 0;
const processEvent = (event) => {
  count++;
  infoElement.innerHTML += ' ' + count;
  console.log('go ' + count);
  //console.log(event);
}

const DELAY = 125;

const handler = [
  processEvent, // normal
  debounceNext(processEvent, DELAY), // dhilt
  makeRateLimitedEventHandler(DELAY, processEvent), // user650881
  debounceWithDelay(processEvent, DELAY, 0), // willem-dhaeseleer
  _.debounce(processEvent, DELAY, {leading: true}), // lodash debounce + leading
  debounceish(DELAY, processEvent) //Mikk3lRo
];

const getHandler = (event) => {
  const index = document.querySelector('input[name="mode"]:checked').value;
  return handler[index](event);
}

viewportElement.addEventListener('scroll', getHandler);




















#viewport{
  height: 200px;
  width: 150px;
  overflow: scroll;
  float: left;
  margin-right: 10px;
}

.inner {
  height: 3000px;
}

#info {
  margin-top: 10px;
}
This demo is based on "Selective timeout based handling: immediate first, debounce next" topic from stackoverflow: https://stackoverflow.com/questions/46633701/selective-timeout-based-handling-immediate-first-debounce-next
//https://stackoverflow.com/a/46677791/3211932

function makeRateLimitedEventHandler(delta_ms, cb) {
    let timeoutId = 0;
    let lastEventTimestamp = 0;
    return (event) => {
        if (timeoutId) {
            clearTimeout(timeoutId);
            timeoutId = 0;
        }
        var curTime = Date.now();
        if (curTime < lastEventTimestamp + delta_ms) {
            timeoutId = setTimeout(() => cb(event), delta_ms);
        } else {
            cb(event);
        }
        lastEventTimestamp = Date.now();
    };
}
// https://stackoverflow.com/a/46699605/3211932

const debounceNext = (cb, delay) => { 
  let timer = null;
  let next = null;

  const runTimer = (delay, event) => {
    timer = setTimeout(() => {
      timer = null;
      if(next) {
        next(event);
        next = null;
        runTimer(delay);
      }
    }, delay);
  };  
  
  return (event) => {
    if(!timer) {
      cb(event);
    }
    else {
      next = cb;
      clearTimeout(timer);
    }
    runTimer(delay, event);
  }
};
// https://stackoverflow.com/a/46678116/3211932

const debounceWithDelay = (func, delay, postDelay) => {
    let postDebounceWait;
    let timeOutLeading = false;
    const debounced = _.debounce((...args) => {
        if (timeOutLeading) {
            func(...args);
        } else {
            postDebounceWait = setTimeout(() => {
                func(...args)
            }, postDelay);
        }
    }, delay, {leading: true});
    return (...args) => {
        timeOutLeading = true;
        clearTimeout(postDebounceWait);
        debounced(args);
        timeOutLeading = false;
    }
};
// https://stackoverflow.com/a/46718342/3211932

function debounceish(delta, fn) {
    let timer = null;
    return (event) => {
        if (timer === null) {
            fn(event);
            timer = setTimeout(() => timer = null, delta);
        } else {
            clearTimeout(timer);
            timer = setTimeout(() => {
                fn(event);
                timer = setTimeout(() => timer = null, delta);
            }, delta);
        }
    };
}