Introduction

Fabric.js est une bibliothèque JavaScript qui facilite la création graphique dans un élément HTML <canvas>.

La page HTML de base

Recopiez le code suivant, dans un fichier index.html qui sera votre page web initiale :

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Premier Test FABRIC</title>
</head>
<body>
<div>
    <canvas id="c1" style="border:1px solid #000000;"/>
</div>
<div>
    <button id="b1">action</button>
</div>
<script type='text/javascript' src='fabric.min.js'></script>
<script type='text/javascript' src='test1.js'></script>
</body>
</html>

Vous aurez besoin de 2 fichiers JavaScript :

  • fabric.min.js que vous devez télécharger depuis le site Fabric.js
  • test1.js qui est un fichier que vous devez créer et dans lequel vous placerez votre code JavaScript

Associer le Canvas à une constante JavaScript

La première ligne de votre fichier test1.js doit être :

const canvas = this.__canvas = new fabric.Canvas('c1');

Vous créez ici la constante JavaScript canvas qui représente un canvas dynamique : les objets graphiques que vous y créez sont mobiles et redimensionnables. Lorsque vous souhaitez créer un canvas statique, votre code devient :

const canvas = this.__canvas = new fabric.StaticCanvas('c1');

L’opérateur new vous a en fait permis de créer un objet. canvas est donc un objet JavaScript composé d’attributs (members) et de méthodes (methods)

La documentation relative à cet objet est accessible ici.

Petit détour par les objets

En JavaScript, il est possible de définir des objets de différentes manières.

Classes

On peut créer des classes qui permettent de définir un modèle. On peut alors créer des objets de cette classe à l’aide de l’opérateur new

Le code new fabric.Canvas('c1') permet donc de créer un objet de la classe Canvas. Cette classe est définie dans la bibliothèque Fabric.js.

Initialisateurs d’objets

Un Initialisateur d’objet est une liste entre accolades d’expressions du type clé:valeur séparées par des virgules.

On peut par exemple définir l’objet o1 suivant :

const o1 = {
    x:10,
    y:50,
    nom:'objet1'
}

On peut manipuler cet objet et accéder à ses différentes propriétés à l’aide de l’opérateur point :

// afficher ses propriétés
console.log('o1=',o1);
// afficher une propriété
console.log('x=',o1.x);
// modifier une propriété
o1.x += 5;
console.log('x=',o1.x);
// ajouter une propriété
o1.z = -20;
console.log('z=',o1.z);

Un objet peut posséder des fonctions (méthodes) :

const o2 = {
    x:10,
    y:51,
    superficie:function(){
        return o2.x * o2.y
    }
};

console.log("s=",o2.superficie());

La fonction superficie utilise les propriétés x et y de l’objet o2. Comme cette fonction est elle-même définie dans l’objet o2, on peut utiliser le mot-clé this au lieu de reprendre le nom de l’objet. Le code devient alors :

const o2 = {
    x:10,
    y:51,
    superficie:function(){
        return this.x * this.y
    }
};

console.log("s=",o2.superficie());

Quelques pièges à éviter

Affectation et recopie d’objets

L’opérateur d’affectation = ne permet pas de créer un nouvel objet. Testez le code suivant :

const o2 = {
    x:10,
    y:51,
    superficie:function(){
        return this.x * this.y
    }
};

const o3=o2;
o2.x=25;
console.log("x=",o3.x);

Vous observez que o2 et o3 sont le même objet.

Pour recopier un objet et en créer un clone, vous pouvez utiliser l’opérateur de décomposition (spread) ... Modifiez la deuxième partie du code précédent comme suit :

const o3={...o2};
o2.x=25;
console.log("x=",o3.x);

Vous pouvez maintenant observer que o2 et o3 sont bien deux objets différents.

Comparaison d’objets

Il n’est pas correct de comparer deux objets à l’aide de l’opérateur classique de comparaison == ou ===. Le test ne retournera true que si on compare un objet à lui même :

