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

Ilmari Heikkinen

Si vous utilisez requestAnimationFrame, vous aimiez que vos peintures soient synchronisées avec la fréquence d'actualisation de l'écran, ce qui donne les animations les plus haute-fidélité possible. De plus, vous épargnez à vos utilisateurs le bruit des ventilateurs du processeur et l'autonomie de la batterie lorsqu'ils passent à un autre onglet.

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

Par souci de clarté, voici de quoi il s'agit:

// 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 de code temporel. Vous n'êtes pas au courant. :)

Pourquoi

Pourquoi ? La fréquence rAF vous permet d'obtenir la fréquence idéale de 60 FPS, ce qui correspond à 16,7 ms par image. En revanche, si nous mesurons avec un nombre entier de millisecondes, nous avons une précision de 1/16 pour tout ce que nous voulons observer et cibler.

Comparaison graphique entre 16 ms et 16 ms entières.

Comme vous pouvez le voir ci-dessus, la barre bleue représente le temps maximal dont vous disposez pour réaliser votre travail avant de peindre un nouveau cadre (à 60 images par seconde). Vous faites probablement plus de 16 choses, mais avec un nombre entier de millisecondes, vous ne pouvez planifier et mesurer que ces incréments grossièretés. Ce n'est pas suffisant.

Le Retardateur haute résolution permet d'obtenir une estimation beaucoup plus précise:

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

Le minuteur haute résolution est actuellement disponible dans Chrome sous le nom 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 progressera dans les normes, la méthode supprimera le préfixe et sera disponible via performance.now().

Vous remarquerez également que les deux valeurs ci-dessus sont différentes de plusieurs ordres de grandeur. performance.now() est une mesure en millisecondes à virgule flottante depuis le début du chargement de cette page spécifique (performance.navigationStart pour être précis).

En cours d'utilisation

Le principal problème lié aux recadrages 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));
}

Une modification pour résoudre ce problème est assez simple... Augmentez startTime et now pour utiliser window.performance.now().

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

Cette implémentation est assez naïve, car 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 mesurer rapidement la quantité concernée. Techniquement, il se peut que cela soit un faux positif, mais seulement si une page Web est accessible en continu pendant 30 ans. Toutefois, nous ne sommes pas en mesure de vérifier s'il s'agit d'un nombre à virgule flottante (plutôt que d'un niveau inférieur à un nombre entier). Si vous demandez suffisamment de minuteurs haute résolution, vous obtiendrez forcément des valeurs entières à un moment donné.

Nous prévoyons de déployer cette modification dans Chrome 21. Par conséquent, si vous utilisez déjà ce paramètre de rappel, n'oubliez pas de mettre à jour votre code.