I have two projects:
Bootstrap
with jQuery
materialize-css
with Vanilla JS
When running a Lighthouse audit on both projects I used to get this warning caused by materialize-css
on one project and jQuery
on the other project:
I say "used to get", because I did manage to fix it for jQuery
simply just applying this workaround:
const opts = (ns) => ... // some code deciding if browser supports passive
$.event.special.touchmove = { setup: function(_, ns, handle) { this.addEventListener('touchmove', handle, opts(ns)) } }
$.event.special.touchstart = { setup: function(_, ns, handle) { this.addEventListener('touchstart', handle, opts(ns)) } }
$.event.special.touchend = { setup: function(_, ns, handle) { this.addEventListener('touchend', handle, opts(ns)) } }
This seem to fix the issue for jQuery
, I dont get such warning anymore and everything seems to be working fine.
Now, for materialize-css
I found this package default-passive-events that (from docs):
It basically will set { passive: true } automatically every time you declare a new event listener.
Unfortunatelly this library did make materialize components break for touch events because of use of e.preventDefault()
...
Is there a way, similar to jQuery
workaround above, to fix all the materialize-css
added event listeners? P.S. It does not use jQuery
First of all, this is just a warning, not an error.
Is there a way, similar to jQuery workaround above, to fix all the materialize-css added event listeners? P.S. It does not use jQuery
Yes, there are, actually, three ways:
Simply add { passive: false }
as third param to all listeners which don't have an object as third parameter. This will tell browsers that .preventDefault()
might be called on those events. However, especially on scroll
, touchmove
and touchstart
events, the performance increase is considerable when the browser knows that the default behavior won't be prevented on an event. When marked as passive, the scrolling will be much smoother and the perceived performance will be significantly increased.
Add { passive: true }
as third param to all listeners which don't have an object as third paramter. This will tell browsers that .preventDefault()
will never be called on those events. You'll see a performance increase, but code relying on preventing those events will break.
Note: this is what both the jQuery fix and the default-passive-events
package do, btw.
The proper way is to go into the source code of whatever lib you're fixing, figure out which events might ever be prevented and add { passive: false }
for those, while adding { passive: true }
for everything else.
I'd argue finding all the places where an event is prevented in a lib is not a huge task.
You can do this in a fork, ideally PR-ing it back into the lib's repo, for others to benefit, just like you benefit from the lib itself.
Here's solution 1.
function patchScrollBlockingListeners() {
let supportsPassive = false;
const x = document.createElement("x");
x.addEventListener("cut", () => 1, {
get passive() { supportsPassive = true; return !!1 }
});
x.remove();
if (supportsPassive) {
const originalFn = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(...args) {
if (
['scroll', 'touchmove', 'touchstart'].includes(args[0]) &&
(typeof args[2] !== 'object' || args[2].passive === undefined)
) {
args[2] = {
...(typeof args[2] === 'object' ? args[2] : {}),
passive: false
};
}
originalFn.call(this, ...args);
}
}
}
patchScrollBlockingListeners();
The above code only "patches" scroll
, touchmove
and touchstart
events (by declaring them non-passive). this makes the warnings go away, without touching third party code.
Note: in order for this to work, the function has to be run before loading whatever library is throwing the warning. The code above only patches events added after it was run, it doesn't patch already bound listeners.
Note: Solution 2 is the same code, except the passive
override is set to true
.
Another, rather important, note: while I can't guarantee that it will work for everyone, passing the following to passive
has worked for me on "patching" a lot of libs in a lot of projects:
passive: typeof args[2] === "boolean" ? args[2] : true
It respects the previous addEventListener
syntax (where 3-rd arg was passive
itself, as boolean
) and sets it to true
when not specified at all. This, however will break events where the passive
was not specified and the events are being canceled in some scenario, which is why I haven't included it above.