requestAnimationFrame API - nu met precisie van minder dan een milliseconde

Ilmari Heikkinen

Als je requestAnimationFrame hebt gebruikt, heb je genoten van het zien van je verf gesynchroniseerd met de vernieuwingsfrequentie van het scherm, wat resulteerde in de meest natuurgetrouwe animaties die mogelijk zijn. Bovendien bespaart u het geluid van de CPU-ventilator en het batterijvermogen van uw gebruikers wanneer ze naar een ander tabblad overschakelen.

Er staat echter een wijziging op stapel in een deel van de API . De tijdstempel die wordt doorgegeven aan uw callback-functie verandert van een typische Date.now() -achtige tijdstempel naar een meting met hoge resolutie van drijvende-komma-milliseconden sinds de pagina werd geopend. Als u deze waarde gebruikt, moet u uw code bijwerken , op basis van de onderstaande uitleg.

Voor de duidelijkheid, dit is waar ik het over heb:

// assuming requestAnimationFrame method has been normalized for all vendor prefixes..
requestAnimationFrame(function(timestamp){
    // the value of timestamp is changing
});

Als u de algemene requestAnimFrame shim gebruikt die hier wordt geleverd , gebruikt u niet de tijdstempelwaarde. Je bent van de haak. :)

Waarom

Waarom? Met rAF kun je de ultieme 60 fps bereiken die ideaal is, en 60 fps vertaalt zich naar 16,7 ms per frame. Maar meten met gehele milliseconden betekent dat we een nauwkeurigheid van 1/16 hebben voor alles wat we willen waarnemen en targeten.

16 ms versus 16 gehele ms grafiekvergelijking.

Zoals je hierboven kunt zien, vertegenwoordigt de blauwe balk de maximale hoeveelheid tijd die je hebt om al je werk te doen voordat je een nieuw frame schildert (bij 60 fps). Je doet waarschijnlijk meer dan zestien dingen, maar met gehele milliseconden kun je alleen in die zeer grote stappen plannen en meten. Dat is niet goed genoeg.

De High Resolution Timer lost dit op door een veel nauwkeuriger cijfer te geven:

Date.now()         //  1337376068250
performance.now()  //  20303.427000007

De timer met hoge resolutie is momenteel beschikbaar in Chrome als window.performance.webkitNow() en deze waarde is over het algemeen gelijk aan de nieuwe argumentwaarde die wordt doorgegeven aan de rAF-callback. Zodra de specificatie verder door de standaarden gaat, zal de methode het voorvoegsel laten vallen en beschikbaar zijn via performance.now() .

Je zult ook merken dat de twee bovenstaande waarden vele ordes van grootte verschillen. performance.now() is een meting van milliseconden met drijvende komma sinds die specifieke pagina begon te laden (de performance.navigationStart om specifiek te zijn).

In gebruik

Het belangrijkste probleem dat wordt bijgesneden, zijn animatiebibliotheken die dit ontwerppatroon gebruiken:

function MyAnimation(duration) {
    this.startTime = Date.now();
    this.duration = duration;
    requestAnimFrame(this.tick.bind(this));
}
MyAnimation.prototype.tick = function(time) {
    var now = Date.now();
    if (time > now) {
        this.dispatchEvent("ended");
        return;
    }
    ...
    requestAnimFrame(this.tick.bind(this));
}

Een bewerking om dit op te lossen is vrij eenvoudig... vergroot de startTime en gebruik now window.performance.now() .

this.startTime = window.performance.now ?
                    (performance.now() + performance.timing.navigationStart) :
                    Date.now();

Dit is een vrij naïeve implementatie, het gebruikt geen prefix now() methode en gaat ook uit van Date.now() ondersteuning, wat niet in IE8 zit.

Functiedetectie

Als u het bovenstaande patroon niet gebruikt en alleen wilt bepalen welk soort callback-waarde u krijgt, kunt u deze techniek gebruiken:

requestAnimationFrame(function(timestamp){

    if (timestamp < 1e12){
        // .. high resolution timer
    } else {
        // integer milliseconds since unix epoch
    }

    // ...

Controleren if (timestamp < 1e12) een snelle test is om te zien met welk groot getal we te maken hebben. Technisch gezien kan dit vals-positief zijn, maar alleen als een webpagina 30 jaar lang onafgebroken geopend is. Maar we kunnen niet testen of het een drijvende-kommagetal is (in plaats van een geheel getal). Vraag om voldoende timers met hoge resolutie en je zult op een gegeven moment gegarandeerd gehele waarden krijgen .

We zijn van plan deze wijziging door te voeren in Chrome 21, dus als u al gebruikmaakt van deze callback-parameter, zorg er dan voor dat u uw code bijwerkt!