Eingabe kommt an den Compositor
Dies ist der letzte Teil einer vierteiligen Blogreihe, in der wir uns Chrome näher ansehen und untersuchen, wie der Code zum Anzeigen einer Website verarbeitet wird. Im vorherigen Post haben wir uns den Renderingprozess angesehen und etwas über den Compositor erfahren. In diesem Beitrag erfahren Sie, wie der Compositor bei einer Nutzereingabe eine reibungslose Interaktion ermöglicht.
Eingabeereignisse aus Browsersicht
Wenn Sie "Eingabeereignisse" hören, denken Sie vielleicht nur an eine Eingabe in einem Textfeld oder einen Mausklick. Aus Browsersicht bedeutet Eingabe jedoch jede Bewegung des Nutzers. Das Scrollen mit dem Mausrad ist ein Eingabeereignis, während Berühren oder Mouseover ebenfalls ein Eingabeereignis sind.
Wenn der Nutzer eine Touch-Geste wie eine Berührung auf einem Bildschirm ausführt, wird die Bewegung zuerst über den Browserprozess empfangen. Der Browserprozess erkennt jedoch nur, wo die Bewegung aufgetreten ist, da Inhalte in einem Tab vom Rendererprozess verarbeitet werden. Daher sendet der Browserprozess den Ereignistyp (z. B. touchstart
) und seine Koordinaten an den Rendererprozess. Der Renderer-Prozess verarbeitet das Ereignis entsprechend, indem das Ereignisziel ermittelt und die zugehörigen Ereignis-Listener ausgeführt werden.
Compositor empfängt Eingabeereignisse
Im vorherigen Post haben wir uns angesehen, wie der Compositor das Scrollen reibungslos handhaben kann, indem Rasterebenen zusammengesetzt werden. Wenn der Seite keine Eingabeereignis-Listener zugeordnet sind, kann der Compositor-Thread einen neuen zusammengesetzten Frame erstellen, der völlig unabhängig vom Hauptthread ist. Aber was wäre, wenn einige Event-Listener an die Seite angehängt wären? Wie würde der Compositor-Thread herausfinden, ob das Ereignis verarbeitet werden muss?
Bereich mit nicht schnellem Scrollen verstehen
Da das Ausführen von JavaScript die Aufgabe des Hauptthreads ist, markiert der Compositor-Thread beim Zusammensetzen einer Seite einen Bereich der Seite, dem Event-Handler als „Non-Fast Scrollable Region“ angehängt sind. Anhand dieser Informationen kann der Compositor-Thread sicherstellen, dass das Eingabeereignis an den Hauptthread gesendet wird, wenn das Ereignis in dieser Region auftritt. Wenn das Eingabeereignis von außerhalb dieser Region stammt, setzt der Compositor-Thread einen neuen Frame zusammen, ohne auf den Hauptthread zu warten.
Vorsicht beim Schreiben von Event-Handlern
Ein gängiges Muster für die Ereignisverarbeitung in der Webentwicklung ist die Ereignisdelegation. Da Ereignisse als Infofeld angezeigt werden, können Sie einen Event-Handler an das oberste Element anhängen und Aufgaben basierend auf dem Ereignisziel delegieren. Vielleicht haben Sie Code wie den folgenden gesehen oder geschrieben.
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault();
}
});
Da Sie nur einen Event-Handler für alle Elemente schreiben müssen, ist die Ergonomie dieses Ereignisdelegierungsmusters ansprechend. Wenn Sie diesen Code jedoch aus der Perspektive des Browsers betrachten, wird die gesamte Seite als nicht schnell scrollbarer Bereich gekennzeichnet. Dies bedeutet, dass der Compositor-Thread mit dem Hauptthread kommunizieren und bei jedem Eingang eines Eingabeereignisses darauf warten muss, selbst wenn Ihre Anwendung die Eingabe von bestimmten Teilen der Seite nicht interessiert. Daher ist das reibungslose Scrollen des Compositors nicht möglich.
Um dies zu verhindern, können Sie passive: true
-Optionen an den Event-Listener übergeben. Dies weist den Browser darauf hin, dass Sie das Ereignis weiterhin im Hauptthread überwachen möchten. Der Compositor kann aber auch einen neuen Frame zusammensetzen.
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
Prüfen, ob der Termin abgesagt werden kann
Stellen Sie sich vor, Sie haben ein Feld auf einer Seite, in dem Sie die Scrollrichtung auf horizontales Scrollen beschränken möchten.
Wenn Sie die Option passive: true
in Ihrem Mauszeigerereignis verwenden, kann das Scrollen der Seite flüssig sein, aber möglicherweise bereits vor dem gewünschten preventDefault
begonnen, um die Scrollrichtung einzuschränken. Sie können das mit der Methode event.cancelable
prüfen.
document.body.addEventListener('pointermove', event => {
if (event.cancelable) {
event.preventDefault(); // block the native scroll
/*
* do what you want the application to do here
*/
}
}, {passive: true});
Alternativ können Sie eine CSS-Regel wie touch-action
verwenden, um den Event-Handler vollständig zu eliminieren.
#area {
touch-action: pan-x;
}
Ereignisziel finden
Wenn der Compositor-Thread ein Eingabeereignis an den Hauptthread sendet, muss zuerst ein Treffertest zum Auffinden des Ereignisziels ausgeführt werden. Bei einem Treffertest werden Paint-Datensatzdaten verwendet, die beim Renderingprozess generiert wurden, um herauszufinden, was sich unter den Punktkoordinaten befindet, in denen das Ereignis aufgetreten ist.
Ereignisweiterleitungen an den Hauptthread minimieren
Im vorherigen Post haben wir erörtert, wie unser typischer Bildschirm den Bildschirm 60-mal pro Sekunde aktualisiert und wie wir mit dem Rhythmus für eine flüssige Animation Schritt halten müssen. Zur Eingabe führt ein typisches Touchscreen-Gerät 60- bis 120-mal pro Sekunde ein Touch-Ereignis aus, bei einer typischen Maus 100-mal pro Sekunde. Das Eingabeereignis hat eine höhere Genauigkeit, als unser Bildschirm aktualisieren kann.
Wenn ein kontinuierliches Ereignis wie touchmove
120 Mal pro Sekunde an den Hauptthread gesendet wird, kann es zu einer übermäßigen Anzahl von Treffertests und zur JavaScript-Ausführung im Vergleich zur langsamen Aktualisierung des Bildschirms kommen.
Um übermäßige Aufrufe an den Hauptthread zu minimieren, fasst Chrome kontinuierliche Ereignisse (z. B. wheel
, mousewheel
, mousemove
, pointermove
, touchmove
) zusammen und Verzögerungen bei der Weiterleitung bis direkt vor dem nächsten requestAnimationFrame
.
Alle diskreten Ereignisse wie keydown
, keyup
, mouseup
, mousedown
, touchstart
und touchend
werden sofort ausgelöst.
getCoalescedEvents
verwenden, um Ereignisse im Frame abzurufen
Bei den meisten Webanwendungen sollten zusammengefasste Ereignisse ausreichen, um eine gute Nutzererfahrung zu bieten.
Wenn Sie jedoch beispielsweise Anwendungen zeichnen und einen Pfad erstellen, der auf touchmove
-Koordinaten basiert, können zwischen den Koordinaten verloren gehen und eine glatte Linie gezeichnet werden. In diesem Fall können Sie die Methode getCoalescedEvents
im Zeigerereignis verwenden, um Informationen zu diesen zusammengeführten Ereignissen abzurufen.
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
});
Nächste Schritte
In dieser Reihe haben wir das Innenleben eines Webbrowsers behandelt. Wenn Sie noch nie darüber nachgedacht haben, warum DevTools Ihrem Event-Handler das Hinzufügen von {passive: true}
empfiehlt oder warum Sie das async
-Attribut in Ihr Skript-Tag schreiben sollten, hoffe ich, dass diese Reihe etwas darüber aufgeklärt hat, warum ein Browser diese Informationen benötigt, um eine schnellere und reibungslosere Webnutzung zu ermöglichen.
Lighthouse verwenden
Wenn Sie Ihren Code für den Browser anpassen möchten, aber keine Ahnung haben, wo Sie anfangen sollen, ist Lighthouse ein Tool, das Website-Prüfungen durchführt und Ihnen einen Bericht dazu liefert, was richtig gemacht wurde und was verbessert werden muss. Die Liste der Prüfungen vermittelt Ihnen einen Eindruck davon, was für einen Browser wichtig ist.
Weitere Informationen zum Messen der Leistung
Die Leistungsoptimierung kann je nach Website variieren. Daher ist es wichtig, dass Sie die Leistung Ihrer Website messen und entscheiden, was für Ihre Website am besten geeignet ist. Das Chrome DevTools-Team bietet mehrere Tutorials zur Messung der Websiteleistung.
Funktionsrichtlinie zu Ihrer Website hinzufügen
Wenn Sie einen zusätzlichen Schritt gehen möchten, ist die Funktionsrichtlinie eine neue Webplattformfunktion, die Ihnen beim Erstellen Ihres Projekts als Schutzmaßnahmen dienen kann. Durch das Aktivieren von Funktionsrichtlinien wird das bestimmte Verhalten Ihrer App garantiert und verhindert, dass Sie Fehler machen.
Wenn Sie beispielsweise dafür sorgen möchten, dass Ihre Anwendung das Parsen nie blockiert, können Sie sie gemäß der Richtlinie für synchrone Skripts ausführen. Wenn sync-script: 'none'
aktiviert ist, wird die Ausführung von JavaScript verhindert, das den Parser blockiert. Dadurch wird verhindert, dass Code den Parser blockiert und der Browser muss den Parser nicht pausieren.
Zusammenfassung
Als ich anfing, Websites zu erstellen, war mir fast nur wichtig, wie ich meinen Code schreibe und was mir helfen würde, produktiver zu sein. Das ist wichtig, aber wir sollten auch überlegen, wie der Browser den von uns geschriebenen Code aufnimmt. Moderne Browser arbeiten kontinuierlich daran, das Web für Nutzer zu verbessern. Eine positive Nutzererfahrung durch den Code zu verbessern, verbessert wiederum die Nutzererfahrung. Ich hoffe, Sie unterstützen mich auch dabei, nett zu den Browsern zu sein.
Ein großes Dankeschön an alle, die erste Entwürfe dieser Reihe gelesen haben. Dazu gehören unter anderem: Alex Russell, Paul Irish, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko und Chasrudask.
Hat Ihnen die Reihe gefallen? Wenn ihr Fragen oder Vorschläge für den zukünftigen Beitrag habt, könnt ihr uns gerne unten im Kommentarbereich oder unter @kosamari auf Twitter kontaktieren.