We have been working on making our Laravel / Vue / JS app more efficient and smoother. Based on analysis and console warning we had been making tweaks. We had just added code to make all events passive by default based on tips from the Chrome Inspector.
Passive Events
The reason to make the event event is to make scrolling smoother and everything quicker. Let me quote from a Google Developer blog post here:
If you call preventDefault() in the
touchstart
or firsttouchmove
events then you will prevent scrolling. The problem is that most often listeners will not callpreventDefault()
, but the browser needs to wait for the event to finish to be sure of that. Developer-defined “passive event listeners” solve this. When you add a touch event with a{passive: true}
object as the third parameter in your event handler then you are telling the browser that thetouchstart
listener will not callpreventDefault()
and the browser can safely perform the scroll without blocking on the listener.
And this warning we got related to a TinyMCE implementation.
Added non-passive event listener to a scroll-blocking
So as said we were told we were using a scroll blocking event and that this would slow dow the app’s experience. The specific error we had before was the following:
[Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event.
Consider marking event handler as 'passive' to make the page more responsive.
See https://www.chromestatus.com/feature/5745543795965952
It was pointing at an issue with TinyMCE and in our case TinyMCE used in a drag and drop toolbar on an html canvas.
Passive Event Listeners
We made this event and all events passive by default using the following code:
(function () { let func = EventTarget.prototype.addEventListener; let supportsPassive = false try { let opts = Object.defineProperty({}, 'passive', { get: function() { supportsPassive = true; } }) document.addEventListener("testPassive", null, opts); document.removeEventListener('testPassive', null, opts); } catch(e) {} EventTarget.prototype.addEventListener = function (type, fn, capture) { this.func = func; capture = capture instanceof Object ? capture : {}; capture.passive = supportsPassive; this.func(type, fn, capture); }; }());
As you can see it also has a check to see if passive is supported and also sends out an error report when there is an issue. Adding passive: true to an EventListener makes it a no longer blocking event which is good for performance.
Unable to preventDefault
But as soon as we started using this our elements could no longer be dropped on the canvas and we got this warning from the console
Unable to preventDefault inside passive event listener invocation.
We were using preventDefault to allow items to be dropped on the canvas. This function entails the following:
The
Event
interface’spreventDefault()
method tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be. The event continues to propagate as usual, unless one of its event listeners callsstopPropagation()
orstopImmediatePropagation()
, either of which terminates propagation at once.
The reason we added this as dropping an element into another is not possible by default (w3 schools basics on this). Specific code block with preventDefault in question was this basic one:
blockEvent: function(event) { event.preventDefault(); },
It was attached to the canvas div block using Vue v-on directives @dragenter="blockEvent" @dragover="blockEvent"
– more on html drag and drop api here. But now that we added the default passive is true to events the preventDefault
function no longer worked disallowing us to drop elements on the canvas. So what to do?
Solution
As possible solutions the Google Developer blog post mentioned the following:
We’ve found that a large majority of impacted pages are fixed relatively easily by applying the touch-action CSS property whenever possible. If you wish to prevent all browser scrolling and zooming within an element apply touch-action: none to it.
In more complex cases it may be necessary to also rely on one of the following:
- If your
touchstart
listener callspreventDefault()
, ensure preventDefault() is also called from associated touchend listeners to continue suppressing the generation of click events and other default tap behavior.- Last (and discouraged) pass
{passive: false}
to addEventListener() to override the default behavior. Be aware you will have to feature detect if the User Agent supports EventListenerOptions.
We decided to implement the following …