This is old but I found it when looking for an answer to pretty much the same problem.
I solved the problem for my purposes, so here's my solution in case it helps anyone else.
The problem is really to define what counts as one continuous action. Without something more concrete to work with, it's just a question of timing. It's the time between events that's the key - so the algorithm is to keep accumulating events until there's a certain gap between them. All that then remains is to figure out how big the allowed gap should be, which is solution specific. That's then the maximum delay after the user stops scrolling until they get feedback. My optimum is a quarter of a second, I'm using that as a default in the below.
Below is my JavaScript, I'm attaching the event to a div with the id 'wheelTestDiv' using jQuery but it works the same with the window object, as in the question.
It's worth noting that the below looks for any onWheel
event but only tracks the Y axis. If you need more axes, or specifically only want to count events towards the timer when there's a change in deltaY
, you'll need to change the code appropriately.
Also worth noting, if you don't need the flexibility of tracking events against different DOM objects, you could refactor the class to have static methods and properties, so there would be no need to create a global object variable. If you do need to track against different DOM objects (I do), then you may need multiple instances of the class.
"use strict";
class MouseWheelAggregater {
// Pass in the callback function and optionally, the maximum allowed pause
constructor(func, maxPause) {
this.maxAllowedPause = (maxPause) ? maxPause : 250; // millis
this.last = Date.now();
this.cummulativeDeltaY = 0;
this.timer;
this.eventFunction = func;
}
set maxPause(pauseTime) {
this.maxAllowedPause = pauseTime;
}
eventIn(e) {
var elapsed = Date.now() - this.last;
this.last = Date.now();
if ((this.cummulativeDeltaY === 0) || (elapsed < this.maxAllowedPause)) {
// Either a new action, or continuing a previous action with little
// time since the last movement
this.cummulativeDeltaY += e.originalEvent.deltaY;
if (this.timer !== undefined) clearTimeout(this.timer);
this.timer = setTimeout(this.fireAggregateEvent.bind(this),
this.maxAllowedPause);
} else {
// just in case some long-running process makes things happen out of
// order
this.fireAggregateEvent();
}
}
fireAggregateEvent() {
// Clean up and pass the delta to the callback
if (this.timer !== undefined) clearTimeout(this.timer);
var newDeltaY = this.cummulativeDeltaY;
this.cummulativeDeltaY = 0;
this.timer = undefined;
// Use a local variable during the call, so that class properties can
// be reset before the call. In case there's an error.
this.eventFunction(newDeltaY);
}
}
// Create a new MouseWheelAggregater object and pass in the callback function,
// to call each time a continuous action is complete.
// In this case, just log the net movement to the console.
var mwa = new MouseWheelAggregater((deltaY) => {
console.log(deltaY);
});
// Each time a mouse wheel event is fired, pass it into the class.
$(function () {
$("#wheelTestDiv").on('wheel', (e) => mwa.eventIn(e));
});
Web page ...
<!DOCTYPE html>
<html>
<head>
<title>Mouse over test</title>
<script src="/mouseWheelEventManager.js"></script>
</head>
<body>
<div id="wheelTestDiv" style="margin: 50px;">Wheel over here</div>
</body>
</html>