Animation avec JavaScript

De EduTech Wiki
Aller à la navigation Aller à la recherche

Introduction

Cette page présente un survol des techniques pour créer des animations avec JavaScript. L'approche utilisée est plutôt "bottom-up", c'est-à-dire qu'on illustrera davantage les principes fondamentaux à la base de l'animation plutôt que de fournir des technologies ou des outils qui aident à créer des animations (e.g. Animate CC, Google Web Designer). L'objectif est de fournir des connaissances/compétences de base qui peuvent être généralisées par la suite indépendamment des techniques d'animation utilisées.

Prérequis

Pour suivre cette page, les connaissances suivantes sont nécessaires :

Des connaissances théoriques sur le rôle et les avantages/inconvénients des animations peuvent également être utiles, mais ne sont pas nécessaires. Voir à ce propos:

Les animations d'un point de vue technique

Techniquement une animation est une "transformation" de l'écran dans un état qui maintient une certaine similarité avec l'état précédent, mais qui est en même temps suffisamment différent pour qu'on puisse percevoir un changement. La succession de ces états modifiés crée l'illusion du mouvement dans le système visuel.

Pour modifier les états, l'écran de l'ordinateur est redessiné à des intervalles réguliers. Lorsqu'un écran possède, parmi ses caractéristiques techniques, une fréquence de 50 ou 60Hz, cela signifie que l'écran est redessiné 50 ou 60 fois par seconde. Lors des animations web, la capacité de redessiner le nouvel état est également déterminée par le navigateur web. Le moteur de "rendering" du navigateur doit être suffisamment puissant pour pouvoir redessiner l'écran assez fréquemment.

Dans le passé, ce rôle était pris en charge principalement par le plugin Macromedia (et ensuite Adobe) Flash. Depuis quelques années (2010 environ), Flash n'est plus l'outil de référence pour créer des éléments animés. Sa place a été prise par JavaScript (côté client) qui possède la possibilité/capacité de modifier les propriétés des éléments d'une page web une fois que la page (et par conséquent le DOM) est disponible dans le navigateur de l'utilisateur.

En bref, une animation JavaScript consiste tout simplement à modifier, de manière graduelle, une ou plusieurs propriétés des éléments d'une page afin que l'élément présente un changement perceptible lorsque l'écran est redessiné à chaque passage.

Différentes animations possibles

JavaScript, à travers le DOM, peut accéder à différents éléments d'une page HTML et par conséquent presque tous les éléments disponibles (ou qui peuvent être injectés) dans le body d'une page peuvent être animés.

On peut distinguer au moins les trois types d'animations suivants :

  • UI Animation : animations des éléments qui constituent l'interface utilisateur (User Interface).
  • Draw Animation : animations à travers le dessin "on run time" d'éléments graphiques (e.g. Canvas (HTML5) ou SVG)
  • 3D Animation : animations en 3D qui utilisent par exemple le format WebGL

UI Animation

Pour les animations liées à l'interface utilisateur, on peut utiliser des animations JavaScript, mais également des animations CSS. Le choix parmi ces deux types d'animations dépend de la complexité de l'animation et de la façon dont elle est déclenchée. Par exemple, pour des animations sur des boutons dont le background se modifie graduellement au passage de la souris, une animation CSS peut être plus appropriée. Par contre, si on veut faire apparaître un feedback avec un mouvement de type "bounce", il faudra plutôt utiliser JavaScript.

Ces types d'animation sont en général assez simples et impliquent trois éléments :

  1. La ou les propriétés de l'élément qui doivent être modifiées (e.g. sa position, sa couleur, sa largeur, etc.)
  2. La durée de l'animation
  3. Le type d'effet ou la "trajectoire" de l'animation (e.g. linéaire, accélérée au début et plus lente à la fin, plus lente au début et accélérée à la fin, etc.)

En fonction du fait que ces animations sont ponctuelles et simples, il n'y a généralement pas de soucis au niveau des ressources hardware que le rendering du navigateur nécessite pour redessiner l'écran.

Draw Animation

