Uitgelijnde invoergebeurtenissen

Dave Tapuska
Dave Tapuska

TL; DR

  • Chrome 60 vermindert jank door de gebeurtenisfrequentie te verlagen, waardoor de consistentie van de frametiming wordt verbeterd.
  • De methode getCoalescedEvents() , geïntroduceerd in Chrome 58, biedt dezelfde schat aan gebeurtenisinformatie die u altijd al heeft gehad.

Het bieden van een soepele gebruikerservaring is belangrijk voor internet. De tijd tussen het ontvangen van een invoergebeurtenis en het moment waarop de beelden daadwerkelijk worden bijgewerkt, is belangrijk en over het algemeen is minder werk belangrijk. In de afgelopen paar releases van Chrome hebben we de invoerlatentie op deze apparaten verlaagd.

In het belang van soepelheid en prestaties voeren we in Chrome 60 een wijziging door die ervoor zorgt dat deze gebeurtenissen met een lagere frequentie plaatsvinden en tegelijkertijd de granulariteit van de verstrekte informatie vergroot. Net zoals toen Jelly Bean werd uitgebracht en de Choreograaf op de markt kwam, die de invoer op Android uitlijnt, brengen we op alle platforms frame-uitgelijnde invoer naar het web.

Maar soms heb je meer evenementen nodig. Daarom hebben we in Chrome 58 een methode geïmplementeerd met de naam getCoalescedEvents() , waarmee uw toepassing het volledige pad van de aanwijzer kan ophalen, zelfs als er minder gebeurtenissen worden ontvangen.

Laten we het eerst hebben over de frequentie van evenementen.

Het verlagen van de frequentie van evenementen

Laten we enkele basisprincipes begrijpen: aanraakschermen leveren invoer op 60-120 Hz en muizen leveren invoer doorgaans op 100 Hz (maar kan overal tot 2000 Hz zijn). Toch is de typische vernieuwingsfrequentie van een monitor 60 Hz. Dus wat betekent dat eigenlijk? Het betekent dat we input sneller ontvangen dan waarmee we het scherm daadwerkelijk bijwerken. Laten we dus eens kijken naar een prestatietijdlijn van devtools voor een eenvoudige app voor het schilderen van canvas.

In de onderstaande afbeelding, met requestAnimationFrame() -aligned input uitgeschakeld, ziet u meerdere verwerkingsblokken per frame met een inconsistente frametijd. De kleine gele blokken geven hittests aan voor zaken als het doel van de DOM-gebeurtenis, het verzenden van de gebeurtenis, het uitvoeren van javascript, het bijwerken van het zwevende knooppunt en mogelijk het opnieuw berekenen van de lay-out en stijlen.

Een prestatietijdlijn die inconsistente frametiming laat zien

Dus waarom doen we extra werk dat geen visuele updates veroorzaakt? Idealiter willen we geen werk doen waar de gebruiker uiteindelijk geen profijt van heeft. Vanaf Chrome 60 vertraagt ​​de invoerpijplijn het verzenden van doorlopende gebeurtenissen ( wheel , mousewheel , touchmove , pointermove , mousemove ) en verzendt deze vlak voordat de callback requestAnimationFrame() plaatsvindt. In de onderstaande afbeelding (met de functie ingeschakeld) ziet u een consistentere frametijd en minder tijd voor het verwerken van gebeurtenissen.

We hebben een experiment uitgevoerd waarbij deze functie is ingeschakeld op de Canary- en Dev-kanalen en hebben ontdekt dat we 35% minder hittests uitvoeren, waardoor de hoofdthread vaker kan worden uitgevoerd.

Een belangrijke opmerking waar webontwikkelaars zich bewust van moeten zijn, is dat elke afzonderlijke gebeurtenis (zoals keydown , keyup , mouseup , mousedown , touchstart , touchend ) die optreedt, meteen wordt verzonden, samen met eventuele lopende gebeurtenissen, waarbij de relatieve volgorde behouden blijft. Als deze functie is ingeschakeld, wordt een groot deel van het werk gestroomlijnd in de normale gebeurtenislusstroom , waardoor een consistent invoerinterval wordt geboden. Dit brengt doorlopende gebeurtenissen in lijn met scroll en resize gebeurtenissen die al zijn gestroomlijnd in de gebeurtenisloopstroom in Chrome.

Een prestatietijdlijn die relatief consistente frametiming laat zien.

We hebben ontdekt dat de overgrote meerderheid van de applicaties die dergelijke gebeurtenissen gebruiken, geen nut hebben voor de hogere frequentie. Android heeft al een aantal jaren gebeurtenissen op één lijn gebracht, dus er is niets nieuws, maar sites kunnen minder gedetailleerde gebeurtenissen ervaren op desktopplatforms. Er is altijd een probleem geweest met janky-hoofdthreads die haperingen veroorzaakten bij de soepelheid van de invoer, wat betekent dat je sprongen in positie kunt zien wanneer de applicatie aan het werk is, waardoor het onmogelijk wordt om te weten hoe de aanwijzer van de ene plek naar de andere is gegaan.

De methode getCoalescedEvents()

Zoals ik al zei, zijn er zeldzame scenario's waarin de toepassing er de voorkeur aan geeft het volledige pad van de aanwijzer te kennen. Om het geval op te lossen waarbij je grote sprongen ziet en de frequentie van gebeurtenissen afneemt, hebben we in Chrome 58 een extensie voor pointer-gebeurtenissen gelanceerd met de naam getCoalescedEvents() . En hieronder ziet u een voorbeeld van hoe jank op de hoofdthread verborgen is voor de applicatie als u deze API gebruikt.

Vergelijking van standaard en samengevoegde gebeurtenissen.

In plaats van één enkele gebeurtenis te ontvangen, heeft u toegang tot de reeks historische gebeurtenissen die de gebeurtenis hebben veroorzaakt. Android , iOS en Windows hebben allemaal zeer vergelijkbare API's in hun eigen SDK's en we stellen een vergelijkbare API beschikbaar op internet.

Meestal heeft een tekenapp een punt getekend door naar de verschuivingen op de gebeurtenis te kijken:

window.addEventListener("pointermove", function(event) {
    drawPoint(event.pageX, event.pageY);
});

Deze code kan eenvoudig worden gewijzigd om de reeks gebeurtenissen te gebruiken:

window.addEventListener("pointermove", function(event) {
    var events = 'getCoalescedEvents' in event ? event.getCoalescedEvents() : [event];
    for (let e of events) {
    drawPoint(e.pageX, e.pageY);
    }
});

Houd er rekening mee dat niet elke eigenschap van de samengevoegde gebeurtenissen is ingevuld. Omdat de samengevoegde gebeurtenissen niet echt worden uitgezonden, maar slechts op de rit zijn, worden ze niet op de proef gesteld. Sommige velden, zoals currentTarget en eventPhase , hebben hun standaardwaarden. Het aanroepen van verzendgerelateerde methoden zoals stopPropagation() of preventDefault() heeft geen effect op de bovenliggende gebeurtenis.