const o3={...o2};
const o4=o3;

console.log("o2=o3:",o2===o3); // false
console.log("o2=o4:",o2===o4); // false
console.log("o3=o4:",o3===o4); // true

Contrôler les propriétés canvas

Remarque

La constante canvas étant un objet, vous savez maintenant comment manipuler cet objet, et accéder à ses différentes propriétés. Vous savez par exemple afficher sa largeur (propriété width) :

console.log("largeur du canvas : ",canvas.width);

Pour vous exercer

Pour effectuer ce qui est demandé ci-dessous, vous aurez sans doute besoin de la documentation concernant l’objet canvas

  1. Affichez la couleur de fond du canvas (propriété backgroundColor).
  2. Modifiez la couleur de fond du canvas
  3. Modifiez les dimensions du canvas avec les fonctions setWidth ou setDimensions
  4. Affichez les dimensions du canvas
  5. Affichez les coordonnées du centre du canvas (à vous de trouver la méthode adaptée)

Créer des formes basiques

Introduction

La bibliothèque Fabric.js met à votre disposition un large panel de classes, ce qui vous permet de générer aisément des objets qui représentent des formes de base.

Parmi ces formes de base, on trouve :

Chacune des classes correspondantes permet de créer des objets et de les modeler en utilisant les méthodes définies sur leur classe.

Créer un rectangle

Pour créer un rectangle, on procédez comme suit :

// créer un objet
const rect1 = new fabric.Rect({
    left: 100,
    top: 100,
    fill: 'red',
    width: 20,
    height: 20
});

// et l'ajouter au canvas
canvas.add(rect1);

Remarquez que pour créer notre rectangle, nous lui passons un objet qui défini ses dimension, sa position etc. Ajoutez la propriété angle:45 à votre objet et observez ce qui se produit.

Capturer un évènement

Si vous observez votre page web, vous observez la présence d’un bouton action. Le code HTML vous permet de connaitre son id : b1.

Le code qui suit vous permet de déclencher l’exécution d’une fonction anonyme lors d’un évènement click sur ce bouton :

document.getElementById("b1").onclick = () => {
    console.log("click");
};
  • La méthode getElementbyId retourne l’élément dont l’id est donné. Dans notre cas, le bouton action.
  • onclick permet de définir la fonction JavaScript qui est déclenchée lors l’évènelent click survient.
  • La fonction déclenchée par le click ne sera utilisée qu’une seule fois dans notre code. Plutôt que de définir une fonction et de ne l’appeler qu’une seule fois, nous préférons ici définir une fonction fléchée anonyme

Faire pivoter le rectangle

Pour faire pivoter le rectangle à la demande, on peut définir notre fonction anonyme comme suit :

document.getElementById("b1").onclick = () => {
    rect1.rotate(rect1.angle +30); // ajouter 30° à l'angle du rectangle
    canvas.renderAll();
};

La méthode rotate définie dans la classe Rect permet de définir l’angle de notre rectangle. La méthode renderAll() définie dans la classe Canvas permet de réafficher les objets graphiques du canvas.

Faire avancer le rectangle

Pour faire avancer le rectangle, il suffit de modifie sa propriété left, qui définit l’abscisse de son angle supéreur gauche :

document.getElementById("b1").onclick = () => {
    rect1.set('left', rect1.left+10);
    canvas.renderAll();
};

