Accueil

Traduction

Tutoriel Canvas - sommaire

Tutoriel Canvas - recherche

L'auteur : Patrick Darcheville

Vous pouvez me contacter via Facebook pour questions & suggestions : Page Facebook relative à mon site

Des animations plus complexes & jeux

Avec l'API Canvas on peut réaliser des animmations complexes voire des jeux.
Je présente dans ce chapitre quelques astuces pour simplifier la programmation des animations et jeux.

Un diaporama automatique

Ci-dessous un document HTML contenant un canevas qui tient lieu de diaporama.
Toutes les deux secondes affichage d'une nouvelle photo sexy.
Remarquez que les photos sont toujours bien centrées dans le canevas et qu'elles ne sont jamais déforméés.
Par aillleurs elles apparaissent avec deux filtres : effet sépia et ombrage.

Cliquez sur le bouton de commmande pour mettre fin au diaporama.
Actualisez la page pour relancer l'animation.

Le code HTML

Notez que l'élément button appelle une instruction JS sur clic.

Le script

var routine; 
function f1()
{
	var canevas = document.querySelector('canvas'); 
	var contexte = canevas.getContext('2d');
	contexte.filter ="sepia(1) drop-shadow(10px 10px 3px gray)"; 
	var X =canevas.width;
	var Y =canevas.height;

	function ajout()
	{
		contexte.clearRect(0,0,X,Y); 
		var image = new Image(); 
		var reel = Math.random() ;
		var entier = Math.ceil(reel*35) ; 
		switch(entier)
		{
			case 1 : image.src ="../images/bikini.jpg" ; break;
			case 2 : image.src ="../images/bikini2.jpg" ; break;
			...
			case 35 : image.src ="../images/bikini35.jpg" ; break;
		} // fin switch 
		
		image.onload =function()
		{
			largeur =image.width; 
			hauteur =image.height; 
			ratio = hauteur /largeur; 
			largeur_fixee = X/100*60; 
			hauteur_fixee = largeur_fixee * ratio; 
			var x = (X-largeur_fixee)/2;
			var y = (Y-hauteur_fixee)/2;
			contexte.drawImage(image,x,y,largeur_fixee,hauteur_fixee) ;
		} // fin fonction anonyme
	} // fin fonction ajout
	routine = setInterval(ajout,2000);
} // fin f1
f1();  // appel f1

La variable entier contient un entier compris entre 1 et 35 qui est généré de façon aléatoire.
On récupère dans les variables largeur & hauteur les dimensions de l'image chargée. A partir de ces infos on calcule les dimensions d'affichage de l'image et la position de son coin supérieur gauche afin qu'elle soit toujours centrée dans le canevas et jamais déformée.

Une image reste affichée deux secondes (2000 millisecondes) avant qu'elle ne soit effacée puis remplacée par une nouvelle.

La trotteuse

Dans cette application on superpose parfaitement deux 'toiles'. C'est le principe des 'calques'.

La difficulté est d'ordre CSS ; vous êtes obligé d'utiliser le positionnement absolu pour empiler les différents 'calques' et les centrer.

Ci-dessous une document HTML qui intègre deux canevas parfaitement surperposés

Cliquez dans le cadran pour lancer la trotteuse.

Le code CSS & HTML

Pour que les deux toiles se superposent parfaitement elles doivent être positionnées en absolu.
Le "calque" du dessous peut avoir une image ou couleur de fond. Celui du dessus est par défaut transparent.

