Chute d'un objet dans un canvas en javascript
Pour animer un objet en javascript, on le dessine à un endroit puis on l'efface et le redessine juste à côté, et ainsi de suite…
On définit donc une fonction qui déplace l'objet d'une position à une autre, puis on l'appelle soit en boucle avec
setInterval
à intervalle de temps régulier, soit on définit cette fonction de manière récursive: elle s'appelle elle-même avec un délai grâce à setTimeou
.
Chute d'un objet simple
Le html et le javascript
On définit le canvas en html:<div style="text-align:center;">
<canvas id="canvas1" width="300" height="300" style="border:2px solid black;">
</canvas>
<br>
<input class="btnstyle" type="button" onclick="Chute1()" value="Chute !">
</div>
Le javascript pour dessiner et animer suit ci-dessous.
Le canvas est découpé en 8 lignes et 8 colonnes pour repérer la position d'un bloc. La fonction
MoveBloc
déplace simplement, sans animation, le boc d'une position à une autre.
La fonction MoveBlocDraw
va être appelée en boucle, avec setInterval
, pour donner l'illusion de l'animation. C'est la fonction CHute
qui se charge de cet appel.
<script>
const canvas1 = document.getElementById("canvas1");
const ctx1 = canvas1.getContext("2d");
Width1=document.getElementById("canvas1").width;
Height1=document.getElementById("canvas1").height;
const bz1=Width1/8;
// Dessin du bloc qui va chuter
function Bloc1(col,lgn) {
ctx1.fillStyle="lightblue";ctx1.strokeStyle = 'blue';
ctx1.strokeStyle = "rgb(255, 0, 0)";
ctx1.fillStyle = "rgb(249,92,94)";
ctx1.beginPath();
ctx1.roundRect((col-1)*bz1,Height1-lgn*bz1,bz1,bz1,10);
ctx1.stroke();ctx1.fill();
}
// Déplacement du bloc
function MoveBloc1(coli,lgni,colf,lgnf,N) {
ctx1.clearRect((coli-1)*bz1-3,Height1-lgni*bz1-3,bz1+6,bz1+6);
Bloc1(colf,lgnf);
}
function MoveBlocDraw1(col,initlgn,endlgn) {
if (ymv1>endlgn+.2) {
ymvold1=ymv1;
ymv1-=.2;
MoveBloc1(col,ymvold1,col,ymv1);}
else {MoveBloc1(col,ymv1,col,endlgn);clearInterval(sImv1);}
}
function Drop1(colMBA1,initlgnMBA1,endlgnMBA1) {
ymv1=initlgnMBA1;
sImv1=setInterval(function() {MoveBlocDraw1(colMBA1,initlgnMBA1,endlgnMBA1);}, 20);}
function Chute1() {
ctx1.clearRect(0, 0, Width1, Height1);
Bloc1(2,7);
Drop1(2,5.8,1);
setTimeout(function() {Drop1(5,10,1);},2000);
}
Chute1();
</script>
Chutes de plusieurs objets dans un canvas
Le javascript est asynchrone. Ceci est particulièrement important lorsqu'on manipule le temps et diffère des exécutions.En particulier, il est fondamental de bien comprendre que, lorsqu'on diffère l'exécution d'une fonction, avec
setTimeout
ou setInterval
, l'exécution se fait avec la valeur des variables qu'elle contient au moment de son exécution, et non pas la valeur des variables au moment de l'appel.
Pour s'en convaincre, on peut essayer de faire tomber un autre objet en même temps dans le script précédent, en modifiant par exemple la fonction finale d'appel:
…
function Chute1() {
ctx1.clearRect(0, 0, Width1, Height1);
Bloc1(2,7);
Drop1(2,5.8,1);Drop1(4,6,1);
setTimeout(function() {Drop1(5,10,1);},2000);
}
<script>
Les trajectoires (c'est-à-dire les valeurs des variables utilisées) s'entremêlent.
Il faut pour éviter cela définir des variables directement dans les fonctions concernées, des variables propres et qui vont s'incrémenter au moment de l'exéctuion de la fonction, à n'importe quel moment que ce soit.
Un exemple est donné ci-dessous, en définissant et utilisant cette fois des tableaux, dont chaque colonne contient la variable associée à l'objet qui chute dans la colonne correspondante du canvas.
Dans l'animation suivante, plusieurs objets peuvent ainsi tomber simultanément.
Cliquer sur le bouton chute pour relancer l'animation. Le script suit juste en-dessous.
Le javascript
On définit le canvas en html:<div style="text-align:center;">
<canvas id="canvas" width="300" height="300" style="border: 2px solid black;">
</canvas>
<br>
<input class="btnstyle" type="button" onclick="Chute()" value="Chute !">
</div>
et le javascript suivant.
Les déplacement de chaque objet sont cette fois manipulés indépendamment et identifiés par la colonne et ligne de départ.
Par exemple
Drop(3,7,2)
fait tomber un objet dans la 3ème colone, de la ligne 7 à la ligne 2 ; la hauteur de l'objet est ymv[3][7]
La référence à la fonction setInterval
utilisée, pour pouvoir la terminer ensuite, est sImv[3][7]
.
Chaque objet est identifié uniquement et les animations ne se perturbent pas.
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
Width=document.getElementById("canvas").width;
Height=document.getElementById("canvas").height;
const dy = 4;
const bz=Width/8;
// Dessin du bloc qui va chuter
function Bloc(col,lgn) {
ctx.fillStyle="lightblue";ctx.strokeStyle = 'blue';
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgb(249,92,94)";
ctx.beginPath();
ctx.roundRect((col-1)*bz,Height-lgn*bz,bz,bz,10);
ctx.stroke();ctx.fill();
}
// Déplacement du bloc
function MoveBloc(coli,lgni,colf,lgnf,N) {
ctx.clearRect((coli-1)*bz-3,Height-lgni*bz-3,bz+6,bz+6);
Bloc(colf,lgnf);
}
ymv=new Array;for (i=0;i<12;i++) {ymv[i]=[0,0,0,0,0,0,0,0,0,0,0]}
ymvold=new Array;for (i=0;i<12;i++) {ymvold[i]=[0,0,0,0,0,0,0,0,0,0,0]}
sImv=new Array;for (i=0;i<12;i++) {sImv[i]=[0,0,0,0,0,0,0,0,0,0,0]}
function MoveBlocDraw(col,initlgn,endlgn) {
if (ymv[col][initlgn]>endlgn+.2) {
ymvold[col][initlgn]=ymv[col][initlgn];
ymv[col][initlgn]-=.2;
MoveBloc(col,ymvold[col][initlgn],col,ymv[col][initlgn]);}
else {MoveBloc(col,ymv[col][initlgn],col,endlgn);clearInterval(sImv[col][initlgn]);}
}
function Drop(colMBA,initlgnMBA,endlgnMBA) {
ymv[colMBA][initlgnMBA]=initlgnMBA;
sImv[colMBA][initlgnMBA]=setInterval(function() {MoveBlocDraw(colMBA,initlgnMBA,endlgnMBA);}, 20);
}
function Chute() {
ctx.clearRect(0, 0, Width, Height);
Bloc(2,7);
Drop(2,5.8,1);
setTimeout(function() {Drop(4,10,1);},500);
setTimeout(function() {Drop(7,10,1);},1000);
setTimeout(function() {Drop(7,9,2);},1500);
setTimeout(function() {Drop(7,8,3);},2000);
}
Chute();
</script>
Un identifiant par objet et une fonction récursive avec setTimeout
Une autre méthode pour identifier et manipuler indépendammant les objets: à chaque déplacement, définir un nouvel identifiant.
Le canvas en html
<div style="text-align:center;">
<canvas id="canvas2" width="300" height="300" style="border:2px solid black;">
</canvas>
<br>
<input class="btnstyle" type="button" onclick="Chute2()" value="Chute !">
</div>
Le javascript pour animer
Ici, on numérote chaque déplacement par la variableDropN
qui est incrémentée à chaque appel de la fonction de déplacement. Les positions de l'objet correspondant sont stockées dans lgnint[DropN]
et lgnf[DropN]
et les trajectoires des objets n'interfèrent pas elles.
<<script>
const canvas2 = document.getElementById("canvas2");
const ctx2 = canvas2.getContext("2d");
Width2=document.getElementById("canvas2").width;
Height2=document.getElementById("canvas2").height;
const bz2=Width2/8;
lgnint=new Array;lgnf=new Array;
DropN=0;
function Bloc2(col,lgn) {
ctx2.fillStyle="lightblue";ctx2.strokeStyle = 'blue';
ctx2.strokeStyle = "black";ctx2.lineWidth=5;
ctx2.fillStyle = "rgb(249,92,94)";
ctx2.beginPath();
ctx2.roundRect(col*bz1,Height1-lgn*bz1,bz1,bz1,10);
ctx2.stroke();ctx2.fill();
}
function MoveB(col,DropN) {
if (lgnint[DropN]>lgnf[DropN]) {
ctx2.clearRect(col*bz2-4,Height2-lgnint[DropN]*bz2-4,bz2+8,bz2+2);
lgnint[DropN]-=.2;
Bloc2(col,lgnint[DropN]);
setTimeout(function() {MoveB(col,DropN);},100);
}
}
function DropB(col,init,end) {DropN++;
lgnint[DropN]=init;
lgnf[DropN]=end;
MoveB(col,DropN);}
function Chute2() {
ctx2.clearRect(0,0,Width2,Height2);
for (iDB=0;iDB<7;iDB++) {DropB(iDB,iDB+3,1);}
setTimeout(function() {DropB(5,7,2);},1500);
setTimeout(function() {DropB(5,7,3);},2500);
setTimeout(function() {DropB(5,7,4);},3500);
}
Chute2();
<</script>