--- title: Creative Coding with P5.js tags: frontend, javascript, typescrypt robots: noindex, nofollow author: Julien Noyer --- # Creative Coding with P5.js *Using the metaphor of a sketch, p5.js has a full set of drawing functionality* ![](https://i.imgur.com/YpCHSs8.png) > &copy; [Julien Noyer](https://www.linkedin.com/in/julien-n-21219b28/) - All rights reserved for educational purposes only --- ## Présentation de P5.js [P5.js](https://p5js.org) est une librairie qui à pour ambition de rendre accessible et plus lisible la génération de visuels sur une canvas HTML. Nullement réservée à des profils expert, l'objectif de P5.js est de permettre à des designers de débuter dans la création graphique assitée par ordinateur. Très inspirée par le [langage Processing](https://fr.wikipedia.org/wiki/Processing), P5.js est un outil idéal car il permet en plus de la création graphique, d'apprendre des principes qui sont ensuite transposables dans d'autres pratiques. <br> <iframe width="100%" height="350" src="https://www.youtube.com/embed/QYjPBcqo1-s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <br><br> Ce support de cours à pour objectif de présenter un principe de mise en place de P5.js, très largement inspiré du travail remarquable de [Daniel Shiffman](https://shiffman.net) sur sa chaine Youtube [The Coding Train](https://www.youtube.com/c/TheCodingTrain/featured). <br> ## Initialiser projet avec [P5.js](https://p5js.org) [P5.js](https://p5js.org) est une bibliothèque [Front End](https://fr.wikipedia.org/wiki/Développement_web_frontal), dans le cadre de ce document nous allons l'utiliser sur une page HTML que nous configurons de cette manière : ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <!-- [FRONTEND] Basic configuration --> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>P5.js Library for Creative Design</title> <!-- [CDN] Load P5.js library --> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script> <!-- [JS] Load inner scripts --> <script src="./js/sketch.js"></script> </head> <body></body> </html> ``` <br> La page HTML chage un fichier `sketch.js` qui doit contenir le code principal de la page, dans lequel nous plaçons le code suivant pour configurer [P5.js](https://p5js.org) : ```javascript= /* To use variables in a P5.js project, you need to declare them at the top of your Javascript document, and add value to them within the preload() function. */ // Video sizing let canvasWidth = null; let canvasHeigth = null; // Main variables let shape = null; let mainCircle = null; let backgroundCircles = null; // /* The setup() function is called once when the program starts. It's used to define initial environment properties. => https://p5js.org/reference/#/p5/setup */ function setup(){ // Define canvas size canvasWidth = window.windowWidth; canvasHeigth = window.windowHeight; // Define main circle value shape = { x: canvasWidth / 2, y: canvasHeigth / 2, width: 60, weight: 5, colors: { stroke: `rgba(255, 255, 255, 0.5)`, fill: `rgba(196, 56, 56, 1)`, } } // Set image rate frameRate(30); // Sketch control (noLoop(), redraw() or loop()) loop(); // Create a canvas element in the document createCanvas(canvasWidth, canvasHeigth); }; // /* The draw() function is called juste after the setup function. => https://p5js.org/reference/#/p5/draw */ function draw(){}; // ``` --- <br><br> # Manipuler un caneva HTML avec P5.js *Principe de base pour créer et manupiler des formes* ![](https://i.imgur.com/yV6AfnJ.png) --- Les fichiers HTML et Javascript que nous avons créé ci-dessus n'à eu pour effet que d'afficher simplement une balise `<canvas>` dans la page HTML. Nous avons défini la taille de cette balise dans la fonction `preload()`, nous allons à présent editer la fonction `draw()` pour créer une première forme : ```javascript= /* The draw() function is called juste after the setup function. It should always be controlled with noLoop(), redraw() and loop(). => https://p5js.org/reference/#/p5/draw */ function draw(){ // Define canvas background color background('rgba(26, 28, 29, 1)'); // Create an elispe in the center of the canvas ellipse( shape.x, // Intial shape x position shape.y, // Intial shape y position shape.width, // Intial shape width shape.width // Intial shape heigth ); }; // ``` <br> La création de cette première forme nous permet de définir les propriétés que vous être les plus manipulées pour créer des formes : les axes `x` et `y`. Du fait que la création de visuels avec P5.js soit uniquement réalisé en Javascript, c'est avec des techniques de programations orientées objet et de coordonnées géographiques que nous devons u parvenir. <br> ## Origanisation des formes Bien que les fonctions spécifiques à P5.js soient à écrire dans des versions antérieur à ES6, il est tout à fait possible d'associer des techniques en programmation Javascript plus évoluée, comme par exemple l'utilisation de classe. Le principe de programmation orienté offre un avantage indéniable lorsque'il s'agit de manipuler plusieurs éléments, dans le cadre du Design Génératif ça semble essentiel. Nous allons donc créer un fichier nommé `shape.model.js` que nous plaçons dans le dossier `js` avant de l'importer dans le fichier HTML, juste avant le fichier `sketch.js` pour respecter la hiérarchie de fichier. Ce fichier contiendra les différentes classes que nous allons créer pour nos forme, en commençant par celle pour générer une ellipse : ```javascript= /* Class Circle Define to create circle */ class Circle { constructor(x, y, width, heigth, strokeWeight = null, strokeColor = null, fillColor = null, index = 0) { // Use `Vector` to set position this.position = new p5.Vector(x, y); // Set shape variables this.width = width; this.heigth = heigth; this.strokeWeight = strokeWeight; this.strokeColor = strokeColor; this.fillColor = fillColor; // Set shape index this.index = index; } display() { // Set colors this.strokeColor ? stroke(this.strokeColor) : noStroke(); this.strokeWeight ? strokeWeight(this.strokeWeight) : 0; this.fillColor ? fill(this.fillColor) : noFill(); // Create ellipse ellipse(this.position.x, this.position.y, this.width, this.heigth); } } // ``` <br> Une fois cette classe créée, il suffi de modifier la fonction `draw()` pour pour créer un cercle de la manière suivante : ```javascript= /* The draw() function is called juste after the setup function. It should always be controlled with noLoop(), redraw() and loop(). => https://p5js.org/reference/#/p5/draw */ function draw(){ // Create main circle mainCircle = new Circle( shape.x, shape.y, shape.width, shape.width, shape.weight, shape.colors.stroke, shape.colors.fill, ); // Define canvas background color background('rgba(26, 28, 29, 1)'); // Call `display()` method to add circle in canvas mainCircle.display(); }; // ``` <br> ## Manipuler des formes Que ce soit pour les déplacer dans le canevas, detecter lorsqu'elles se touchent ou modifier leur forme, la manipulation d'un objet est une étape importante à intégrer pour décupler les possibilitées créatives. Avant de commencer une faut prendre conscience d'un phénomène important, avec P5.js on ne déplace par une forme, on la dessine à un autre endroit. Le principe de la fonction `draw()` et d'écrire un visuel complet dans le canevas à la fréquence définie dans le fonction `setup()`. Le principe est dont le même que pour l'annimation traditionnelle, il s'agit de réaliser des animation, image par image, avec l'avantage de travailler en Javascript et de pouvoir exécuter des calcules complèxes. Nous allons donc faire en sorte de pouvoir déplacer notre cercle en appuyant sur les touche directionnelles de notre clavier. Pour ce faire nous devons dans un premier temps rendre dynamique les coordonnées `x` et `y` de notre cercle car c'est la modification de ces valeurs qui permettra de faire bouger le cercle via une fonction que nous crérons dans un nouveau fichier nommé `helpers.js`. Il nous faut dans un premier temps être capable de capter les touches du clavier, P5.js dispose d'un varaible nommée `keyIsPressed` qui indique si une touche est activée. Cette variable est accessible dans la méthode `draw()` que nous modifions de la manière suivante : ```javascript= function draw(){ // Create main circle mainCircle = new Circle( shape.x, shape.y, shape.width, shape.width, shape.weight, shape.colors.stroke, shape.colors.fill, ); // Define canvas background color background('rgba(26, 28, 29, 1)'); // Call `display()` method to add circle in canvas mainCircle.display(); // Bind key press to update x and y position if (keyIsPressed) { [ shape.x, shape.y ] = keyMove(shape.x, shape.y, 10, canvasWidth, canvasHeigth, 60) }; }; ``` <br> Juste avant de créer notre cercle, nous modifions sa position grâce à une fonction `keyMove()` que nous allons créer ultérieurement. Dans la mesure ou les valeurs des propriétés de `mainCircle` sont définies dans la méthode `setup()` et que la méthode `draw()` tourne en boucle, il est possible d'incrémenter légérement les valeurs pour générer un déplacement. Déplacer une forme consite à appeler une fonction qui modifié des valeurs, cette fonction représente un calcule qui permet de générer une résultat qui est ensuite utilisé pour modifier un état. Nous aurons à réaliser d'autre type de calcule, c'est pourquoi nous devons créer un fichier nommé `helper.js` pour y écrire les différentes fonctions de calcule dont nous aurons besoin. Après avoir créé et importé le fichier dans la page HTML, nous pouvons ajouter la fonction suivante qui modifie deux valeur selon la touche directionnelle pressée : ```javascript= /* Function to move position with key @params{number} x: shape x axe @params{number} y: shape y axe @params{number} velocity: mouvement @params{number} bounceX: right limit @params{number} bounceY: bottom limit @params{number} size: shape size */ function keyMove(x, y, velocity, bounceX, bounceY, size){ // Switch key code switch(keyCode){ case RIGHT_ARROW: if( x + (size/2) + velocity < bounceX ){ x += velocity } break; case LEFT_ARROW: if( x - velocity > (size/2) ){ x -= velocity; } break; case UP_ARROW: if( y - velocity > (size/2) ){ y -= velocity; } break; case DOWN_ARROW: if(y + (size/2) + velocity < bounceY){ y += velocity } break; default: break; } // Return updated position return [ x, y ]; } // ``` > Les mots clefs "keyCode", "RIGHT_ARROW", ... sont propres à P5.js. <br> ## Randomiser la création de forme Nous venons de créer tous les éléments dont nous avons besoin pour réaliser des projets plus complet. La mise en place qui vous est présentée dans ce document à pour but de structurer votre création à traver différents fichiers : - le fichier `index.html` contenant un DOM classique - le fichier `shape.model.js` contenant les constructeurs de formes - le fichier `helpers.js` contenant les focntions de calcules - le fichier `sketch.js` contenant la logique de création <br> Nous allons nous baser sur cette structure pour aborder d'autres notions, comme par exemple celle qui permet de randomiser des valeurs. Les valeurs aléatoires sont très simples à définir, en Javascript l'objet `Math` propose diférentes méthodes que nous allons utiliser dans le fichier `helpers.js` pour créer la fonction suivante : ```javascript= /* Function to define random value @params{number} min: first step @params{number} max: last step @params{boolean} floor: option to set floor */ function randomize(min, max, floor = false){ // Define random value const rando = Math.random() * max; // Return random if(!floor){ return rando + min } else{ return Math.floor( rando + min ) }; } // ``` <br> Nous allons à présent utiliser cette méthode pour générer un nombre aléatoire de cercle, disposé aléatoirement sur le canvas et dont les couleurs seront également aléatoires. Oui, le quelques ligne de code que nous venons d'ajouer nous le permettent. Nous ouvrons à présent le fichier `helpers.js` pour ajouter la fonction suivante : ```javascript= /* Function to define random value No params needed */ function generateCircles(){ // Define tmp value let tmpArray = []; // Create a loop between 10 and 15 for ( let i = 0; i < randomize(20, 50, true); i++ ) { // Generate size between 10 and 40 const size = randomize(10, 20, true); // Create and display new circle tmpArray.push( new Circle( randomize(150, (window.windowWidth - 205), true), randomize(150, (window.windowHeight - 250), true), size, size, randomize(2, 4, true), `rgba(${randomize(100, 200, true)}, ${randomize(100, 200, true)}, ${randomize(100, 200, true)}, 0.5)`, `rgba(${randomize(100, 200, true)}, ${randomize(100, 200, true)}, ${randomize(100, 200, true)}, 1)`, i, false, ) ); } // Return value return tmpArray; } // ``` <br> Nous pouvons à présent appeler cette fonction dans le fichier `sketch.js` au niveau de la fonction `setup()` pour générer des ellipse et les injecter dans le tableau `backgroundCircles` de cette manière : ```javascript= /* The setup() function is called once when the program starts. It's used to define initial environment properties. => https://p5js.org/reference/#/p5/setup */ function setup(){ // Define canvas size canvasWidth = window.windowWidth; canvasHeigth = window.windowHeight; // Define main circle value shape = { x: canvasWidth / 2, y: canvasHeigth / 2, width: 60, weight: 5, colors: { stroke: `rgba(255, 255, 255, 0.5)`, fill: `rgba(196, 56, 56, 1)`, } } // Set image rate frameRate(30); // Sketch control (noLoop(), redraw() or loop()) loop(); // Create a canvas element in the document createCanvas(canvasWidth, canvasHeigth); // Generate background circle value backgroundCircles = generateCircles(); }; // ``` <br> Et pour finir cette intégration et afficher les ellispes générées alétoirement dans le cavans, nous modifions la fonction `draw()` de la manière suivante : ```javascript= /* The draw() function is called juste after the setup function. => https://p5js.org/reference/#/p5/draw */ function draw(){ // Create main circle mainCircle = new Circle( shape.x, shape.y, shape.width, shape.width, shape.weight, shape.colors.stroke, shape.colors.fill, ); // Define canvas background color background('rgba(26, 28, 29, 1)'); // Display background circles for ( let i = 0; i < backgroundCircles.length; i++ ) { // Display new circle backgroundCircles[i].display(); } // Call `display()` method to add circle in canvas mainCircle.display(); // Bind key press to update x and y position if (keyIsPressed) { [ shape.x, shape.y ] = keyMove(shape.x, shape.y, 10, canvasWidth, canvasHeigth, 60) }; }; // ``` <br> Pour finir cette exploration des principe de base de la manipulation d'éléments graphique avec P5.js, nous allons ajouter la possibilité de détéecter des colision entre des éléments. Nous allons faire en sorte de capter le moment ou le cercle qui se déplace touche un des cercles générer aléatoirement. Pour ce faire, nous allons ajouter la méthode suivante dans la classe `Circle` : ```javascript= checkColision(circleCollection, mainShape){ for( let item of circleCollection ){ // Get distances between the balls components let distanceVect = p5.Vector.sub(item.position, this.position); // Calculate magnitude of the vector separating the balls let distanceVectMag = distanceVect.mag(); // Minimum distance before they are touching let minDistance = (this.width/2); if(distanceVectMag < minDistance && !item.hidden){ // Update main shape mainShape.width += (item.width / 3); mainShape.colors.stroke = item.strokeColor; mainShape.colors.fill = item.fillColor; // Hide touched circle item.hidden = true; } } } ``` <br> Enfin, nous editons la fontion `draw()` du fichier `sketech.js` afin de capter les colisions de la manière suivante : ```javascript= /* The draw() function is called juste after the setup function. => https://p5js.org/reference/#/p5/draw */ function draw(){ // Create main circle mainCircle = new Circle( shape.x, shape.y, shape.width, shape.width, shape.weight, shape.colors.stroke, shape.colors.fill, ); // Define canvas background color background('rgba(26, 28, 29, 1)'); // Display background circles for ( let i = 0; i < backgroundCircles.length; i++ ) { // Display new circle backgroundCircles[i].display(); } // Call `display()` method to add circle in canvas mainCircle.display(); // Bind key press to update x and y position if (keyIsPressed) { [ shape.x, shape.y ] = keyMove(shape.x, shape.y, 10, canvasWidth, canvasHeigth, 60) }; // Get collision on main circle mainCircle.checkColision(backgroundCircles, shape); }; // ```