Dans ce type d'animation, les éléments graphiques sont dessinés à chaque mise à jour de l'écran. Les éléments graphiques dessinés peuvent être de type matriciel (avec Canvas (HTML5)) ou vectoriel (avec SVG).

Dans le premier cas, le dessin se fait par pixellisation d'un élément de fond (le canvas): pour faire bouger une ligne, par exemple, on "colore" des pixels qui se trouvent à x = 0 au premier passage, à x = 1 au deuxième passage, etc.

Dans le deuxième cas, par contre, le dessin se fait par computation d'une nouvelle formule mathématique qui présente un changement par rapport au passage précédent. Pour agrandir un cercle, par exemple, nous pouvons passer à chaque passage un radius plus grand.

3D Animation

Ce type d'animation ressemble aux animations de type "draw", à la différence qu'elles utilisent des API dans le navigateur qui permettent de créer des éléments en 3D. Les ressources hardware pour ce type d'animations sont par conséquent beaucoup plus élevées.

Techniques d'animation

Il existe plusieurs techniques d'animation en JavaScript. De suite, on propose deux modalités différentes pour comparer les avantages/limites.

setInterval() ou setTimeout()

La première technique consiste à utiliser les fonctions liées au temps :

  • setTimeout(callback, delay)
  • setInterval(callback, delay)

Voir cet article pour les différences entre les deux méthodes.

Dans les deux cas, on demande à une fonction d'être exécutée après un certain temps. À l'intérieur de cette fonction, il faudra à chaque fois changer la ou les propriétés de l'élément que nous voulons animer.

En raison du fait que ces fonctions acceptent un temps en millisecondes avant d'invoquer la fonction de callback, on peut définir une sorte de "frame per second", par exemple :

  • 1000 / 24 = 41.6666667 -> avec 24 frame per second, on aura un changement des propriétés chaque 41.6666667 ms
  • 1000 / 60 = 16.6666667 -> avec 60 frame par second, on aura un changement des propriétés chaque 16.6666667 ms
  • ...

Comme on a vu, la fréquence de mise à jour d'un écran est normalement de 60Hz, donc ça n'a pas de sens de créer des frame rates plus élevés, car le moteur de rendering ne redessinerait pas à temps l'écran à chaque passage, surtout sur des dispositifs avec une puissance computationnelle limitée.

La qualité de l'animation dépendra donc à la fois du frame rate et du "gap" de la propriété (e.g. l'augmentation ou la diminution de la valeur). Dans l'exemple suivant, vous pouvez tester les conséquences des changements du frame rate et/ou du "gap" de la propriété CSS margin-left dont l'incrémentation comporte un effet de mouvement vers la droite :

Code pour cet exemple :

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>CodePen - JavaScript animation with setInterval</title>
    <style>
        #box {
            width: 50px;
            height: 50px;
            border: 1px solid #000;
            background-color: darkorange;
        }
    </style>
</head>

<body translate="no">
    <h1>JavaScript animation with setInterval()</h1>
    <p>
        <label>Choose de "frame rate" from the list:</label>
        <select id="frameRate">
            <option value="5">5 fps</option>
            <option value="15">15 fps</option>
            <option value="24" selected>24 fps</option>
            <option value="40">40 fps</option>
            <option value="60">60 fps</option>
        </select>
    </p>
    <p>
        <label>Choose the distance covered in every frame:</label>
        <select id="distance">
            <option value="1">1px</option>
            <option value="5" selected>5px</option>
            <option value="10">10px</option>
            <option value="25">25px</option>
            <option value="50">50px</option>
        </select>
    </p>
    <div id="box"></div>
    <script>
        var box = document.querySelector("#box");
        var frameRate = document.querySelector("#frameRate");
        var distance = document.querySelector("#distance");
        var int;

        function animate() {
            var margin = 0;
            int = setInterval(function () {
                    margin = (margin > window.innerWidth ? 0 : margin + Number(distance.value));
                    box.style.marginLeft = margin + "px";
                },
                1000 / Number(frameRate.value))
        }

        function reset() {
            clearInterval(int);
            animate();
        }

        animate();

        frameRate.addEventListener("change", reset);
        distance.addEvenetListener("change", reset);
    </script>
