DEV Community

Cover image for Animazione canvas js: come fare rimbalzare delle palle per lo schermo
Roberto
Roberto

Posted on • Updated on

Animazione canvas js: come fare rimbalzare delle palle per lo schermo

Ho già affrontato le animazioni in questo articolo dove ne ho parlato in maniera basica.

Come esempio ho fatto muovere un cerchio da un lato all'altro del canvas. Un animazione molto semplice e bruttina, ma che era giusto per lo scopo.
Per questo in questo articolo impareremo qualcosa di più articolato (ma non troppo).

Nel primo esempio faremo muovere una palla in 2d in giro per lo schermo e rimbalzerà nella direzione opposta toccato lo schermo.

INIZIALIZIAMO

Inizializiamo un elemento grande 600x400

  • HTML
<canvas id="myCanvas"></canvas>
Enter fullscreen mode Exit fullscreen mode
  • JS
let canvas = document.getElementById('myCanvas');
canvas.width = 600;
canvas.height = 400;

let ctx = canvas.getContext('2d');
Enter fullscreen mode Exit fullscreen mode

FUNZIONE DI RENDERING

Creiamo un funzione ricorsiva che servirà per il rendering dell'animazione, quindi avrà il compito di ripetere il codice al suo interno ciclicamente.

Tutto questo grazie al metodo requestAnimationFrame() che chiameremo, una fuori dalla funzione per chimarla la prima volta e una all'interno per creare il loop.

let loop = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  ctx.beginPath();
  ctx.arc(canvas.width / 2, canvas.height  / 2, 30, 0, 2 * Math.PI);
  ctx.fillStyle = 'red';
  ctx.fill();
  ctx.lineWidth = 3;
  ctx.strokeStyle = '#000';
  ctx.stroke();

  requestAnimationFrame(loop)
}

requestAnimationFrame(loop)
Enter fullscreen mode Exit fullscreen mode

All'interno della funzione loop() abbiamo:

  1. la funzione che pulisce il canvas ad ogni ciclo
  2. i metodi che disegnano un cerchio rosso con il contorno nero al centro (canvas.width / 2, canvas.height / 2) del canvas
  3. il metodo che richiama la funzione loop. Grazie a quest'ultimo metodo la funzione verrà richiamata circa 60 volte al secondo (60 fps).

Come si può vedere per disegnare un solo cerchio ci vogliono tante righe di codice, pensate se dovessimo disegnare più elementi, sarebbe un casino e non è riutilizzabile.

CLASSE JS

Per questo creeremo una classe Circle che darà origine a quanti cerchi vogliamo e dove avremmo il totale controllo.

class Circle {
  constructor(){
    this.x = canvas.width / 2;
    this.y = canvas.height / 2;
    this.radius = 30;
    this.color = 'red';
  }

  draw(){
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.lineWidth = 3;
    ctx.strokeStyle = '#000';
    ctx.stroke();
  }
}


let loop = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  requestAnimationFrame(loop)
}

let ball = new Circle();
requestAnimationFrame(loop)
Enter fullscreen mode Exit fullscreen mode

Grazie alla classe Circle, con il comando new abbiamo salvato un'istanza della classe nella variabile ball .

Per ultimo abbiamo sostituito all'interno della funzione loop, il codice che disegnava un cerchio con il metodo draw() della variabile ball.

Il risultato finale non è cambiato ma il codice è più ordinato e riutilizzabile.

MUOVIAMO LA PALLA

Per far muovere la palla, aggiungiamo due proprietà nel costruttore della classe Circle che indicano di quanti pixel la nostra palla si dovrà spostare, una è per lo spostamento orizzontale e l'altra è per lo spostamento verticale.

class Circle {
  constructor(){
    this.x = canvas.width / 2;
    this.y = canvas.height  / 2;
    this.radius = 30;
    this.color = 'red';
    this.speedX = 3;
    this.speedY = 3;
  }
  ...
Enter fullscreen mode Exit fullscreen mode

Queste due proprietà dovranno sommarsi ad ogni ciclo del loop, this.speedX con this.x e this.speedX con this.y. Quindi creiamo un nuovo metodo nella classe che compirà questo compito e nella funzione loop richiamiamo questo metodo che ho chimato update().

class Circle {
  constructor(){
    ...
  }

  draw(){
    ...
  }

  update(){
    this.x += this.speedX;
    this.y += this.speedY;
  }


let loop = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.update();
  ball.draw();
  requestAnimationFrame(loop)
}

...
Enter fullscreen mode Exit fullscreen mode

La nostra funzione loop adesso cosa fa?

