Een kijkje in de moderne webbrowser (deel 4)

Mariko Kosaka

Er komt invoer naar de Compositor

Dit is de laatste van de vierdelige blogreeks die een kijkje in Chrome neemt; onderzoeken hoe het omgaat met onze code om een ​​website weer te geven. In het vorige bericht hebben we naar het weergaveproces gekeken en meer geleerd over de compositor . In dit bericht bekijken we hoe de compositor een soepele interactie mogelijk maakt wanneer gebruikersinvoer binnenkomt.

Voer gebeurtenissen in vanuit het perspectief van de browser

Wanneer u 'invoergebeurtenissen' hoort, denkt u misschien alleen aan het typen in een tekstvak of een muisklik, maar vanuit het perspectief van de browser betekent invoer elk gebaar van de gebruiker. Scrollen met het muiswiel is een invoergebeurtenis en aanraken of er overheen bewegen is ook een invoergebeurtenis.

Wanneer een gebruikersgebaar zoals aanraking op een scherm plaatsvindt, is het browserproces het proces dat het gebaar als eerste ontvangt. Het browserproces is zich echter alleen bewust van waar dat gebaar plaatsvond, aangezien de inhoud binnen een tabblad wordt afgehandeld door het rendererproces. Het browserproces verzendt dus het gebeurtenistype (zoals touchstart ) en de coördinaten ervan naar het rendererproces. Het Renderer-proces handelt de gebeurtenis op de juiste manier af door het gebeurtenisdoel te vinden en de gekoppelde gebeurtenislisteners uit te voeren.

invoergebeurtenis
Figuur 1: Invoergebeurtenis gerouteerd via het browserproces naar het rendererproces

Compositor ontvangt invoergebeurtenissen

Figuur 2: Viewport zweeft over paginalagen

In het vorige bericht hebben we gekeken hoe de compositor soepel met scrollen om kon gaan door gerasterde lagen samen te stellen. Als er geen invoergebeurtenislisteners aan de pagina zijn gekoppeld, kan de Compositor-thread een nieuw samengesteld frame maken, volledig onafhankelijk van de hoofdthread. Maar wat als er enkele gebeurtenislisteners aan de pagina waren gekoppeld? Hoe komt de compositor-thread erachter of de gebeurtenis moet worden afgehandeld?

Niet-snel scrollbare regio begrijpen

Omdat het uitvoeren van JavaScript de taak van de hoofdthread is, markeert de compositor-thread, wanneer een pagina wordt samengesteld, een gebied van de pagina waaraan gebeurtenishandlers zijn gekoppeld als "Niet-snel scrollbare regio". Door over deze informatie te beschikken, kan de compositor-thread ervoor zorgen dat de invoergebeurtenis naar de hoofdthread wordt verzonden als de gebeurtenis in die regio plaatsvindt. Als de invoergebeurtenis van buiten dit gebied komt, gaat de compositor-thread door met het samenstellen van een nieuw frame zonder op de hoofdthread te wachten.

beperkt niet-snel scrollbaar gebied
Figuur 3: Diagram van beschreven invoer voor het niet-snel scrollbare gebied

Houd er rekening mee wanneer u gebeurtenishandlers schrijft

Een veelgebruikt patroon voor het afhandelen van gebeurtenissen bij webontwikkeling is het delegeren van gebeurtenissen. Omdat gebeurtenissen bubbelen, kunt u één gebeurtenishandler aan het bovenste element koppelen en taken delegeren op basis van het gebeurtenisdoel. Mogelijk heb je code zoals hieronder gezien of geschreven.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

Omdat u slechts één gebeurtenishandler voor alle elementen hoeft te schrijven, is de ergonomie van dit gebeurtenisdelegatiepatroon aantrekkelijk. Als je deze code echter vanuit het perspectief van de browser bekijkt, is de hele pagina nu gemarkeerd als een niet-snel scrollbaar gebied. Dit betekent dat zelfs als uw applicatie niets geeft om invoer van bepaalde delen van de pagina, de thread van de compositor moet communiceren met de hoofdthread en daarop moet wachten elke keer dat er een invoergebeurtenis binnenkomt. Dus het soepele scrollvermogen van de compositor wordt verslagen.

niet-snel scrollbaar gebied op volledige pagina
Figuur 4: Diagram van beschreven invoer voor het niet-snel scrollbare gebied dat een hele pagina beslaat

Om te voorkomen dat dit gebeurt, kunt u passive: true opties doorgeven in uw gebeurtenislistener. Dit geeft aan de browser aan dat je nog steeds naar de gebeurtenis in de hoofdthread wilt luisteren, maar de compositor kan doorgaan en ook een nieuw frame samenstellen.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

Controleer of het evenement kan worden geannuleerd

pagina scrollen
Figuur 5: Een webpagina waarbij een deel van de pagina is vastgezet op horizontaal scrollen

Stel je voor dat je een vak op een pagina hebt waarin je de scrollrichting wilt beperken tot alleen horizontaal scrollen.

Het gebruik van passive: true -optie in uw pointer-gebeurtenis betekent dat het scrollen van de pagina soepel kan verlopen, maar het verticale scrollen kan al zijn begonnen tegen de tijd dat u preventDefault wilt voorkomen om de scrollrichting te beperken. U kunt dit controleren door de event.cancelable -methode te gebruiken.

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});

Als alternatief kunt u CSS-regels zoals touch-action gebruiken om de gebeurtenishandler volledig te elimineren.

#area {
  touch-action: pan-x;
}

Het doel van de gebeurtenis vinden

druk op de proef
Figuur 6: De rode draad die naar de verfrecords kijkt en vraagt ​​wat er op het xy-punt is getekend