</body>

</html>

requestAnimationFrame()

Une deuxième technique d'animation, plus récente, consiste à utiliser la fonction

  • requestAnimationFrame(callback)

Cette fonction informe le navigateur web que votre application/site souhaite apporter une modification lors de la prochaine mise à jour de l'écran. Cette approche implique deux conséquences importantes :

  1. Le frame rate est déterminé automatiquement en fonction des capacités hardware du navigateur du client
  2. La mise à jour de l'écran se fait exclusivement si la fenêtre est affichée à l'écran (e.g. si le tab est actif ou la fenêtre n'est pas minimisée).

Pour créer une animation avec cette méthode il faut :

  1. Passer la fonction qui détermine les changements de propriétés en tant que fonction de callback de requestAnimationFrame()
  2. Que cette fonction de callback relance requestAnimationFrame(fonction-animation) de manière récursive

Exemple :

//Déclarer une fonction qui appelle requestAnimationFrame de manière récursive
function animate() {
  //Changement des propriétés sur les éléments à animer
  requestAnimationFrame(animate);
}

//Initialiser la première request
requestAnimationFrame(animate);

L'exemple suivant propose la même animation de l'exemple avec setInterval(). Veuillez noter que cette fois-ci, il n'est pas possible de déterminer le frame rate car il est défini automatiquement :

Code de l'exemple :

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>CodePen - JavaScript animation with requestAnimationFrame</title>
    <style>
        #box {
            width: 50px;
            height: 50px;
            border: 1px solid #000;
            background-color: darkorange;
        }
    </style>
</head>

<body translate="no">
    <h1>JavaScript animation with requestAnimationFrame()</h1>
    <p>
        <label>Choose the distance covered in every frame:</label>
        <select id="distance">
            <option value="1">1px</option>
            <option value="5" selected>5px</option>
            <option value="10">10px</option>
            <option value="25">25px</option>
            <option value="50">50px</option>
        </select>
    </p>
    <div id="box"></div>
    <script>
        var box = document.querySelector("#box");
        var distance = document.querySelector("#distance");
        var margin = 0;

        function animate() {
            margin = (margin > window.innerWidth ? 0 : margin + Number(distance.value));
            box.style.marginLeft = margin + "px";
            requestAnimationFrame(animate);
        }

        requestAnimationFrame(animate);
    </script>
</body>

</html>

Comparaison

Les deux techniques présentent des ressemblances, mais également des différences importantes au niveau du fonctionnement interne. En effet, on peut obtenir exactement le même résultat visible en utilisant les deux techniques, mais au niveau de la gestion des ressources du navigateur les choses se passent de manière très différente :

  1. requestAnimationFrame() s'appuie sur le navigateur web pour déterminer automatiquement le frame rate, ce qui permet des animations plus homogènes et adaptées aux dispositifs (e.g. un smartphone n'a pas la même puissance graphique d'un ordinateur)
  2. requestAnimationFrame() met automatiquement en pause les requêtes pour un changement de l'affichage de l'écran lorsque la page n'est pas active, ce qui réduit la charge de travail du navigateur

D'autre part, cependant, avec requestAnimationFrame() il faut utiliser des mécanismes plus complexes si on veut générer une animation qui dure exactement un certain temps (e.g. 2 secondes).

Dans les deux cas, la création d'animation n'est pas très simple si ce n'est pour des exemples de base. Pour cette raison, il existe de nombreuses bibliothèques JavaScript qui facilitent la création d'animations.

Bibliothèques

Il existe des nombreuses bibliothèques JavaScript qui permettent de créer des animations. Certaines proposent des effets en complément à d'autres fonctionnalités (e.g. jQuery), tandis que d'autres sont spécialisées pour les animations.

Dans cette section, nous proposons un aperçu de quelques bibliothèques pour montrer des approches différentes aux animations.

VelocityJS

