Tutoriel Vidéo JavaScript Optimisation des animations & requestAnimationFrame

Télécharger la vidéo Télécharger les sources

Souvent lorsque l'on fait du JavaScript on a tendance à négliger l'impact qu'à notre code sur les performances. Il est important de bien comprendre comment le navigateur fonctionne pour améliorer sa façon d'écrire du JavaScript.

Bien choisir la propriété à modifier

La premier point auquel il faut faire attention est le choix de la propriété à modifier pour un élément HTML. Toutes les propriétés n'ont pas la même impact lors du rendu de la page. Par exemple si on modifie la propriété left d'un élément, le navigateur devra alors reconstruire le layout pour déterminer où se trouve l'élément. Toutes les propriétés n'affecte pas le moteur de la même façon. Par exemple, si on modifie notre élément en utilisant plutôt une transformation CSS, le moteur n'aura plus besoin de calculer la structure de la page et on obtiendra alors de meilleur performance. Pour en savoir plus sur l'impact des propriétés sur les différents moteur vous pouvez vous rendre sur le site csstriggers.com.

Layout trashing

De la même façon, l'ordre d'éxécution de votre script est important afin d'éviter au maximum le layout trashing. Prenons un exemple concret pour mieux comprendre ce phénomène. Nous allons récupérer tous les liens d'une page, changer la taille de la police, et obtenir la nouvelle taille de l'élément.

var time = (new Date()).getTime()
var divs = document.querySelectorAll('a')
for(var i = 0; i < divs.length; i++) { 
    divs[i].style.fontSize = i + "px" // invalide le layout
    var a = divs[i]. offsetWidth // Le layout doit être recalculé pour obtenir cette information
}
console.log('Ce script a mis ' + ((new Date()).getTime() - time) + 'ms')
// 28ms sur la home de grafikart

Le problème avec cette méthode c'est que le changement de taille de police invalide le layout, la ligne suivante impose au navigateur de recalculer le layout afin d'extraire la largeur de l'élément. Ainsi, avec le code ci-dessus le layout est recalculé autant de fois qu'il y a de lien sur la page.

Même si cela peut sembler contre-productif, faire 2 boucles permet d'éviter au maximum le layout trashing et ainsi d'améliorer le temps d'éxécution de notre code :

var time = (new Date()).getTime()
var divs = document.querySelectorAll('a')
for(var i = 0; i < divs.length; i++) { 
    divs[i].style.fontSize = i + "px" // invalide le layout
}
for(var i = 0; i < divs.length; i++) { 
    var a = divs[i].offsetWidth // Le layout doit être recalculé pour obtenir cette information
}
console.log('Ce script a mis ' + ((new Date()).getTime() - time) + 'ms')
// 15ms sur la home de grafikart

Avec cette manière de faire, le layout est invalidé une seule fois (lors de la première boucle) et recalculé qu'une seule fois pour la seconde boucle. Il est important de prendre en compte cette contrainte lorsque l'on écrit un script afin de bien organiser son code pour éviter au maximum ce phénomène de layout trashing.

requestAnimationFrame

Lorsque l'on crée des animations on a tendance à utiliser la fonction setInterval ou setTimeout. Ce qui peut poser plusieurs problèmes :

  • Le navigateur ne rafraichit l'affichage qu'à une certaine fréquence. Cette fréquence varie suivant l'affichage et notre timer n'est pas forcément en synchronisation avec ce taux de rafraichissement. Du coup, avec un timer il déclenche parfois plus d'étapes que nécessaire.
  • Lorsque l'onglet n'est pas affiché, nos timer continue. L'animation n'étant pas visible à l'écran il est inutile de continuer.

La solution à ces 2 problèmes est l'utilisation de la fonction requestAnimationFrame. Cette fonction prend en paramètre un callback qui sera éxécué par le navigateur juste avant le rafraichissement de l'écran. C'est le navigateur qui va choisir la fréquence de rafraichissement suivant différent critère.

var div = document.querySelector('.test')
var left = 0
var updateLeft = function () {
    left++
    div.style.transform = "translateX(" + left + "px)"
    requestAnimationFrame(updateLeft)
}
requestAnimationFrame(updateLeft)

En revanche, contrairement aux fonctions setInterval et setTimeout il n'est pas possible de contrôler précisément le temps entre chaque appel. Aussi, si on souhaite avoir une animation avec une durée précise, il faudra créer une fonction d'animation plus complexe qui calcul l'écart entre chaque appel afin d'adapter l'animation suivant la fréquence de rafraichissement.