Eingabe wird an den Compositor gesendet
Dies ist der letzte Teil der vierteiligen Blogreihe, in der wir uns Chrome genauer ansehen. Wir untersuchen, wie der Code für die Darstellung einer Website verarbeitet wird. Im vorherigen Beitrag haben wir uns den Rendering-Prozess und den Compositor angesehen. In diesem Beitrag sehen wir uns an, wie der Compositor eine reibungslose Interaktion bei Nutzereingaben ermöglicht.
Eingabeereignisse aus der Sicht des Browsers
Wenn Sie „Eingabeereignisse“ hören, denken Sie vielleicht nur an das Eintippen in ein Textfeld oder an einen Mausklick. Aus Sicht des Browsers bedeutet Eingabe jedoch jede Aktion des Nutzers. Das Scrollen mit dem Mausrad ist ein Eingabeereignis und das Berühren oder Bewegen der Maus ist ebenfalls ein Eingabeereignis.
Wenn ein Nutzer eine Touch-Geste auf dem Bildschirm ausführt, wird die Geste zuerst vom Browserprozess empfangen. Der Browserprozess weiß jedoch nur, wo diese Geste stattgefunden hat, da der Inhalt eines Tabs vom Rendererprozess verarbeitet wird. Der Browserprozess sendet also den Ereignistyp (z. B. touchstart
) und seine Koordinaten an den Rendererprozess. Der Renderer-Prozess verarbeitet das Ereignis ordnungsgemäß, indem er das Ereignisziel findet und die zugehörigen Ereignis-Listener ausführt.
Compositor empfängt Eingabeereignisse
Im vorherigen Beitrag haben wir uns angesehen, wie der Compositor durch das Zusammenführen von gerasterten Ebenen ein flüssiges Scrollen ermöglichen kann. 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. Was passiert aber, wenn der Seite Ereignis-Listener hinzugefügt wurden? Wie würde der Compositor-Thread herausfinden, ob das Ereignis verarbeitet werden muss?
Nicht schnell scrollbarer Bereich
Da das Ausführen von JavaScript die Aufgabe des Hauptthreads ist, kennzeichnet der Compositor-Thread beim Zusammensetzen einer Seite einen Bereich der Seite, an den Ereignishandler angehängt sind, als „Nicht schnell scrollbarer Bereich“. Anhand dieser Informationen kann der Compositor-Thread dafür sorgen, dass das Eingabeereignis an den Hauptthread gesendet wird, wenn es in dieser Region auftritt. Wenn das Eingabeereignis außerhalb dieser Region stammt, fährt der Compositor-Thread mit dem Erstellen des neuen Frames fort, ohne auf den Hauptthread zu warten.
Beachten Sie Folgendes, wenn Sie Ereignis-Handler schreiben
Ein gängiges Muster für die Ereignisbehandlung in der Webentwicklung ist die Ereignisdelegierung. Da Ereignisse weitergeleitet werden, können Sie einen Ereignishandler an das oberste Element anhängen und Aufgaben basierend auf dem Ereignisziel delegieren. Möglicherweise haben Sie schon einmal Code wie den unten stehenden gesehen oder geschrieben.
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault();
}
});
Da Sie nur einen Ereignishandler für alle Elemente schreiben müssen, ist die Ergonomie dieses Ereignisdelegierungsmusters attraktiv. Wenn Sie sich diesen Code jedoch aus der Perspektive des Browsers ansehen, ist jetzt die gesamte Seite als Bereich gekennzeichnet, der nicht schnell scrollbar ist. Das bedeutet, dass der Compositor-Thread auch dann mit dem Haupt-Thread kommunizieren und jedes Mal auf ihn warten muss, wenn ein Eingabeereignis eintritt, selbst wenn Ihre Anwendung keine Eingaben von bestimmten Seitenbereichen benötigt. Dadurch wird die Funktion zum flüssigen Scrollen des Renderers beeinträchtigt.
Du kannst das verhindern, indem du passive: true
-Optionen in deinen Ereignis-Listener übergibst. Dies weist den Browser darauf hin, dass Sie das Ereignis weiterhin im Hauptthread überwachen möchten, der Compositor aber auch einen neuen Frame zusammenstellen kann.
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
Prüfen, ob das Ereignis abgebrochen werden kann
Angenommen, Sie haben ein Feld auf einer Seite, für das Sie die Scrollrichtung auf horizontales Scrollen beschränken möchten.
Wenn Sie die Option passive: true
in Ihrem Zeigerereignis verwenden, kann das Seitenscrollen flüssig sein. Das vertikale Scrollen hat jedoch möglicherweise bereits begonnen, wenn Sie preventDefault
ausführen möchten, um die Scrollrichtung einzuschränken. Mit der Methode event.cancelable
können Sie das überprü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 Ereignis-Handler vollständig zu entfernen.
#area {
touch-action: pan-x;
}
Ereignisziel ermitteln
Wenn der Compositor-Thread ein Eingabeereignis an den Hauptthread sendet, wird zuerst ein Treffertest ausgeführt, um das Ereignisziel zu finden. Beim Kollisionstest werden Daten aus Paint-Eintragsdaten verwendet, die beim Rendering generiert wurden, um herauszufinden, was sich unter den Punktkoordinaten befindet, an denen das Ereignis aufgetreten ist.
Minimieren von Ereignisweiterleitungen an den Haupt-Thread
Im vorherigen Beitrag haben wir erläutert, dass ein typisches Display 60 Mal pro Sekunde aktualisiert wird und dass wir mit der Taktfrequenz Schritt halten müssen, um eine flüssige Animation zu erzielen. Ein typisches Touchscreen-Gerät sendet 60 bis 120 Mal pro Sekunde Touch-Ereignisse und eine typische Maus sendet 100 Mal pro Sekunde Ereignisse. Das Eingabeereignis hat eine höhere Auflösung als unser Display aktualisieren kann.
Wenn ein kontinuierliches Ereignis wie touchmove
120 Mal pro Sekunde an den Hauptthread gesendet wird, kann dies im Vergleich zur Geschwindigkeit, mit der der Bildschirm aktualisiert werden kann, zu einer übermäßigen Anzahl von Treffertests und JavaScript-Ausführungen führen.
Um übermäßige Aufrufe des Hauptthreads zu minimieren, fasst Chrome kontinuierliche Ereignisse (z. B. wheel
, mousewheel
, mousemove
, pointermove
und touchmove
) zusammen und verzögert die Weiterleitung bis kurz vor dem nächsten requestAnimationFrame
.
Alle diskreten Ereignisse wie keydown
, keyup
, mouseup
, mousedown
, touchstart
und touchend
werden sofort gesendet.
getCoalescedEvents
verwenden, um Intraframe-Ereignisse abzurufen
Für die meisten Webanwendungen sollten zusammengeführte Ereignisse ausreichen, um eine gute Nutzererfahrung zu bieten.
Wenn Sie jedoch z. B. eine Zeichenanwendung erstellen und einen Pfad basierend auf touchmove
-Koordinaten platzieren, gehen möglicherweise Zwischenkoordinaten verloren, um eine glatte Linie zu zeichnen. In diesem Fall können Sie die Methode getCoalescedEvents
im Zeigerereignis verwenden, um Informationen zu diesen zusammengeführten Ereignissen zu erhalten.
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 uns mit der Funktionsweise eines Webbrowsers beschäftigt. Wenn Sie sich noch nie gefragt haben, warum in den DevTools empfohlen wird, {passive: true}
in Ihren Ereignishandler einzufügen oder warum Sie das async
-Attribut in Ihr Script-Tag schreiben sollten, hoffe ich, dass diese Reihe Ihnen etwas Licht ins Dunkel gebracht 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 optimieren möchten, aber nicht wissen, wo Sie anfangen sollen, ist Lighthouse das richtige Tool für Sie. Es führt eine Analyse jeder Website durch und erstellt einen Bericht dazu, was richtig gemacht wird und was verbessert werden muss. Wenn Sie sich die Liste der Prüfungen ansehen, erhalten Sie auch einen Eindruck davon, welche Aspekte für einen Browser wichtig sind.
Leistung messen
Leistungsoptimierungen können für verschiedene Websites 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 hat einige Anleitungen zum Messen der Leistung Ihrer Website veröffentlicht.
Richtlinie zu Funktionen auf Ihrer Website hinzufügen
Wenn Sie noch einen Schritt weitergehen möchten, ist die Funktionsrichtlinie eine neue Funktion der Webplattform, die Ihnen beim Erstellen Ihres Projekts als Leitfaden dienen kann. Wenn Sie die Richtlinie für Funktionen aktivieren, wird das Verhalten Ihrer App festgelegt und Sie können Fehler vermeiden.
Wenn Sie beispielsweise dafür sorgen möchten, dass Ihre App das Parsen nie blockiert, können Sie die Richtlinie für synchrone Scripts verwenden. Wenn sync-script: 'none'
aktiviert ist, wird die Ausführung von JavaScript, das den Parser blockiert, verhindert. So wird verhindert, dass Ihr Code den Parser blockiert, und der Browser muss den Parser nicht pausieren.
Zusammenfassung
Als ich anfing, Websites zu erstellen, ging es mir fast nur darum, wie ich meinen Code schreiben und wie ich produktiver werden könnte. Diese Dinge sind wichtig, aber wir sollten auch darüber nachdenken, wie Browser den von uns geschriebenen Code verarbeiten. Moderne Browser wurden und werden kontinuierlich weiterentwickelt, um die Nutzerfreundlichkeit zu verbessern. Wenn wir unseren Code so strukturieren, dass er für den Browser möglichst nutzerfreundlich ist, verbessert sich auch die Nutzerfreundlichkeit. Ich hoffe, Sie schließen sich mir an, um die Browser zu unterstützen.
Vielen Dank an alle, die frühe Entwürfe dieser Reihe geprüft haben, darunter: Alex Russell, Paul Irish, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yasuda, Nasko Oskov und Charlie Reis.
Hat Ihnen diese Reihe gefallen? Wenn du Fragen oder Vorschläge für den nächsten Beitrag hast, kannst du sie mir unten in den Kommentaren oder auf Twitter unter @kosamari senden.