VelocityJS est une bibliothèque qui peut être utilisée avec (ou sans) jQuery, et dont les performances sont meilleures par rapport à jQuery. Le site de la bibliothèque montre à ce propos une comparaison de la même application en utilisant les deux bibliothèques.

Pour utiliser VelocityJS avec jQuery il faut tout simplement inclure le fichier de la bibliothèque et remplacer les fonctions de jQuery de type .animate() avec la méthode .velocity(). Les arguments sont pratiquement les mêmes.

La méthode .velocity() peut être configurée de différentes manières, proposant plusieurs options qui permettent de définir de manière très précise les caractéristiques de l'animation. Dans l'exemple suivant, la fonction est configurée pour :

  1. Modifier certaines propriétés d'un élément #box identifié avec jQuery
  2. Une durée de 4 secondes
  3. Un effet de type "spring" (disponible grâce à la bibliothèque)

Voici le code :

$("#box")
  .velocity(
    //Define the properties to change
    {
      rotateX: 180,
      rotateY: 180,
      rotateZ: 180,
      marginLeft: 400,
      backgroundColor: "#FF0000"
    },
    //Define the duration
    4000,
    //Define the type of effect
    "spring"
  );

p5.js

p5,js est une bibliothèque qui permet, parmi d'autres choses, de créer des animations de type "draw" en utilisant l'élément canvas.

Dans l'exemple suivant, l'animation consiste simplement en deux fonctions :

  1. setup() qui n'est invoqué qu'une seule fois et sert à déterminer les caractéristiques du canvas, par exemple taille et frame rate
  2. draw() qui est invoqué à chaque mise à jour de l'écran
function setup() {
  createCanvas(480, 480);
  frameRate(60);
}

var radius = 0;

function draw() {
  radius++;
  if (radius > 480) {
    radius = 0;
  }
  ellipse(240, 240, radius, radius);
}

Dans cet exemple, à chaque nouveau frame, on redessine une ellipse avec un radius qui est incrémenté d'un pixel. Lorsque le radius dépasse la largeur du canvas, le radius repart de 0. Vous pouvez cependant noter qu'après le premier cycle, un cercle noir reste fixé aux marges du canvas. Cela s'explique justement par le fait que le cercle "n'existe pas" en tant qu'objet, mais il y a tout simplement les pixels qui sont colorés chaque fois que la fonction draw() est appelée.

Three.js

Three.js est une bibliothèque qui permet de créer des scènes en 3D en utilisant WebGL (il est possible d'utiliser également canvas). Les animations en 3D sont plus complexes car elles nécessitent 3 éléments :

  1. Une scène sur laquelle poser des objects
  2. Une caméra qui détermine le point de vue avec lequel on regarde la scène
  3. Un mécanisme de rendering qui affiche la scène en fonction de la position de la caméra

La mise en place d'une animation 3D est donc forcément plus articulée, mais le mécanisme reste néanmoins le même : à chaque frame rate, on peut modifier des propriétés pour animer la scène. Voici un exemple de base adapté depuis la documentation officielle de Three.js :

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth / 2, window.innerHeight / 2);
renderer.setClearColor(0xffcc00);
document.body.appendChild(renderer.domElement);

var geometry = new THREE.BoxGeometry(2, 2, 2);
var material = new THREE.MeshBasicMaterial({
  color: 0x970000
});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);

camera.position.z = 5;

var render = function() {
  requestAnimationFrame(render);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
};

render();

On remarque bien que l'animation est créée tout simplement avec le même mécanisme requestAnimationFrame() :

var render = function() {
  requestAnimationFrame(render);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
};

render();

Ce qui est plus compliqué, c'est la mise en place, car il faut déterminer plusieurs éléments (scène, caméra, objets, textures, etc.).

Liens externes

Ressources

  • Head, V. (2016). Designing Interface Animation. Meaningful Motion for User Experience. Brooklyn, NY: Rosenfeld Media
  • McCarthy, L., Reas, C., Fry, B. (2016). Make: Getting Started with p5.js. San Francisco, CA: Maker Media