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.