Event.preventDefault() tells the browser not to do the default predefined action for that event, such as navigating to a page or submitting the enclosing form, etc. It does not necessarily prevent events from firing.
Also, there is a difference between the wheel event and the scroll event. The wheel event is fired when the user rotates a wheel button, and the scroll event is fired when the target's scrollTop or scrollLeft property is changed due to the scroll position being changed.
When the user rotates the wheel button, the wheel event is fired before any scroll events that could be fired. However, the wheel event might not result in any scroll event simply because the pointer is not hovering on any element or the element is not scrollable at the moment.
To aggregate quickly repeated function calls to the event handler, you can debounce the event handler function. The idea is to wait a certain amount before committing to the action. When a function is debounced, it becomes a new function, when called, sets off a timer that calls the wrapped function inside. The timer is reset and restarted when debounced function is called again. Look at the example diagram below.
© Ilya Kantor(https://github.com/javascript-tutorial/en.javascript.info, licensed under CC-BY-NC)
The function f is a debounced function with a 1000ms timeout duration and is called at time instants 0, 200ms, and 500ms with arguments a, b, and c, respectively. Because f is debounced, calls f(a) and f(b) were "not committed/ignored" because there was another call to f within a 1000ms duration. Still, call f(c) was "committed/accepted" at the time instant 1500ms because no further call followed within 1000ms.
To implement this, you can use the setTimeout and clearTimeout functions. The setTimeout function accepts an action(code or function to execute) and delay in milliseconds, then returns a timer ID in integer. The given action will be executed when the timer expires without being canceled.
const timerId = setTimeout(action, delay)
The clearTimeout function could then be used to destroy the timer with a given ID.
clearTimeout(timerId)
Following simple debounce implementation could be used:
// Default timeout is set to 1000ms
function debounce(func, timeout = 1000) {
// A slot to save timer id for current debounced function
let timer
// Return a function that conditionally calls the original function
return (...args) => {
// Immediately cancel the timer when called
clearTimeout(timer)
// Start another timer that will call the original function
// unless canceled by following call
timer = setTimeout(() => {
// Pass all arguments and `this` value
func.apply(this, args)
}, timeout)
}
}
Read more: Default parameters, Rest parameters, Function.apply(), this keyword
To use is quite simple:
eventTarget.addEventListener('wheel', debounce((e) => {
console.log('wheel', e)
}))
This will limit console.log calls to whenever a wheel event has not been fired in a second.
Live example:
function debounce(f, d = 99, t) {
return (...a) => {
clearTimeout(t)
t = setTimeout(() => {
f.apply(this, a)
}, d)
}
}
document.addEventListener('wheel', debounce((_) => {
console.log('wheel')
}))
A more modern approach uses Promise on top of this idea.