La règle de style relative aux éléments CANVAS peut paraitre un peu compliquée ...
Notez que les deux canevas s'affichent avec une largeur de 60% dans leur conteneur. Pour qu'ils soient centrés dans leur élément parent, il faut donc règler la propriété left à 50% et la propriété margin-left à -30% (moitié de leur largeur d'affichage).
La propriété CSS position

Le script

function f1()
// pour dessiner dans le premier canevas
{
	var canvas = document.querySelector("#canvas1"); 	
	var ctx = canvas.getContext("2d");
	var X =canvas.width; 	
	var Y =canvas.height;
	ctx.translate(X/2,Y/2); 
	// l'origine du canevas devient son centre
	ctx.strokeStyle = "gold";
	ctx.lineWidth = 4;

	// marques heures
	for (var i=0;i<12;i++)
	{
		ctx.beginPath();
		// un trait tous les 30°
		ctx.rotate(Math.PI/180 * 30);
		ctx.moveTo(80,0);
		ctx.lineTo(100,0);
		ctx.stroke();
	}
	// marques minutes
	ctx.lineWidth = 2;
	for (i=0;i<60;i++)
	{
		ctx.beginPath();
		// un trait tous les 6°
		ctx.rotate(Math.PI/180 *6);
		ctx.moveTo(90,0);
		ctx.lineTo(100,0);
		ctx.stroke();
	}
} // fin f1
f1(); 

function f2()
// pour dessiner dans le deuxième canevas
{
	var canvas = document.querySelector("#canvas2"); 	
	var contexte = canvas.getContext("2d");
	var X = canvas.width; 	
	var Y = canvas.height;
	var stop;
	contexte.strokeStyle ="black"; 
	contexte.translate(X/2,Y/2); 
	// l'origine du canevas devient son centre

	canvas.onclick = function() {stop = setInterval(deplacer,1000);}
	// une fois par seconde
	canvas.onmouseout = function() {clearInterval(stop);}

	function deplacer()
	{
		contexte.rotate(Math.PI/180 *6);
		contexte.lineWidth =3;
		contexte.beginPath();
		contexte.moveTo(0,0);
		contexte.lineTo(0,-100);
		contexte.closePath();
		contexte.stroke();
		contexte.clearRect(-X/2,-Y/2,X/2,Y/2);
	}
} //fin f2
f2();

Deux fonctions : une pour remplir le canevas du dessous et une autre pour dessiner dans le "calque" du dessus.
Dans le "calque" du dessous je dessine le cadran horaire. Dans le canevas de dessus je dessine et anime la "trotteuse".

Pour dessiner des ronds et des aiguilles qui tournent autour d'un axe, il faut que l'origine du canevas soit son centre. D'ou l'instruction : translate(X/2,Y/2).
Mais attention il faut adapter le code de la commande clearRect() en conséquence pour tenir compte du changement d'origine : clearRect(-X/2,-Y/2,X/2,Y/2)

Une belle horloge

Le rendu

Ci-dessous un document HTML contenant une 'toile' qui affiche une horloge.

Deux fois par minute il y a actualisation de l'affichage.

Le code

Code HTML & CSS

Règles de style : les canevas seront superposés.

Notez dans la partie HEAD l'instruction : meta http-equiv="Refresh" content="30"
Ce qui veut dire que toutes les trente secondes la page est rechargée ; donc le script exécuté et la nouvelle heure affichée.

Le script

Je ne vous communique pas le code de la fonction qui remplit le premier canevas. Il suffit de vous inspirer de l'application "trotteuse" évoquée plus haut dans ce chapitre.

...
// pour remplir le deuxième canevas
function f2()
{
	let canvas = document.querySelector("#canevas2"); 	
	let ctx = canvas.getContext("2d");
	let X = canvas.width;
	let Y = canvas.height;
	ctx.translate(X/2,Y/2); 
	// l'origine du canevas devient son centre
	ctx.strokeStyle ="black";
	ctx.lineCap ="round";

	let maintenant = new Date();
	var secondes = maintenant.getSeconds();
	let minutes = maintenant.getMinutes();
	let heures  = maintenant.getHours();
	heures = heures >=12 ? heures-12 : heures;
	// passer de h 24 à h12
	ctx.rotate(-Math.PI/180*90); 
	ctx.save();

// aiguille des heures
	heure_fraction = (heures + minutes/60);
	ctx.rotate(Math.PI/180 * 30 * heure_fraction);
	ctx.lineWidth = 10;
	ctx.beginPath();
	ctx.moveTo(0,0);
	ctx.lineTo(80,0);
	ctx.stroke();
	
// aiguille des minutes
	ctx.restore();
	ctx.rotate(Math.PI/180 * 6 * minutes);
	ctx.lineWidth = 6;
	ctx.beginPath();
	ctx.moveTo(0,0);
	ctx.lineTo(100,0);
	ctx.stroke(); 

} // fin f2
f2() // appel de f2

Analyse du script

...New Date() : création d'un objet Date qui contient l'instant présent.
... maintenant.getHours() : récupération de l'heure sous forme d'un entier
ctx.translate(X/2,Y/2) : l'origine du canevas devient son centre.
ctx.rotate(-Math.PI/180*90) : l'axe X du canevas doit être orienté vers le haut avant toute application d'une nouvelle rotation.
heure = heure >=12 ? heure-12 : heure : heure 24 doit être convertie en heure 12.
heure_fraction = (heures + minutes/60) : si il 7 heures-10 l'aiguille des heures doit être plus près de 7 que de 6.
ctx.rotate(Math.PI/180 * 30 * heure_fraction) : une heure correspond à 30° (360 /12) sur un cadran horaire.
ctx.rotate(Math.PI/180 * 6 * minutes) : une minute correspond à 6° (360 /60) sur un cadran horaire.

Notez les commandes ctx.save() & ctx.restore(). En effet avant toute rotation pour afficher les minutes, Il faut restaurer le contexte initial (rotation de -90% pour que l'axe des X soit vertical).

Exercice

Ci-dessous document HTML avec une horloge digitale affichée dans une 'toile'

Le format d'affichage de l'heure est : hh:mm:ss (toujours deux chiffres pour l'heure, les minutes et les secondes).

Consignes

Vous devez être capable réaliser cette animation.

Attention on ne peut pas imaginer une recharge de la page chaque seconde, le serveur web serait trop sollicité.
Il doit y avoir rafraichissemnent sans rechargement de la page donc via un "timer" qui appelle une fonction d'actualisation chaque seconde.

Revoyez le chapitre "Insertion de textes et ombrages" pour revoir toutes les propriétés et méthodes qui gèrent le texte inséré dans un canevas : texAlign, textBaseline, font, ... fillText(), strokeText, ...

Pour afficher l'heure courante au format francophone utilisez la méthode toLocaleTimeString().
Pour afficher la date du jour au format francophone utilisez le constructeur Intl.DateTimeFormat.
Tous ces outils sont évoqués dans le chapitre "Objets natifs en JavaScript" du tuto sur ce langage.
Cliquez ici !

Extrait du script

Le script pour dessiner et animer l'horloge digitale est très succinct grâce à l'utilisation des outils précités.

let maintenant = new Date();
// heure au format francophone
let heure = maintenant.toLocaleTimeString();
...
// création d'un format de date 
let format_date = new Intl.DateTimeFormat("fr-FR",	{weekday : "long", 
	year : "numeric" , month : "long", day : "numeric"}); 
// format pour afficher la date en français
let cejour = format_date.format(maintenant); 
...

Il n'y a plus qu'afficher les variables "heure & cejour" dans le canevas.

Un jeu de squash

Ci-dessous un document HTML qui affiche le jeu de squash.

La balle et la raquette ne sont pas ombrées mais ça serait un jeu d'enfant.
Si vous trouvez que le jeu est trop facile ; comment le durcir ?

Le code dans ce document

Code HTML

L'élément canevas définit un repère de 400 par 400.

Le script

Il est orienté objet !

var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var X = canvas.width; 
var Y = canvas.height;  
var balle = 
{
		x: X/2,
		y: 20,
		vx: 3,
		vy: 4,
		rayon: 15,
		dessin: function () 
		{
			ctx.beginPath();
			ctx.arc(this.x, this.y, this.rayon, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.fillStyle = 'red'; 
			ctx.fill();
			// rajoutez un ombrage de la balle
		}, 
}; // fin définition objet balle
var raquette = 
{
		x: X/2,
		y: Y-15,
		vx: 15,
		w: 70,
		h:10,
		dessin: function () 
		{
			ctx.fillStyle = 'navy';
			ctx.fillRect(this.x,this.y, this.w, this.h);
			// rajoutez un ombrage de la raquette
		}, 
}; // fin définition objet raquette

balle.dessin();
raquette.dessin();
canvas.onclick = function()
{
	var stop = setInterval(boucle_jeu,50); 
	var compteur =0; 
	function boucle_jeu()
	{
		ctx.clearRect(0,0,X,Y); 
		compteur++; 
		balle.dessin();
		raquette.dessin();
        
		// changer coordonnées balle
		balle.x +=balle.vx;
		balle.y +=balle.vy;
      
		// gestion des rebonds
		if(balle.x > X-15 || balle.x <= 15)     {balle.vx = -balle.vx;}
		if (balle.y < 15) {balle.vy = -balle.vy;}
       
	   // Si balle atteint le bas 
	   if (balle.y > Y - 15)
		{
			if (balle.x > raquette.x  && balle.x < raquette.x + raquette.w)
				{balle.vy = -balle.vy;} // balle interceptée par raquette donc rebond
			else  // balle non interceptée par raquette
				{
					alert('perdu ; nombre de points : ' + Math.round(compteur/10)); 
					balle.x = X/2 ;balle.y = 20 ;raquette.x =X/2;
					ctx.clearRect(0,0,X,Y);
					balle.dessin(); raquette.dessin();
					clearInterval(stop); 
				}
		} // fin si externe
				
		// gestion clavier
		document.onkeydown = function(e)
		{
			if (e.keyCode == 37) {raquette.x -=raquette.vx; }
			if (e.keyCode == 39) {raquette.x += raquette.vx; }	
		} 
	} // fin boucle_jeu
} // fin fonction anonyme 

On définit deux objets : la balle et la raquette.
Puis on dessine ces deux objets dans la 'toile'.

Le jeu est déclenché par un clic dans le canevas.

La boucle de jeu est itérée tous les 50 millisecondes donc 20 fois par seconde : var stop = setInterval(boucle_jeu,50).
Donc pour 'durcir' le jeu il suffit de jouer sur ce paramètre.

Déplacement de la raquette : pour des explications concernant la programmation des touches du clavier, je vous renvoie au chapitre précédent.

la balle ne peut sortir du canevas :
si un bord haut/gauche/droit est atteint alors balle.vx / balle.vy change de signe :

if(balle.x > X-15 || balle.x <= 15)    {balle.vx = -balle.vx;}
if (balle.y < 15) {balle.vy = -balle.vy;}

Gestion de l'interception de la balle par la raquette :
si la raquette intercepte la balle alors alors balle.vy change de signe donc rebond de la balle.

if (balle.x > raquette.x  && balle.x < raquette.x + raquette.w)
	{balle.vy = -balle.vy;} 

Déplacer un objet avec la souris

Dans le cadre d'un jeu il peut être plus pratique de déplacer un objet avec la souris plutôt que via le clavier.
Il faut alors maitriser la méthode Javascript getBoundingClientRect() associée à un élément du DOM.
Les propriétés left & top appliquées à cette méthode retournent la position de l'élément du DOM dans la fenêtre.
Il faut aussi utiliser les propriété clientX & clientY qui appliquées à l'objet de type "event" retournent respectivement les positions X & Y du pointeur dans la fenêtre.

Ci-dessous un document HTML avec un canevas

Ce document affiche la position X & Y de la souris dans la fenêtre et dans le canevas.

Le code du document

Code HTML

...canvas id ="canvas" width ="800" height = "300" onmousemove="info(event)" ... </canvas>

Le script

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var X = canvas.width;
var Y = canvas.height;
ctx.font="18pt Arial";
ctx.fillStyle="purple";

function info(e) 
{
		ctx.clearRect(0,0,X,Y); // efface le cadre
		var element = canvas.getBoundingClientRect(); 
   	 
		// calcul de la position de la souris dans le caneva
		var XcurseurT = Math.round(e.clientX - element.left); 
		var YcurseurT = Math.round(e.clientY - element.top);
  
		var info1 = "Position du curseur dans la fenêtre :" + e.clientX + " ," + e.clientY;
		var info2 = "Position du curseur dans le canevas  :" + XcurseurT + " , " + YcurseurT;
		ctx.fillText(info1, 50, Y/5 *2);
		ctx.fillText(info2, 50, Y/5 *4);
} 

Nouveauté JS : var element = canvas.getBoundingClientRect() ; la variable element référence le canevas. On peut ensuite appliquer à l'objet element les propriétés left, top, right, bottom, widht, height.

Pour calculer la position X du pointeur dans le canevas il faut retirer de sa position X dans la fenêtre la position 'left' du canevas.
Pour calculer la position Y du pointeur dans le canevas il faut retirer de sa position Y dans la fenêtre la position 'top' du canevas.

Contrôler une balle avec la souris

À partir du script précédent il est facile de créer un programme qui déplace une forme du canevas via la souris.

Ci-dessous un canevas dans cette page web

Cliquez dans le canevas puis déplacez la souris et observez
Actualisez la page pour mettre fin à l'animation.

La balle est initialement au centre du canevas.
Tout déplacement de la souris (après le clic) s'accompagne du déplacement de la balle rouge.
Les coordonnées de la balle sont alors affichées sous le canevas dans une boite DIV.

Le code correspondant

L'élément canvas définit un repère de 600 par 600 avec l'attribut ID.
Sous la balise canvas un élément div.

Le script

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
var X = canvas.width;
var Y = canvas.height;
var info = document.getElementById("info");

const balle = {
  x: X/2,
  y: Y/2,
  vx: 5,
  vy: 1,
  radius: 25,
  color: "red",
  dessin() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};
balle.dessin();

canvas.addEventListener("click", function()
{
	var element = canvas.getBoundingClientRect();
	
	canvas.addEventListener("mousemove", function(e) 
	{
		ctx.clearRect(0,0,X,Y);
		var XcurseurT = Math.round(e.clientX - element.left); 
		var YcurseurT = Math.round(e.clientY - element.top);
		info.innerHTML ="Position X & Y de la souris dans le canevas : "
			+  XcurseurT + " " + YcurseurT ; 
		balle.x = XcurseurT;
		balle.y = YcurseurT; 
		balle.dessin();
	});
});

J'ai recouru à la POO : création d'un objet nommé "balle".
Notez que l'événement "mousemove" est déclenché uniquement si il y a eu un clic préable.

Canvas & jQuery

Rien n'interdit d'utiliser l'API Canvas avec le framework jQuery.

Connaitre la position de la souris ?

Exemple

Ci-dessous un document HTML contenant une 'toile'.

Déplacez la souris dans la fenêtre ;
Réduisez la fenêtre.

Le code de ce document

Le mot clé "this" désigne le canevas.
Pour déterminer la position du curseur dans le canevas le principe est le même qu'en JS natif. C'est simplement la syntaxe qui change.

Une application jQuery-Canvas : le jeu de morpion

Préambule

Je précise d'emblée que je ne suis pas l'auteur de cette animation complexe.
Je respecte les termes de la licence : "Vous êtes libre d’utiliser librement ce code. Seulement, si vous le diffusez, merci de citer vos sources et de conserver les commentaires des fichiers ... ".
Suivez le lien ci-dessous pour récupérer les fichiers.
vonkrafft.fr/tutoriels/un-morpion-en-javascript

Le jeu de morpion

Extrait du commentaire

La zone de jeu :
Pour dessiner la grille de jeux, j’ai implémenté un fonction drawGrid(). Elle dessine quatre segments grâce à la méthode lineTo(). Cette méthode est également utilisée pour tracer la croix du joueur 1. Pour le cercle du joueur 2, j’ai utilisé la méthode arc().

Déroulement d’une action :
Je récupère l’événement d’un clic de la souris grâce au code $("#canvas").click(function(e){}). Lorsque l’on clique sur la zone de jeu, le script calcule les coordonnées du centre de la case la plus proche. Ensuite, si la case est libre, on appelle la fonction drawSector(). En fonction du joueur qui est en train de jouer, cette fonction dessine une croix ou un cercle, permute le numéro du joueur en train de jouer et actualise le message d’information. Enfin, elle vérifie si une ligne est complète. Si c’est le cas, le script appelle la fonction theEnd(). Si un joueur a gagné, on incrémente son compteur de points. S’il y a match nul, seul le compteur de manche est incrémenté. Enfin, le message d’information est actualisé et deux boutons s’affichent : “Nouvelle manche” et “Nouvelle partie”.