Créer une horloge dans le navigateur avec JavaScript et p5.js

A l'aide de la bibliothèque JavaScript p5.js, il est possible d'extraire des données, de les manipuler et de les exploiter sous la forme d'une création graphique. Nous allons voir comment, à partir des données de l'horloge de l'appareil, créer une pendule avec les aiguilles qui tournent en temps réel pour afficher l'heure (après tout, c'est bien là la fonction d'une horloge).
Les outils nécessaires à cet exercice sont :
  • un éditeur de texte (Brackets par exemple)
  • un navigateur Internet
  • le fichier de la bibliothèque JavaScript p5.js (à télécharger à partir du site de p5 dans l'onglet « Download »)

La page HTML

Créons tout d'abord une page HTML classique. Dans l'entête, nous insérons les liens vers le fichier de la bibliothèque p5 et vers le fichier de l'animation elle-même :
<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>horloge</title>
        <script type="application/javascript" src="p5.min.js"></script>
        <script type="application/javascript" src="animation.js"></script>
    </head>
    <body>
    </body>
</html>
À vous de déterminer les liens relatifs en fonction de l'organisation des dossiers de fichiers. Le fichier HTML est déjà terminé. Enregistrons-le, nous pouvons passer au code JavaScript.

Le dessin en JavaScript

Créons le fichier « animation.js » à l'emplacement déterminé dans la page HTML.
Un fichier p5 s'articule autour de deux fonctions : setup() et draw(). La fonction setup() est lue une fois au démarrage du script et contient les paramètres de notre animation comme la taille du canvas (la zone de dessin), la valeur d'une variable au départ...
À l'inverse, la fonction draw() est lue à chaque rafraîchissement de l'écran, en boucle, ce qui en fait le moteur de l'animation.
Pour commencer notre fichier JavaScript, créons nos deux fonctions :
function setup() {
}

function draw() {
}
Maintenant que la structure de base est prête, entrons quelques éléments.
Il faut créer l'espace de dessin (canvas) avec la fonction createCanvas(largeur, hauteur). Cette fonction, qui prend comme paramètres la largeur et la hauteur, met en place la zone de dessin.
La fonction smooth() permet d'adoucir les contours des formes dessinées.
Le système de couleurs utilisé couramment dans Processing (le mode RVB pour Rouge-Vert-Bleu) est utilisable dans p5. Il y a 256 niveaux de couleur pour chacune des trois variables, de 0 à 255. (0, 0, 0) donnera du noir tandis que (255, 255, 255) donnera du blanc. Pour créer une nuance de gris, comme les trois valeurs sont identiques, on peut se contenter d'écrire une fois la valeur : nous écrirons background(255) pour obtenir un fond blanc.
Commençons par préparer notre arrière-plan :
function setup() {
  createCanvas(400, 400);
  smooth();
  background(255);
}
Nous venons de créer un dessin carré de 400 pixels de côté de couleur blanche.
Le code p5 reprend la structure d'un code JavaScript pour dessiner une forme. Il faut d'abord définir les couleurs de remplissage et de contour de la forme (fill et stroke) et l'épaisseur du contour en pixels (strokeWeight) avant de définir la forme dessinée et son positionnement.
Pour dessiner le cadran de l'horloge, il faut faire une ellipse. Nous la dessinerons dans la fonction draw() car faisant partie de la zone à rafraîchir en permanence.
function draw() {
  fill(255);
  stroke(50);
  strokeWeight(1);
  ellipseMode(CENTER);
  ellipse(width/2, height/2, 300, 300);
}
Vous avez dessiné un cercle partant du centre (largeur/2 et hauteur/2) de 300 pixels de diamètre, de couleur blanche avec un contour d'un pixel de couleur gris foncé.
Pour supprimer l'effet d'aliasing sur le contour du cercle, replacez la description de l'arrière-plan (background) au début de la fonction draw(). Ainsi, à chaque répétition de la boucle, le fond du cadre se redessine sur un nouvel arrière-plan.
Écrire du texte dans notre canvas est simple : appelons la fonction text("notre texte", positionLargeur, positionHauteur). Le positionnement d'un élément dans le canvas se fait à partir du point initial haut gauche noté 0, 0. La taille du texte se paramétrant avec textSize() en pixels, l'alignement avec textAlign(CENTER) pour le centrer. Toujours dans draw(), après notre cercle :
  fill(0);
  textSize(20);
  textAlign(CENTER);
  text('12', width/2, height/2-100);
  text('3', width/2+110, height/2);
  text('6', width/2, height/2+120);
  text('9', width/2-110, height/2);
Pour dessiner les repères de cadran, utilisons une boucle for.
Pour faire nos repères de cadran, nous dessinerons de petits traits, line(pointDépartX, pointDépartY, pointArrivéeX, pointArrivéeY) en tournant autour du point central. Pour ce faire, initialisons, au début du draw(), le centre du dessin comme le point initial :
  translate(width/2, height/2);
Si vous testez votre animation, vous vous apercevrez que le cadran est décalé vers l'extérieur. Il faut recentrer les positions des éléments, ceux-ci étaient encore placés par rapport au point haut gauche.
Faisons un point sur notre code :
function setup() {
  createCanvas(400, 400);
  smooth();
}

function draw() {
  translate(width/2, height/2) ;
  background(255);
  fill(255);
  stroke(50);
  strokeWeight(1);
  ellipseMode(CENTER);
  ellipse(0, 0, 300, 300);
  fill(0);
  textSize(20);
  textAlign(CENTER);
  text('12', 0, -100);
  text('3', 110, 0);
  text('6', 0, 120);
  text('9', -110, 0);
}
Dessinons les lignes. Après le texte, toujours dans la boucle draw(), placez ce code :
  fill(0);
  rectMode(CENTER);
  for (var i = 0; i < 60; i++) {
    stroke(100);
    strokeWeight(1);
    line(0, 150, 0, 130);
    rotate(PI / 30);
  }
  for (var i = 0; i < 12; i++) {
    rect(0, 140, 8, 20);
    rotate(PI / 6);
  }
Qu'avons nous fait ?
Dans une première boucle, nous avons dessiné 60 fois une ligne d'un pixel d'épaisseur et d'une longueur de 10 pixels, à 130 pixels de distance de notre centre, en gris foncé. La rotation de PI/30 (un cercle complet correspondant à 2 fois PI, PI/30 = 180/30 = 6 degrés) permet de faire le tour du cadran par pas de 6°. Les repères des minutes sont placés.
Dans la deuxième boucle, nous avons dessiné 12 fois un rectangle de 8 pixels sur 20 avec une rotation de 30° entre chaque occurrence. Les heures sont indiquées.
Maintenant que le cadran est prêt, il est temps de passer à la partie animée de notre horloge.
La fonction noStroke() permet de ne pas afficher de contour sur une forme.
Il nous faut trois aiguilles : pour les heures, les minutes et les secondes. Elles seront dessinées sous la forme de rectangles de différentes tailles comme pour tout cadran d'une montre. Celle des secondes sera colorée avec une forme circulaire à proximité de son extrémité extérieure. Celle des heures sera plus large et courte que les autres.
Voici le dessin des aiguilles :
  noStroke();
  // aiguille des secondes
  fill(150, 80, 0);
  rect(0, - 70, 2, 140);
  ellipse(0, - 120, 10, 10);
  // aiguille des minutes
  fill(0);
  rect(0, - 68, 2, 136);
  // aiguille des heures
  fill(0);
  rect(0, - 40, 10, 80);
  ellipse(0, - 80, 10, 10);
  fill(255);
  ellipse(0, - 76, 6, 6);
Nous sommes toujours sur des formes positionnées par rapport à leur centre, d'où les coordonnées 0 en largeur (centrée) et -40 en hauteur pour une aiguille de 80 pixels de long. Les trois aiguilles sont placées l'une sur l'autre, il est l'heure de régler l'horloge.

Conception d'une animation avec p5

Préparons les variables.
La position d'une aiguille sera déterminée par l'angle appliqué au dessin du rectangle qui la représente, ce dernier étant calculé à partir des données que nous récupérons de l'appareil.
Pour obtenir l'heure, nous avons besoin de trois fonctions de p5 : hour(), minute() et second(). Celles-ci récupèrent les données sous forme de nombre : entre 0 et 23 pour les heures, 0 et 59 pour les minutes et les secondes.
Nous avons déterminé qu'une seconde représentait un angle de 6° sur notre cadran (soit PI/30).
Nous créons, avant la fonction setup() une première variable « division » prenant cette valeur :
  var division = PI/30 ;
Les aiguilles se dessinent en appliquant un angle de rotation aux rectangles. Nous avons besoin des variables :
  var angle_secondes, angle_minutes, angle_heures;
Pour positionner l'aiguille des secondes, notre programme aura donc besoin de déterminer, outre la forme de l'aiguille, l'angle de cette aiguille par rapport à la position initiale qui correspond à zéro seconde. Nous multiplions notre variable division par le nombre de secondes. Dans la boucle draw(), juste avant la commande de dessin de l'aiguille, écrivons :
  angle_secondes = second() * division;
  rotate(angle_secondes);
Pour l'aiguille des minutes, il nous faut appliquer un mouvement continu, qui correspond au déplacement naturel de cette aiguille. L'angle est donc calculé en additionnant le nombre récupéré avec minute() et l'angle des secondes divisé par 60 (il y a 60 secondes dans l'intervalle d'une minute). Plaçons ce code avant de dessiner l'aiguille des minutes :
  angle_minutes = minute() * division;
  rotate(angle_minutes + (angle_secondes / 60));
L'aiguille des heures fonctionne sur le même principe que celle des minutes, nous calculons l'angle à partir de du nombre d'heures et d'une fraction du nombre de minutes. Attention, il y a douze heures par tour de cadran, nous ne prenons donc pas PI/30 comme fraction de rotation initiale, mais P/6.
  angle_heures = hour() * PI / 6;
  rotate(angle_heures + (angle_minutes / 12));
Mais le nombre d'heure peut être supérieur à 12, cela ne posera-t-il pas problème ?
Non, l'aiguille comptabilisera un tour (et reviendra à son point de départ) plus la fraction de tour du cadran à afficher.
Si vous testez ce code maintenant, vous observez un problème de taille : les rotations s'additionnent et les aiguilles n'indiquent pas l'heure mais une rotation d'ensemble. Il nous faut isoler les parties du code correspondant à chaque aiguille pour que la rotation calculée ne s'applique qu'au rectangle en question. Découvrons l'utilité des fonctions push() et pop().
La fonction push() ouvre une séquence de code isolée du reste du code : les opérations de translation et de rotation placées dans cette séquence ne s'appliqueront pas au reste du code. La fonction pop(), indispensable au binôme, clôt notre séquence de code.
Voici le code de dessin des aiguilles :
  // aiguille des secondes
  push();
  fill(150, 80, 0);
  angle_secondes = second() * division;
  rotate(angle_secondes);
  rect(0, - 70, 2, 140);
  ellipse(0, - 120, 10, 10);
  pop();
  // aiguille des minutes
  push();
  fill(0);
  angle_minutes = minute() * division;
  rotate(angle_minutes + (angle_secondes / 60));
  rect(0, - 68, 2, 136);
  pop();
  // aiguille des heures
  push();
  fill(0);
  angle_heures = hour() * PI / 6;
  rotate(angle_heures + (angle_minutes / 12));
  rect(0, - 40, 10, 80);
  ellipse(0, - 80, 10, 10);
  fill(255);
  ellipse(0, - 76, 6, 6);
  pop();
Pour compléter le cadran, deux ellipses centrées se placent au dessus des aiguilles et symbolisent le pivot autour duquel l'action se joue.
Voici le code complet de notre animation :
var angle_secondes, angle_minutes, angle_heures, division;
 
function setup() {
  createCanvas(400, 400);
  smooth();
  frameRate(3);
  division = PI / 30;
}
function draw() {
  background(255);
  translate(width / 2, height / 2);
  // dessin du fond du cadran
  fill(255, 180, 0);
  stroke(50);
  strokeWeight(1);
  ellipseMode(CENTER);
  ellipse(0, 0, 300, 300);
  fill(210);
  strokeWeight(2);
  ellipse(0, 0, 290, 290);
  noStroke();
  fill(255);
  ellipse(0, 4, 280, 270);
  fill(51);
  textSize(20);
  textAlign(CENTER);
  text('12', 0, - 100);
  text('3', 110, 5);
  text('6', 0, 120);
  text('9', - 110, 5);
  fill(255, 180, 0);
  textSize(12);
  text('pictalink', 0, 30);
  fill(0);
  rectMode(CENTER);
  for (var i = 0; i < 60; i++) {
    stroke(100);
    strokeWeight(1);
    line(0, 150, 0, 130);
    rotate(PI / 30);
  }
  for (var i = 0; i < 12; i++) {
    rect(0, 140, 8, 20);
    rotate(PI / 6);
  }  // fin du dessin du fond du cadran

  noStroke();
  // aiguille des secondes
  push();
  fill(150, 80, 0);
  angle_secondes = second() * division;
  rotate(angle_secondes);
  rect(0, - 70, 2, 140);
  ellipse(0, - 120, 10, 10);
  pop();
  // aiguille des minutes
  push();
  fill(0);
  angle_minutes = minute() * division;
  rotate(angle_minutes + (angle_secondes / 60));
  rect(0, - 68, 2, 136);
  pop();
  // aiguille des heures
  push();
  fill(0);
  angle_heures = hour() * PI / 6;
  rotate(angle_heures + (angle_minutes / 12));
  rect(0, - 40, 10, 80);
  ellipse(0, - 80, 10, 10);
  fill(255);
  ellipse(0, - 76, 6, 6);
  pop();
  fill(50);
  stroke(150, 100, 100);
  ellipse(0, 0, 16, 16);
  fill(255);
  ellipse(0, 0, 8, 8);
}
Voilà, votre horloge s'affiche dans le navigateur.

Commentaires

Enregistrer un commentaire

Posts les plus consultés de ce blog

Pour les débutants : 3 méthodes faciles pour créer un texte incurvé dans Inkscape

Débuter avec les dégradés de couleurs dans Inkscape

Pour les débutants : utiliser les guides dans le logiciel Inkscape pour préparer une animation