Pour vous exercer

  1. Lorsque le rectangle dépasse la limite droite du canvas, il disparaît. Modifiez ce code pour que dans ce cas, le rectangle réapparaisse sur le bord gauche du canvas.

  2. Ajouter des boutons “b2”, “b3”, et “b4” pour pouvoir déplacer le rectangle vers la droite, vers le haut, vers le bas, toujours en le faisant réapparaître sur le bord opposé lorsqu’il sort du cadre. Pour cela, dupliquez le code qui gère l’évènement sur le bouton “b1” et adaptez-le à chaque cas.

  3. On souhaite maintenant que le rectangle change de couleur à chaque click sur l’un des boutons : d’abord rouge, le rectangle devient bleu, puis jaune, puis redevient bleu, etc. Créez un tableau global avec les trois couleurs puis, dans chacune des quatre fonctions anomymes, ajoutez les lignes de code qui gèrent le changement de couleur.

  4. Pour effectuer le changement de couleur, le même code a été rajouté aux 4 fonctions anonymes. Créez une fonction nouvelle\_couleur qui prend en argument un tableau t qui permet de gérer le changement de couleur. Utilisez cette fonction dans chacun,e des quatre fonctions anonymes.

  5. On remarque que le code est presque le même pour la gestion de ces quatre évènements. Ecrivez une fonction deplacer qui prend en argument

    • un objet Rect,

    • une chaine de caractère représentant un id de bouton,

    • un entier représentant un nombre de pixels et qui gère les mouvements du rectangle dans les quatre directions. Ainsi, les quatre instructions permettant de gérer les clicks s’écrivent sur le modèle suivant :

      document.getElementById("b_droite").onclick = () => {
          deplacer(rect1,'b_droite',10);
      };
  6. Remarquez que l’évènement onclick se voit affecter une fonction anonyme qui exécute une fonction. Testez votre code en affectant directement la fonction deplacer à l’évènement onclick, et vérifiez que cela ne fonctionne pas.

  7. Pour que cela fonctionne, modifiez votre fonction deplacer de sorte qu’elle retourne une fonction qui contient le code de la fonction deplacer. Le code permettant de gérer chacun des 4 évènements s’écrit maintenant selon le modèle suivant :

    document.getElementById("b_droite").onclick = deplacer(rect1, 'b_droite', 10);

    Avant d’écrire votre code, il vous est très vivement conseiller de lire d’abord le paragraphe suivant, de tester le code qui y est présenté et de tenter de comprendre ce qui se passe.

  8. On constate maintenant que les quatres lignes permettant de gérer les quatre évènements sont quasi identiques. Pour éviter d’écrire quatre fois le même code, créez un tableau contenant les quatre identifiants de boutons, et écrivez une boucle qui parcours ce tableau pour générer les gestions d’évènements document.getElementById(....).

  9. Pour les étudiants plus téméraires : vous pouvez tenter d’écrire une fonction gererClicks(id) qui retourne une fonction qui effectue la gestion des évènements. id est une chaine de caractères représentant un id de bouton. En appliquant la méthode forEach au tableau créé prédemment, on génère les quatre gestions d’évènements :

    ["b_droite","b_gauche","b_bas","b_haut"].forEach(id=>gererClicks(id)());
  10. Pour les étudiants plus téméraires et qui auraient réussi l’exércice précédent : modifiez le tableau d’identifiants en créant un tableau d’objets qui donne tous les éléments nécessaires à la gestion des évènements :

    const t1 = [
       {rect: rect1, id: "b_droite", nbPixels: 10},
       {rect: rect1, id: "b_gauche", nbPixels: 10},
       {rect: rect1, id: "b_bas", nbPixels: 10},
       {rect: rect1, id: "b_haut", nbPixels: 10}
    ];

    puis adaptez la fonction gererClicks et la boucle forEach.

Une fonction qui retourne une fonction

En JavaScript, il est assez courant d’écrire des fonctions qui retournent des fonctions. Par exemple, le code ci-dessous :

function f1(x){
    return function(){
        return x+1;
    }
}

Permet de déclarer une fonction f1 qui retourne une fonction. En exécutant le code suivant :

console.log("test1:", f1(5));

On constate que la valeur retournée par f1 est bien une fonction.

Pour obtenir un résultat, on peut écrire le code suivant :

const f2 = f1(5);
console.log("test2",f2());

f2 est bien une fonction, et lorsqu’on l’exécute, on obtient bien une valeur numérique. Une écriture plus rammassée est également possible :

console.log("test3:", f1(5)());