Wanneer de compositorthread een invoergebeurtenis naar de hoofdthread verzendt, is het eerste dat moet worden uitgevoerd een hittest om het gebeurtenisdoel te vinden. Hittest maakt gebruik van verfregistratiegegevens die zijn gegenereerd tijdens het weergaveproces om erachter te komen wat zich onder de puntcoördinaten bevindt waarin de gebeurtenis plaatsvond.

Minimaliseren van gebeurtenisverzendingen naar de hoofdthread

In het vorige bericht hebben we besproken hoe ons typische beeldscherm het scherm 60 keer per seconde vernieuwt en hoe we de cadans moeten bijhouden voor vloeiende animaties. Voor invoer levert een typisch touchscreen-apparaat aanrakingsgebeurtenissen 60-120 keer per seconde, en een typische muis levert gebeurtenissen 100 keer per seconde. Invoergebeurtenis heeft een hogere betrouwbaarheid dan ons scherm kan vernieuwen.

Als een continue gebeurtenis zoals touchmove 120 keer per seconde naar de hoofdthread wordt verzonden, kan dit een buitensporig aantal hittests en JavaScript-uitvoering veroorzaken in vergelijking met hoe langzaam het scherm kan worden vernieuwd.

ongefilterde gebeurtenissen
Figuur 7: Gebeurtenissen die de frametijdlijn overspoelen en paginajanking veroorzaken

Om overmatige aanroepen naar de hoofdthread te minimaliseren, voegt Chrome continue gebeurtenissen samen (zoals wheel , mousewheel , mousemove , pointermove , touchmove ) en vertraagt ​​de verzending tot vlak voor de volgende requestAnimationFrame .

samengevoegde gebeurtenissen
Figuur 8: Dezelfde tijdlijn als voorheen, maar de gebeurtenis wordt samengevoegd en uitgesteld

Alle discrete gebeurtenissen zoals keydown , keyup , mouseup , mousedown , touchstart en touchend worden onmiddellijk verzonden.

Gebruik getCoalescedEvents om intra-frame-gebeurtenissen op te halen

Voor de meeste webapplicaties zouden samengevoegde gebeurtenissen voldoende moeten zijn om een ​​goede gebruikerservaring te bieden. Als u echter zaken bouwt zoals het tekenen van een toepassing en het plaatsen van een pad op basis van touchmove , kunt u tussenliggende coördinaten kwijtraken om een ​​vloeiende lijn te tekenen. In dat geval kunt u de methode getCoalescedEvents in de pointer-gebeurtenis gebruiken om informatie over die samengevoegde gebeurtenissen op te halen.

getCoalescedEvents
Afbeelding 9: Vloeiend pad met aanraakgebaren aan de linkerkant, samengevoegd beperkt pad aan de rechterkant
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.
    }
});

Volgende stappen

In deze serie hebben we de innerlijke werking van een webbrowser besproken. Als je er nog nooit over hebt nagedacht waarom DevTools aanbeveelt om {passive: true} toe te voegen aan je gebeurtenishandler of waarom je async attribuut in je scripttag zou kunnen schrijven, hoop ik dat deze serie enig licht werpt op waarom een ​​browser deze informatie nodig heeft om sneller en soepeler te kunnen leveren webervaring.

Gebruik vuurtoren

Als u ervoor wilt zorgen dat uw code vriendelijk is voor de browser, maar geen idee heeft waar u moet beginnen, is Lighthouse een tool die een audit van elke website uitvoert en u een rapport geeft over wat er goed wordt gedaan en wat er moet worden verbeterd. Als u de lijst met audits doorneemt, krijgt u ook een idee van waar een browser om geeft.

Leer hoe u prestaties kunt meten

Prestatieaanpassingen kunnen per site verschillen. Het is dus van cruciaal belang dat u de prestaties van uw site meet en beslist wat het beste bij uw site past. Het Chrome DevTools-team heeft enkele tutorials over hoe u de prestaties van uw site kunt meten .

Voeg functiebeleid toe aan uw site

Als u een extra stap wilt zetten, is Feature Policy een nieuwe webplatformfunctie die een vangrail voor u kan zijn bij het bouwen van uw project. Het inschakelen van featurebeleid garandeert het bepaalde gedrag van uw app en voorkomt dat u fouten maakt. Als u er bijvoorbeeld zeker van wilt zijn dat uw app het parseren nooit blokkeert, kunt u uw app uitvoeren op basis van het beleid voor synchrone scripts. Wanneer sync-script: 'none' is ingeschakeld, wordt voorkomen dat parser-blokkerende JavaScript wordt uitgevoerd. Dit voorkomt dat uw code de parser blokkeert en dat de browser zich geen zorgen hoeft te maken over het pauzeren van de parser.

Sluit af

Bedankt

Toen ik begon met het bouwen van websites, maakte het me bijna alleen maar uit hoe ik mijn code zou schrijven en wat me zou helpen productiever te zijn. Die dingen zijn belangrijk, maar we moeten ook nadenken over hoe de browser de code verwerkt die we schrijven. Moderne browsers investeren en blijven investeren in manieren om gebruikers een betere webervaring te bieden. Vriendelijk zijn voor de browser door onze code te organiseren, verbetert op zijn beurt uw gebruikerservaring. Ik hoop dat je met mij meegaat in de zoektocht om aardig te zijn tegen de browsers!

Hartelijk dank aan iedereen die vroege versies van deze serie heeft beoordeeld, inclusief (maar niet beperkt tot): Alex Russell , Paul Irish , Meggin Kearney , Eric Bidelman , Mathias Bynens , Addy Osmani , Kinuko Yasuda , Nasko Oskov en Charlie Reis.

Vond je deze serie leuk? Als je vragen of suggesties hebt voor het toekomstige bericht, hoor ik graag van je in het commentaargedeelte hieronder of via @kosamari op Twitter.