Passive Events Support
Introduction
The Issue
How many times have you yourself forgotten to make an event listener as passive
, or installed a library such as Bootstrap, jQuery or Materialize and suddenly in the Google Chrome you see:
[Violation] Added non-passive event listener to a scroll-blocking 'touchstart'
event. Consider marking event handler as passive
to make the page more responsive.
Or when running Lighthouse you get a lower score with a message:
Does not use passive listeners to improve scrolling performance
Consider marking your touch
and wheel
event listeners as passive
to improve your page's scroll performance.
What's a passive
option?
According to Official Documentation a passive
option is:
A boolean value that, if true, indicates that the function specified by listener will never call preventDefault(). If a passive listener does call preventDefault(), the user agent will do nothing other than generate a console warning.
Why is it necessary?
It is well docummented that:
According to the specification, the default value for the passive option is always false. However, this introduces the potential for event listeners handling certain touch events (among others) to block the browser's main thread while it is attempting to handle scrolling, resulting in possibly enormous reduction in performance during scroll handling.
See Improving scrolling performance with passive listeners to learn more.
The solution
Making event listeners as passive
manually could be a repetetive and time consuming experience. Also if caused by a 3rd party, modifying it's source code should never be an option!
Here comes the Passive Events Support package! This is the package that will help you debug the source, solve the issue, but also improve the performance. It is flexible and highly configurable package!
How it works
When event listener does not have a passive
option, it will be added and its value will depend on whether preventDefault()
is being called in the handler or not.
When event listener is not calling preventDefault()
and passive
option is not passed, it will add a { passive: true }
element.addEventListener('touchstart', (e) => {})
When event listener is calling preventDefault()
and passive
option are not passed, it will add a { passive: false }
element.addEventListener('touchstart', (e) => { e.preventDefault() })
When passive
or other option is passed, their values will not be overwritten
element.addEventListener('touchstart', handler)
element.addEventListener('touchstart', handler, { passive: false })
element.addEventListener('touchstart', handler, { capture: true)
element.addEventListener('touchstart', handler, { capture: false, passive: false })
Installation
yarn add passive-events-support
Usage
This package must be imported before any package or code that is causing an issue or Lighthouse warning.
import { passiveSupport } from 'passive-events-support/src/utils'
passiveSupport({})
<script>
window.passiveSupport = {}
</script>
<script type="text/javascript" src="node_modules/passive-events-support/dist/main.js"></script>
By default, importing this package will not update non-passive event listeners. For this package to act, you must specify which event listeners should be made as passive. See the Configuration section below.
Configuration
It is highly recommended to configure and only pass the custom list of event listeners, that trigger the console or Lighthouse warning.
Configurable Options
Option | Type | Default |
---|
debug | boolean | false |
events | array | [] |
listeners | array | [] |
Option: debug
When enabled, all the non-passive event listeners will be console logged.
{
debug: true
}
Console output
[Passive Events Support] Non-passive Event Listener
element: div.some-element
event: 'touchstart'
handler:
fn: ƒ (e)
fnArgument: 'e'
fnContent: 'console.log(e)'
fnPrevented: false
arguments: false
updatedArguments: { passive: true }
The updatedArguments
parameter will be shown only if the event listener was updated by this package.
Option: events
The list of events whose event listeners will have a passive
option assigned with the value of true
or false
decided by the package as it is documented in the How It Works section.
Supported Events:
Type | Events |
---|
Touch | touchstart , touchmove , touchenter , touchend , touchleave |
Wheel | wheel , mousewheel |
{
events: ['touchstart', 'touchmove']
}
Events that are not supported will be ignored.
Known events
option issue:
While this option enables the package to assign the correct passive
option value to all the event listeners for the listed events it also might break certain event listeners. The issue appear when preventDefault()
is not being called from the handler itself, but rather from the another method called by the handler. In this case this package loses the track of preventDefault()
and it marks the event listener as passive. This causes the event listener to break prompting an error message:
Unable to preventDefault inside passive event listener invocation.
Luckilly, this can easilly be debugged in debug
mode and fixed by the listeners
configuration option!
Option: listeners
(Recommended)
With this option, instead of certain events, you target certain event listeners.
When working altogether with events
option, this option could be used to fix the event listeners broken by events
option.
{
listeners: [
{
element: '.select-choice',
event: 'touchstart',
prevented: true
},
{
element: '.select-choice',
event: 'touchmove'
}
]
}
When prevented
option is not presented, the package will calculate the passive
value automatically as it is documented in the How It Works section.
Events that are not supported will be ignored. See supported events list.
Browser Support
Even tho the passive
option is not supported by all the browsers (see Browser compatibility), this package, by default, checks the browser support for passive
option before assigning it.
Debugging the browser support
You can yourself access the variable indicating the support:
import { passiveSupported } from 'passive-events-support/src/utils'
console.log(passiveSupported())
<script type="text/javascript" src="node_modules/passive-events-support/dist/main.js"></script>
<script>
console.log(window.passiveSupported)
</script>
Manually assigning the passive option
You, just like this package, yourself can manually add the passive option:
element.addEventListener('tocuhstart', handler, passiveSupported() ? { passive: true } : false)