Avez-vous déjà exploré les outils de tableau blanc collaboratif qui pullulent sur Internet ? Ils sont d’une grande aide pour stimuler la créativité et favoriser la collaboration en temps réel, tout comme si nous étions physiquement présents dans la même pièce. Mais avez-vous déjà envisagé de créer un de ces outils vous-même ? Eh bien, j’ai relevé le défi et je suis ravi de partager comment j’ai concrétisé un prototype !
Qu’est ce que SignalR
Commençons par donner une petite définition de ce qu’est SignalR, si quelqu’un se pose la question.
SignalR est une technologie Microsoft utilisée dans les applications pour mettre en œuvre une communication en temps réel, SignalR fournit un tunnel reliant directement le client et le serveur, de cette manière le client (navigateur par exemple) peut recevoir des mises à jour directement du serveur, sans avoir besoin d’actualiser le navigateur ou envoyer des requêtes au serveur. Des exemples d’applications pourraient être : des chats en temps réel, des outils collaboratifs, des mises à jour boursières, etc.
Préparons le terrain
Commençons par créer une application Web MVC dans Visual Studio, j’utilise .NET 6 pour cet article.
Une fois que vous avez votre projet nouvellement créé, ajoutons l’ingrédient principal SignalR
Vous pouvez le faire de différentes manières, j’utilise personnellement l’outil de gestion de bibliothèque de Visual Studio, pour ce faire, faites un clic droit sur le dossier wwwroot, aller sur Ajouter puis Bibliothèque côté client
Dans la fenêtre qui s’ouvre, tapez microsoft-signalr et choisissez la première suggestion sur la liste
Vous pouvez désormais soit inclure tous les fichiers, soit ne prendre que la version minifiée, cela dépend de vos préférences. J’ai personnellement pris tous les fichiers du dossier
Cliquez sur installer et dans votre dossier wwwroot vous devriez voir maintenant un nouveau dossier nommé microsoft-signalr avec le fichier signalr.js ou signalr.min.js.
Maintenant que nous avons avons toutes les dépendances requises pour SignalR, ajoutons ces fonctionnalités dans notre code.
En un début nous avons ajouter les fichiers js qui permettent d’utiliser SignalR dans notre _Layout.cshtml
<script src="~/microsoft-signalr/signalr.min.js"></script>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
On a setup notre environnement avec SignalR, c’est super. Pour le moment nous allons nous en arrêter là, nous reprendrons un peu plus loin dans l’article, concentrons nous déjà sur la mise en place de notre tableau blanc avec HTML et Javascript, après tout il faudrait déjà avoir un tableau qui marche avant de parler de collaboration en temps réel :-p
Nous allons maintenant travailler sur notre fichier Index.cshtml dans le dossier Views/Home
Nous allons juste créer un canvas (un canvas est un élément HTML qui permet de dessiner des graphiques à la volée avec Javascript) nous allons lui attribuer un id canvas, qui pourra nous permettre de le référencer dans le Javascript.
Après cela, nous allons ajouter le code suivant sur le fichier site.css dans wwwroot/css/site.css
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
Ce bout de code permet juste de changer l’aspect du curseur dans le Canvas et ainsi que de dessiner une délimitation autour de celui-ci.
Nous pouvons run notre projet pour la première fois et voir notre canvas sur la page d’accueil
On peut pas faire grand chose dessus pour le moment mais ça viendra
Maintenant qu’on a notre canvas en place, travaillons sur l’implémentation des fonctionnalités de dessins
Nous allons maintenant principalement travailler sur le ficher site.js dans wwwroot/js
Nous allons d’abord commencer par déclarer plusieurs variables, qui nous serviront tout au long du process
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasx = $(canvas).offset().left;
var canvasy = $(canvas).offset().top;
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
var tooltype = 'draw';
Voyons un peu de quoi il s’agit.
- Récupération de l’élément canvas et du contexte 2D :
var canvas = document.getElementById('canvas');
: Cela obtient l’élément canvas HTML en utilisant son ID.var ctx = canvas.getContext('2d');
: Cela obtient le contexte 2D du canvas, qui est nécessaire pour effectuer des opérations de dessin.
2. Obtention des coordonnées du canvas :
var canvasx = $(canvas).offset().left;
: Cette ligne obtient la position horizontale (coordonnée x) du canvas par rapport au document.var canvasy = $(canvas).offset().top;
: Cette ligne obtient la position verticale (coordonnée y) du canvas par rapport au document.
3. Initialisation de variables de suivi de la souris :
var last_mousex = last_mousey = 0;
: Ces variables stockent les coordonnées de la souris lors de la dernière interaction.var mousex = mousey = 0;
: Ces variables stockent les coordonnées actuelles de la souris.var mousedown = false;
: Cette variable indique si le bouton de la souris est enfoncé (true) ou non (false).var tooltype = 'draw';
: Cette variable indique le type d’outil actuellement sélectionné. Dans ce cas, l’outil est draw (dessin).
Pour qu’on puisse dessiner sur le canvas il faudrait, qu’on clique est qu’on maintienne le clic gauche sur le canvas et inversement pour arrêter de dessiner il faudrait qu’on relâche le clic gauche, sachant cela nous allons jouer sur les événements mousedown et mouseup de la souris, pour savoir respectivement si il est appuyer ou relâcher, insérons le code suivant.
$(canvas).on('mousedown', function (e) {
console.log("mouse down")
last_mousex = mousex = parseInt(e.clientX - canvasx);
last_mousey = mousey = parseInt(e.clientY - canvasy);
mousedown = true;
});
$(canvas).on('mouseup', function (e) {
mousedown = false;
});
Ce code réagit à un clic de souris (événement mousedown) sur un élément HTML avec l’ID canvas. Il enregistre les coordonnées de la position de la souris par rapport à ce canvas et indique que le bouton de la souris est enfoncé ou relâcher à travers notre témoin mousedown.
Savoir quand est ce qu’on clique sur la souris et récupérer la position, c’est une chose, dessiner, une autre.
Implémentons, ensemble la fonction qui nous permettra de dessiner sur notre canvas.
var drawCanvas = function (prev_x, prev_y, x, y, clr) {
ctx.beginPath();
console.log("X: " + x + " Y: " + y);
ctx.globalCompositeOperation = 'source-over';
ctx.strokeStyle = clr
ctx.lineWidth = 3;
ctx.moveTo(prev_x, prev_y);
ctx.lineTo(x, y);
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
};
Passons ce code à la loupe.
ctx.beginPath();
: Cette ligne commence un nouveau chemin de dessin sur le contexte 2D du canvas (ctx
). Un chemin est essentiel pour dessiner des formes ou des lignes.console.log("X: " + x + " Y: " + y);
: Cela affiche les coordonnées actuellesx
ety
dans la console du navigateur à des fins de débogage. Il permet de suivre les coordonnées de l’endroit où le dessin est effectué.ctx.globalCompositeOperation = 'source-over';
: Cette ligne définit le mode de composition. “source-over” signifie que le dessin sera superposé au contenu existant du canvas sans effacer quoi que ce soit.ctx.strokeStyle = clr;
: Ici, la couleur du trait (strokeStyle
) est définie en utilisant la valeur declr
, qui est généralement une couleur spécifiée (par exemple, “red” ou “#00FF00”).ctx.lineWidth = 3;
: Cette ligne définit l’épaisseur du trait à 3 pixels. Cela détermine l’épaisseur des lignes dessinées.ctx.moveTo(prev_x, prev_y);
: Cette instruction déplace le “stylet” virtuel du canvas à la position précédente (prev_x
,prev_y
) sans dessiner de ligne.ctx.lineTo(x, y);
: Cette ligne trace une ligne depuis la position précédente (prev_x
,prev_y
) jusqu’à la position actuelle (x
,y
). C’est ici que le dessin réel est effectué.ctx.lineJoin = ctx.lineCap = 'round';
: Ces deux instructions définissent le style de fin et de jonction des lignes. “round” indique que les extrémités et les coins des lignes seront arrondis.ctx.stroke();
: Enfin, cette ligne effectue le rendu du dessin sur le canvas. Elle trace la ligne entre les points précédents et actuels, utilisant les paramètres spécifiés.
Maintenant que nous pouvons savoir quand l’utilisateur a cliqué sur sa souris et nous avons la fonction nécessaire pour dessiner sur notre canvas, la dernière partie nécessaire pour assembler le tout et faire le dessin quand on bouge la souris est de savoir… quand est ce qu’on bouge la souris (oui sérieusement).
C-a-d on va appeler la fonction drawCanvas
quand on détecte un mouvement et un clic sur le bouton gauche, pour cela on a l’événement Javascript mousemove
qui va nous aider.
Ajoutons le code suivant.
$(canvas).on('mousemove', function (e) {
mousex = parseInt(e.clientX - canvasx);
mousey = parseInt(e.clientY - canvasy);
var clr = $('select[id=color]').val()
if ((last_mousex > 0 && last_mousey > 0) && mousedown) {
drawCanvas(mousex, mousey, last_mousex, last_mousey, clr);
}
last_mousex = mousex;
last_mousey = mousey;
});
Psst…passe moi la loupe
$(canvas).on('mousemove', function (e) { ... });
: Cette ligne ajoute un gestionnaire d’événements pour l’événement mousemove (mouvement de la souris) sur l’élément HTML avec l’ID canvas. La fonction passée en argument sera exécutée à chaque mouvement de la souris sur le canvas.mousex = parseInt(e.clientX - canvasx);
: Cette ligne calcule la coordonnée horizontale (x) de la position actuelle de la souris par rapport au coin supérieur gauche du canvas. La valeur est stockée dans la variablemousex
.mousey = parseInt(e.clientY - canvasy);
: Cette ligne fait la même chose que la ligne précédente, mais pour les coordonnées verticales (y) de la position de la souris, qui sont stockées dans la variablemousey
.var clr = $('select[id=color]').val();
: Cette ligne récupère la valeur sélectionnée dans un élément<select>
HTML avec l’ID color. Cette valeur est généralement une couleur qui sera utilisée pour dessiner.if ((last_mousex > 0 && last_mousey > 0) && mousedown) { ... }
: Cette structure conditionnelle vérifie deux conditions :
Si
last_mousex
etlast_mousey
sont supérieurs à zéro, ce qui signifie qu’il y a une position précédente de la souris enregistrée.Si la variable
mousedown
est définie surtrue
, indiquant que le bouton de la souris est actuellement enfoncé.
Si ces conditions sont remplies, les instructions à l’intérieur de la structure conditionnelle seront exécutées.
drawCanvas(mousex, mousey, last_mousex, last_mousey, clr);
: Cette ligne appelle la fonctiondrawCanvas
avec les coordonnées actuelles de la souris (mousex
,mousey
) et les coordonnées précédentes de la souris (last_mousex
,last_mousey
), ainsi que la couleur (clr
). Cela dessine une ligne entre les deux positions.last_mousex = mousex;
: Cette ligne met à jour la variablelast_mousex
avec la nouvelle valeur demousex
, en préparation pour le prochain mouvement de la souris.last_mousey = mousey;
: De même, cette ligne met à jour la variablelast_mousey
avec la nouvelle valeur demousey
pour le prochain mouvement de la souris.
FÉLICITATIONS ! Maintenant nous sommes fin prêt pour dessiner de magnifique figures sur notre canvas. Sauvegarder votre projet et lancer le pour voir le résultat de tout ceci.
Maintenant, signalons le R (riez s’il vous plaît)
Nous avions déjà gérer tout ce qui est dépendance pour SignalR un peu plus haut, nous allons maintenant faire la configuration nécessaire pour que plusieurs personnes puissent se connecter en même temps sur le tableau et gérer les changements en temps réel. Pour cela nous allons d'abord créer un hub.
Euh,… qu’est ce qu’un hub ?
Ah ouais, un hub dans SignalR est un composant clé de la bibliothèque, qui est utilisée pour faciliter la communication en temps réel entre les clients et le serveur dans des applications web. Plus précisément, un hub est une abstraction qui permet aux développeurs de créer des canaux bidirectionnels de communication entre le serveur et les clients de manière simple et efficace.
Voici quelques points clés pour comprendre ce qu’est un hub dans SignalR :
Communication en temps réel : Les hubs sont conçus pour prendre en charge des scénarios de communication en temps réel, ce qui signifie que les informations peuvent être envoyées instantanément entre le serveur et les clients sans nécessiter de rafraîchissement de la page.
Abstraction de haut niveau : Les hubs offrent une abstraction de haut niveau pour gérer la communication. Ils cachent la complexité de la gestion des connexions et des messages sous-jacents, permettant aux développeurs de se concentrer sur la logique applicative.
Canal de communication : Un hub crée un canal de communication bidirectionnel entre le serveur et les clients. Les clients peuvent envoyer des messages au serveur et vice-versa.
Communication multiplateforme : Les hubs SignalR prennent en charge la communication entre des clients et un serveur qui peuvent être basés sur différentes plateformes, notamment des navigateurs web, des applications mobiles, des applications de bureau, etc.
Méthodes et événements : Les hubs permettent aux développeurs de définir des méthodes côté serveur qui peuvent être appelées depuis les clients, ainsi que des événements côté serveur qui peuvent être déclenchés pour informer les clients de certaines actions ou mises à jour.
Gestion de la connexion : Les hubs gèrent automatiquement la gestion des connexions, la reconnexion en cas de défaillance et la gestion de groupes de clients pour simplifier la diffusion de messages à un groupe de clients spécifique.
Scalabilité : SignalR est conçu pour être hautement évolutif, permettant de gérer un grand nombre de connexions en même temps.
En résumé, un hub dans SignalR est un composant puissant qui facilite la mise en place de communications en temps réel dans des applications web.
Pour ce projet, nous allons un hub très simple qui se chargera juste de répliquer les modifications faite par les différents clients en temps réel.
Créer un nouveau dossier Hubs, dans ce dossier créer une classe DrawingHub.cs
Notre classe DrawingHub va hériter de la classe de base Hub de SignalR
Voici l’implémentation
using Microsoft.AspNetCore.SignalR;
namespace SharedWhiteBoard.Hubs
{
public class DrawingHub : Hub
{
public Task Drawing(int prevX, int prevY, int currentX, int currentY, string color)
{
return Clients.Others.SendAsync("drawing", prevX, prevY, currentX, currentY, color);
}
}
}
Lorsque cette méthode Drawing est appelée depuis un client, elle envoie un message à tous les autres clients connectés au hub, informant les autres clients des coordonnées du point de départ et du point d’arrivée d’un dessin, ainsi que de la couleur utiliser pour le dessin. Cela permet de synchroniser le dessin en temps réel entre tous les participants connectés au hub DrawingHub, les paramètres ressemblent étrangement à ce qu’on avez dans le Javascript, humm.
Maintenant, nous devons configurer les points d’extrémité (endpoints) de notre application pour inclure un hub SignalR spécifique, en l’occurrence, le hub DrawingHub
Ca se fera dans le fichier Program.cs
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<DrawingHub>("drawing");
});
Cette ligne indique à l’application de mapper (associer) le hub SignalR DrawingHub à une route spécifique, en l’occurrence, drawing. Voici ce que cela signifie en pratique :
Lorsqu’une requête HTTP arrive à l’URL correspondant à drawing (par exemple, “http://votre-site.com/drawing“), cette ligne de code dirige cette requête vers le hub DrawingHub.
Grâce à cette configuration, le hub DrawingHub est désormais accessible via cette route, ce qui permet aux clients de se connecter et d’interagir avec lui en temps réel.
Retournons sur notre fichier Javascript site.js.
Avec le hub SignalR mis en place, nous allons ajouter la communication en temps réel entre le client et le serveur pour le dessin sur le canvas.
Nous allons d’abord créer la connexion avec SignalR
var connection = new signalR.HubConnectionBuilder()
.withUrl('/drawing')
.build();
Cette partie du code crée une instance de connexion SignalR nommée connection
. La méthode .withUrl('/drawing')
indique au client de se connecter au hub SignalR DrawingHub via l’URL “/drawing”. Cela correspond à la configuration définie précédemment dans Program.cs.
Maintenant que nous avons notre connexion, il s’agira de la démarrer maintenant
connection.start();
Cette ligne démarre la connexion SignalR. Après cela, le client est en mesure d’envoyer et de recevoir des messages en temps réel depuis et vers le hub.
La prochaine étape est d’écouter et de réagir aux messages du serveur, ajouter la ligne suivante
connection.on('drawing', function (prevX, prevY, currentX, currentY, color) {
drawCanvas(prevX, prevY, currentX, currentY, color);
});
Cette partie du code configure le client pour écouter des messages du serveur avec le nom drawing. Lorsque le serveur envoie un message de dessin, la fonction spécifiée (ici, une fonction anonyme) est appelée. Cette fonction appelle ensuite la fonction drawCanvas
pour effectuer le dessin sur le canvas en utilisant les données reçues (les coordonnées et la couleur).
Nous allons maintenant modifier l’événement mousemove sur l’élément canvas pour effectuer deux actions supplémentaires :
Appeler le hub
connection.invoke('drawing', ...)
pour envoyer un message au serveur chaque fois que le mouvement de la souris se produit, ainsi tout les clients sont mis à jour par rapport à ce qui se passe. Ce message comprend les coordonnées actuelles de la souris, les coordonnées précédentes et la couleur du tracé.Il appelle également
drawCanvas
pour effectuer le dessin localement sur le canvas.
$(canvas).on('mousemove', function (e) {
mousex = parseInt(e.clientX - canvasx);
mousey = parseInt(e.clientY - canvasy);
var clr = $('select[id=color]').val()
if ((last_mousex > 0 && last_mousey > 0) && mousedown) {
drawCanvas(mousex, mousey, last_mousex, last_mousey, clr);
connection.invoke('drawing', last_mousex, last_mousey, mousex, mousey, clr);
}
last_mousex = mousex;
last_mousey = mousey;
});
Le code en global donnera ça
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasx = $(canvas).offset().left;
var canvasy = $(canvas).offset().top;
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
var tooltype = 'draw';
$(canvas).on('mousedown', function (e) {l
console.log("mouse down")
last_mousex = mousex = parseInt(e.clientX - canvasx);
last_mousey = mousey = parseInt(e.clientY - canvasy);
mousedown = true;
});
$(canvas).on('mouseup', function (e) {
mousedown = false;
});
var drawCanvas = function (prev_x, prev_y, x, y, clr) {
ctx.beginPath();
console.log("X: " + x + " Y: " + y);
ctx.globalCompositeOperation = 'source-over';
ctx.strokeStyle = clr
ctx.lineWidth = 3;
ctx.moveTo(prev_x, prev_y);
ctx.lineTo(x, y);
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
};
$(canvas).on('mousemove', function (e) {
mousex = parseInt(e.clientX - canvasx);
mousey = parseInt(e.clientY - canvasy);
var clr = $('select[id=color]').val()
if ((last_mousex > 0 && last_mousey > 0) && mousedown) {
drawCanvas(mousex, mousey, last_mousex, last_mousey, clr);
connection.invoke('drawing', last_mousex, last_mousey, mousex, mousey, clr);
}
last_mousex = mousex;
last_mousey = mousey;
});
var connection = new signalR.HubConnectionBuilder()
.withUrl('/drawing')
.build();
connection.on('drawing', function (prevX, prevY, currentX, currentY, color) {
drawCanvas(prevX, prevY, currentX, currentY, color);
});
connection.start();
Maintenant lancer le projet, ouvrez deux onglets avec la même adresse et admirez la magie (bon c’est pas vraiment de la magie hein).
Voilà voilà, plein d’améliorations peuvent être ajouter à ce projet, un sélecteur de couleur par exemple pour que chaque utilisateur puisse choisir la couleur qui lui plaît (j’ai même mis de petits indices dans le code :-)), un sélecteur de type de pinceau, voir les personnes connectées (oui oui c’est bien possible).
À la prochaine.