API requestAnimationFrame : désormais avec une précision inférieure à la milliseconde

Ilmari Heikkinen

Si vous utilisiez requestAnimationFrame, vous aimiez voir vos peintures synchronisées avec la fréquence d'actualisation de l'écran, ce qui vous permettait de créer les animations les plus fidèles possibles. De plus, vous économisez le bruit du ventilateur du processeur et la batterie de vos utilisateurs lorsqu'ils passent à un autre onglet.

Une partie de l'API va toutefois être modifiée. L'horodatage transmis à votre fonction de rappel passe d'un code temporel Date.now() typique à une mesure haute résolution en millisecondes à virgule flottante depuis l'ouverture de la page. Si vous utilisez cette valeur, vous devrez mettre à jour votre code, conformément à l'explication ci-dessous.

Pour être clair, voici ce dont je parle:

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

Si vous utilisez le shim requestAnimFrame commun fourni ici, vous n'utilisez pas la valeur d'horodatage. Vous n'êtes pas concerné. :)

Pourquoi

Pourquoi ? Eh bien, rAF vous aide à atteindre les 60 FPS idéaux, ce qui correspond à 16,7 ms par image. Mais en mesurant avec des nombres entiers en millisecondes, nous avons une précision de 1/16 pour tout ce que nous voulons observer et cibler.

Comparaison des graphiques 16 ms et 16 ms entiers.

Comme vous pouvez le voir ci-dessus, la barre bleue représente le temps maximal dont vous disposez pour effectuer tout votre travail avant de peindre un nouveau frame (à 60 FPS). Vous effectuez probablement plus de 16 tâches, mais avec des millisecondes entières, vous ne pouvez planifier et mesurer que ces incréments très importants. Ce n'est pas suffisant.

Le minuteur haute résolution résout ce problème en fournissant un chiffre beaucoup plus précis:

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

Le minuteur haute résolution est actuellement disponible dans Chrome sous la forme window.performance.webkitNow(). Cette valeur est généralement égale à la nouvelle valeur d'argument transmise au rappel rAF. Une fois que la spécification progresse dans les normes, la méthode supprime le préfixe et sera disponible via performance.now().

Vous remarquerez également que les deux valeurs ci-dessus sont très différentes. performance.now() correspond à une mesure en millisecondes à virgule flottante depuis le début du chargement de cette page spécifique (performance.navigationStart, plus précisément).

Utilisées

Le principal problème concerne les bibliothèques d'animation qui utilisent ce modèle de conception:

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

La correction est assez simple : modifiez startTime et now pour utiliser window.performance.now().

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

Il s'agit d'une implémentation assez naïve. Elle n'utilise pas de méthode now() préfixée et suppose également la prise en charge de Date.now(), qui n'est pas disponible dans IE8.

Détection de fonctionnalités

Si vous n'utilisez pas le modèle ci-dessus et que vous souhaitez simplement identifier le type de valeur de rappel que vous obtenez, vous pouvez utiliser cette technique:

requestAnimationFrame(function(timestamp){

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

    // ...

Vérifier if (timestamp < 1e12) permet de tester rapidement la taille d'un nombre donné. Techniquement, il peut s'agir d'un faux positif, mais uniquement si une page Web est ouverte en continu pendant 30 ans. Toutefois, nous ne pouvons pas vérifier s'il s'agit d'un nombre à virgule flottante (plutôt que d'un entier arrondi). Demandez suffisamment de minuteurs haute résolution, et vous obtiendrez à un moment donné des valeurs entières.

Nous prévoyons de déployer ce changement dans Chrome 21. Par conséquent, si vous utilisez déjà ce paramètre de rappel, veillez à mettre à jour votre code.