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.
Ainsi dans des scènes complexes, seuls quelques formes changent tandis que d'autres restent inchangées.
L'optimisation consiste à utiliser plusieurs calques sous forme de canevas empilés.

Autres suggestions : il est préférable de programmer objet ; rien n'interdit de combiner l'API Canvas avec la librairie jQuery.

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

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/japonaise_nue.jpg" ; break;
			case 2 : image.src ="../images/black_nue.jpg" ; break;
			...
			case 35 : image.src ="../images/bikini28.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 comprend un entier compris entre 1 et 35 et 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

Vous pouvez superposer plusieurs canevas. C'est la principe des calques.

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

Constatez que les 'toiles' superposées le sont entierement et sont centrées horizontalement dans la page.

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 mais pas celui du dessus (qui a donc par défaut un fond transparent).

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 "calque" du dessus je dessine et anime la "trotteuse".

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

Une belle horloge

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

L'affichage de l'heure est actualisé automatiquement.

Le code

Code HTML & CSS

Deux canevas superposés comme pour l'application précédente.
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 la nouvelle heure est 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" ; c'est le même décor.

...
// 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.
Suivez le lien

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

Intérêt de ce jeu : la programmation du clavier.
Ci-dessous un document HTML qui affiche le jeu de squash.

Utilisez les flèches droite et gauche du clavier pour déplacer la raquette.

Le code dans ce document

Code HTML :

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();
		}, 
}; // 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);
		}, 
}; // fin définition objet raquette

balle.dessin();
raquette.dessin();
function jeu()
{
	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) || (e.keycode == 81)) {raquette.x -=raquette.vx; }
			if ((e.keyCode == 39) || (e.keycode == 83)) {raquette.x += raquette.vx; }	
		} 
	} // fin boucle_jeu
} // fin fonction jeu

On définit deux objets : la balle et la raquette.

Puis on dessine ces deux objets dans la 'toile'.

Le jeu est déclenché en cas de clic sur le bouton de commmande.
La boucle de jeu est itérée tous les 50 millisecondes donc 20 fois par seconde.
La raquette est déplacée latéralement par les touches de direction "gauche" et "droite".
La balle ne peut sortir du canevas : si un bord haut/gauche/droit est atteint alors balle.vx / balle.vy change de signe.
Si la balle atteint le bas et est interceptée par la raquette alors rebond sinon fin de partie.
En cas de fin de partie le score est affiché ; il est égal au nombre d'itérations de la boucle de jeu / par 10. Donc plus le score est élevé et plus vous avez tenu longtemps.

Notez bien le test de réception de la balle par la raquette :

if (balle.x > raquette.x  && balle.x < raquette.x + raquette.w)
				{balle.vy = -balle.vy;} // balle interceptée par raquette donc rebond

En clair si vous réceptionnez la balle avec les bords de la raquette vous avez quand même perdu ...
Que faire pour corriger ce défaut ?

Faites évoluer ce jeu

La programmation du clavier

L'un des intérêts de cette application est la programmation du clavier.

document.onkeydown = function(e)
{	// si appui sur une touche
	if ((e.keyCode == 37) || (e.keycode == 81)) gauche(); 	// déplacement à gauche raquette
	if ((e.keyCode == 39) || (e.keycode == 83)) droite(); 	
}

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.
Il faut maitriser la méthode Javascript getBoundingClientRect() qui s'applique à un élément du DOM tel un canevas.

Connaitre la position de la souris dans le canevas

La méthode HTMLelement.getBoundingClientRect() retourne les dimensions et la position de cet élément 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 de ce document

Les positions X & Y de la souris dans la fenêtre sont dépendantes de la largeur de celle-ci.

Pour calculer la positionX de la souris dans le canevas il faut retirer de sa positionX dans la fenêtre la position 'left' du canevas.
Pour calculer la positionY de la souris dans le canevas il faut retirer de sa positionY 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.

Exemple

Ci-dessous un canevas dans cette page web :

Cliquez dans le canevas puis déplacez la souris et observez

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.

Le code correspondant

Le canevas fait 600 par 600 et est identifié "canvas".

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.

Un jeu : star war

Ci-dessous un document HTML affichant un embryon de jeu.
Vous devez déplacer la fusée via la souris pour éviter une collision avec la planète.

Il s'agit plutôt d'une animation que d'un jeu.

Le code correspondant

Le repère cartésien du canevas fait 1200 par 600.

Le script (extrait)

Pour la construction des objets (fusee, planete) je ne vous communique pas le code. Revoyez le chapitre de ce tuto en lien.
Ces deux objets sont créés à partir de deux images matricielles : fusee.png & planete.png.
Insertion d'images dans un canevas

...
function jeu()
{
	var stop = setInterval(boucle_jeu,50);
	function boucle_jeu() 
	{
		ctx.clearRect(0,0,X,Y);
		fusee.dessin();
		planete.dessin();
		// déplacement planete
		planete.x += planete.vx;
		planete.y += planete.vy;
 
		// gestion des rebonds
		if(planete.x > X || planete.x < 0)     {planete.vx = -planete.vx;}
		if(planete.y >= Y || planete.y < 0)    {planete.vy = -planete.vy;}
  
		// déplacement fusee par la souris
		var toile = canevas.getBoundingClientRect();
		canevas.addEventListener("mousemove", function(e) 
		{
			var XcurseurT = Math.round(e.clientX - toile.left); 
			var YcurseurT = Math.round(e.clientY - toile.top);
			fusee.x = XcurseurT;
			fusee.y = YcurseurT; 
		}); // fin fonction anonyme
	} // fin boucle_jeu
	canevas.onclick =function() {clearInterval(stop); }
} // fin jeu
...

Améliorez cette animation !

Construisez une deuxième planète (plus grande et toujours basée sur l'image planete.png).
Ajouter la gestion des collisions de la fusée avec les planètes.

Un jeu de morpion en jQuery & Canvas

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

L'auteur combine dans le script la librairie Canvas et le framework jQuery.

Connaitre la position de la souris dans le canevas - solution jQuery

Dans le jeu "morpion" la connaissance de la position de la souris dans le canevas est indispensable !
Extrait du commentaire du script : "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(). ..."

Ci-dessous un document HTML avec un canevas

Un script indique la position de la souris dans le canevas ; script écrit en jQuery.

Le code de cette page

Le mot clé "this" désigne le canevas.