  • cancella tutto quello che c'era disegnato nello schermo
  • aggiunge 3px alle due coordinate della palla.
  • disegna la palla sulle nuove coordinate.
  • ricomincia il giro facendo ripartire la funzione stessa.

Tutto questo è talmente veloce che si vede solo il movimento della palla.

BORDI

Se provassimo il nostro script, la palla schizzerebbe fuori dai bordi per non tornare più.
Per questo dobbiamo recintare il nostro canvas in modo che appena la palla tocchi uno dei quattro bordi rimbalzi indietro.

Per fare questo dobbiamo controllare quando la circonferenza della palla tocca un bordo.
Dobbiamo tenere conto anche del raggio del cerchio oltre alle coordinate se no la nostra palla fuoriuscirà del canvas come nell'immagine sottostante.

Alt Text

Vediamo le 4 condizioni per la quale la palla dovrà rimbalzare:

  • Margine sinistro: Se la x meno il raggio (x - r) è minore di 0
  • Margine destro: Se la x più il raggio (x + r) è maggiore della lunghezza del canvas (canvas.width)
  • Margine superiore: Se la y meno il raggio (y - r) è minore di 0
  • Margine inferiore: Se la y più il raggio (y + r) è maggiore della altezza del canvas (canvas.height)

Alt Text

Traduciamo il tutto in un metodo della classe, edgeIsTouched(), richiamata poi nel update()

update(){
    this.edgeIsTouched();
    this.x += this.speedX;
    this.y += this.speedY;
  }

  edgeIsTouched(){
    if (this.x - this.radius < 0 || this.x + this.radius > canvas.width) {
      this.speedX = -this.speedX;
    } 
    if (this.y - this.radius < 0 || this.y + this.radius > canvas.height) {
      this.speedY = -this.speedY;
    }
Enter fullscreen mode Exit fullscreen mode

Nel primo if controlla se vengono superati i margini destra e sinistra, se così fosse, il valore dello speedX cambia di segno, se questo era positivo diventa negativo e viceversa, così la x andrà nel verso opposto a dove stava andando.

Uguale per quanto riguarda la y il controllo sui margine superiore e inferiore.

100 Palle per lo schermo

Modifichiamo il nostro programma per fare in modo che invece di una palla in giro per lo schermo ci siano 100 palle di dimensioni e colori diversi.

Randomiziamo la dimensione, il colore e la direzione.
Nel costruttore della classe, al posto dei valori del raggio, colore e delle due direzioni mettiamo la funzione Math.random() che passa un valore casuale.

class Circle {
  constructor(){
    this.x = canvas.width / 2;
    this.y = canvas.height  / 2;
    this.radius = Math.random() * (30 - 10) + 10; 
    this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
    this.speedX = Math.random() * 5 - 2.5;
    this.speedY = Math.random() * 5 - 2.5;
  }
Enter fullscreen mode Exit fullscreen mode

La funzione Math.random() ritorna un numero causale da 0 a 1, per questo dobbiamo dotarlo di un range massimo e minimo nel quale stare.

  • this.radius = Math.random() * (30 - 10) + 10; Il raggio della palla sarà compreso tra 10 e 30 px.
  • this.color = 'hsl(${Math.random() * 360}, 100%, 50%)'; Ho randomizzato la tonalità del colore con un range della scala dei colori che va da 0 a 360, mantendendo la saturazione e la luminosità fisse.
  • this.speedX = Math.random() * 5 - 2.5; Per le direzioni ho un range che va da -2.5 a +2.5, così che mi possa tornare sia un numero positivo ( destra e in basso ), sia un numero negativo ( sinistra e in alto).

Il prossimo passo è creare una funzione init() che crei, grazie ad un _for, 100 instanze della classe Circle e le salvi in un array e che faccia partire il ciclo con requestAnimationFrame .

Modifichiamo la funzione loop() in modo che vengano disegnate tutte e aggiornate tutte le 100 palle contenute nell'array grazie al metodo _forEach e il gioco è fatto.

let balls = []

class Ball {
  ...
}

let init = () => {
  for (i = 0; i < 10; i++) {
    balls.push( new Ball())
  } 
  requestAnimationFrame(loop)
}

let loop = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  balls.map( ball => {
    ball.draw();
    ball.update();
  })
  requestAnimationFrame(loop)
}

init()
Enter fullscreen mode Exit fullscreen mode

Conclusione

In questo tutorial abbiamo visto come animare una e più sfere che rimbalzano nel canvas, spero ti sia piacuto.

Se avete dei consigli, suggerimenti o critiche costruttive lasciatemi un commento qui sotto oppure contattatemi trammite i miei social.

Top comments (2)

Collapse
 
micheleminardi profile image
michele👨‍💻🤖

Bello leggere qualcosa in italiano ! 🤏 Complimenti per l'articolo

Collapse
 
camizzilla profile image
Roberto

Grazie mille. L'obiettivo sarà farli sia in italiano che in inglese. Tempo permettendo 😅.