Introduction

Dans ce chapître, nous allons créer des élements animés toujours en utilisant les fonctionnalités de Fabric.js. Comme précedemment, il vous est demandé de créer une page index.html disposant :

  • d’un élément <canvas>,
  • de la bibliothèque Fabric.js, et d’un fichier test.js dans lequel vous écrirez votre code.

Animer un objet

Tous les objets créés en utilisant Fabric.js peuvent être animés à l’aide de la méthode animate.

Exemple simple

Le code ci-dessous permet de créer un rectangle et de la faire bouger de gauche à droite :

const rect1 = new fabric.Rect({
    left: 100,
    top: 100,
    fill: 'red',
    width: 20,
    height: 20
});

rect1.animate(
    'left',
    '+=100',
    {
        onChange: canvas.renderAll(canvas)
    }
);
  1. Testez cette animation pour modifier différentes propriétés du rectangle (position, dimensions, couleur, …)

  2. Créez un cercle et effectuez différents textes

  3. Positionnez votre rectangle en haut de votre canvas, et faites en sorte qu’il tombe jusqu’en bas.

  4. A la suite de la propriété onChange, ajoutez les propriétés :

    duration: 300,
    easing: fabric.util.ease.easeOutBounce

    Par défaut, la durée de l’animation est de 500 ms, modifiez-la à souhait. La propriété easing peut pren,dre différentes valeurs, modifiant le comportement de l’animation. Testez différentes possibilités.

  5. Faites en sorte que le cercle se déplace latéralement, et rebondisse sur les parois du canvas.

Animer plusieurs objets graphiques

La fonction canvas.renderAll() réaffiche systématiquement l’ensemble des objets présents, ce qui peut être pénalisant. Fabric.js propose une fonction fabric.util.animate qui possède différents arguments dont onChange qui est la fonction vue précédemment et onComplete qui est une fonction qui s’exécute lorsque l’animation est terminée.

Vous pouvez tester le code suivant, qui vous permet de bien comprendre le comportement de cette fonction :

let cpt=0;
function animate() {
    fabric.util.animate({
        duration: 200,
        onChange: () => {
            canvas.renderAll();
            console.log(cpt++);
        },
        onComplete: () => {
            console.log("terminé");
        }
    });
}

animate();

Modifiez la valeur de la propriété duration et observez ce qui s’affiche dans la console.

Lancer plusieurs fois la même animation

Testez le code suivant :

let cpt=0;
let n=0;
function animate() {
    fabric.util.animate({
        duration: 200,
        onChange: () => {
            canvas.renderAll();
            console.log(cpt++);
        },
        onComplete: () => {
            if(n<3) animate();
            console.log("fin du tour " + n++);
        }
    });
}

animate();

Vous observez que l’animation est relancée tant que la variable n reste inférieure à 3. Il suffit de supprimer cette condtion pour que votre animation devienne infinie.

  1. Complétez ce code en y ajoutant le cercle, celui qui se déplace latéralement et rebondit contre les parois.
  2. Supprimez le test sur n afin d’obtenir une animation infinie.
  3. On souhaite maintenant que le mouvement du cercle se déclenche lorsque le pointeur de souris survole le canvas (évènement mouse:over) et s’arrête lorsque le pointeur sort du canvas (évènement mouse:out). Ecrivez le code qui gère ces deux évènements et permette de déclencher ou d’arrêter le mouvement.
  4. On souhaite faut maintenant que le cercle ce déplace en diagonale et rebondisse sur les 4 parois du canvas. Pour cela, il doit se déplacer latéralement d’une distance dX et verticalement d’une distance dY. Lorsqu’il touche une paroi, dX devient -dX, ou dY devient -dY selon les cas.

requestAnimFrame

La fonction fabric.util.requestAnimFrame permet de gérer plus finement les animations en contrôlant précisément les intervalles de temps. Cette fonction permet juste d’utiliser la fonction requestAnimationFrame de JavaScript.

Testez le code suivant qui permet de comprendre comment cela fonctionne :

let cpt = 0;
let startTime = Date.now(),
    prevTime = startTime;

function animate() {
    let time = Date.now();

    if (time > prevTime + 1000) {
        prevTime = time;
        console.log("time=", time, "cpt=", cpt++);
    }
    if (cpt < 3) fabric.util.requestAnimFrame(animate, canvas.getElement());
    else console.log("stop");
    canvas.renderAll();
}
console.log("hello!!");
animate();
  1. Expliquez le rôle des variables startTime, prevTime et time.
  2. Observez à quel moment le message Hello s’affiche. Expliquez ce phénomène.
  3. Expliquez le rôle de la variable cpt.
  4. Combien de fois la fonction requestAnimFrame s’exécute-t-elle?
  5. Comment la fonction animate est-elle lancée?
  6. A quoi sert la fonction canvas.renderAll?
  7. Modifiez ce code pour aller plus vite, moins vite.
  8. Comment faire pour que cette fonction devienne infinie?
  9. Ajoutez un cercle rebondissant sur les parois du canvas
  10. Modifiez le code pour que l’animation se déclenche lorsque le pointeur de souris survole le canvas, et qu’elle s’arrête sinon.
  11. Ajoutez des cercles de taille, couleur, etc. différentes et se dépaçant selon des angles différents.

Collisions

  1. Créez un cadre dans lequel circulent deux balles rebondissantes

  2. Lorsque ces balles se percutent, il faut faire en sorte qu’elles repartent dans des directions cohérentes. Le code étant plus complexe, ci-dessous une méthode qui permet de modifier les trajectoires de deux balles qui se percutent. Supposons que ces balles soient définies par des objets b1 et b2 avec les propriétés suivantes :

    • x, y : les positions en abscisse et ordonnée
    • deltaX et deltaY : le déplacement des balles (nombre de pixels) en abscisse et en ordonnée à chaque unité de temps
       handleCollision(b1, b2) {
           let norme = v => Math.hypot(v.x, v.y);
           let somme = (u, v) => {
               return {x: u.x + v.x, y: u.y + v.y}
           };
           let mul = (k, v) => {
               return {x: k * v.x, y: k * v.y}
           };
    
           let n = {x: b2.x - b1.x, y: b2.y - b1.y};
           let q = Math.sqrt(Math.pow(n.x, 2) + Math.pow(n.y, 2));
           let un = {x: n.x / q, y: n.y / q};
           let ut = {x: -un.y, y: un.x};
    
           let v1 = {x: b1.deltaX, y: b1.deltaY};
           let v2 = {x: b2.deltaX, y: b2.deltaY};
    
           let v_1n = (Math.pow(norme(somme(un, v1)),2) - Math.pow(norme(un),2) - Math.pow(norme(v1),2))/2;
           let v_1t = (Math.pow(norme(somme(ut, v1)),2) - Math.pow(norme(ut),2) - Math.pow(norme(v1),2))/2;
           let v_2n = (Math.pow(norme(somme(un, v2)),2) - Math.pow(norme(un),2) - Math.pow(norme(v2),2))/2;
           let v_2t = (Math.pow(norme(somme(ut, v2)),2) - Math.pow(norme(ut),2) - Math.pow(norme(v2),2))/2;
    
           let vp_1n = mul(v_2n, un);
           let vp_1t = mul(v_1t, ut);
           let vp_2n = mul(v_1n, un);
           let vp_2t = mul(v_2t, ut);
    
           let vp_1 = somme(vp_1n, vp_1t);
           let vp_2 = somme(vp_2n, vp_2t);
    
           b1.deltaX = vp_1.x;
           b1.deltaY = vp_1.y;
           b2.deltaX = vp_2.x;
           b2.deltaY = vp_2.y;
       }

Attention : Ce code vous permettra de modifier correctement les trajectoires des balles mais ne règlera pas tous vos problèmes. Il vous restera à l’adapter à votre code et, peut-être, à l’améliorer légèrement.

Remarque : Si vous souhaitez comprendre ce code, je vous conseille la lecture de l’article suivant qui explique en détails les calculs avec en prime la possibilité de gérer des balles de masses différentes.