Canvas
Oggetto Canvas
L’oggetto canvas (rappresentato in html col tag <canvas>) è uno strumento che permette di creare un’area all’interno della pagina web in cui è possibile disegnare programmaticamente linee, forme, figure, nella modalità 2D1.
I canvas possono essere usati nelle pagine html per questi scopi:
– arte visuale: ad esempio scritte artistiche, scritte a scorrimento, ecc. in particolare nel coding creativo[1]
– animazioni (in alternativa a SVG[2])
– grafici (es. a istogramma, barra, torta, ecc.)
– videogiochi
– è alla base della libreria WebGL per la grafica 3D
Vediamo come funziona.
Creazione ed utilizzo
L’area (rettangolare) in html la si inizializza con questo codice:
<canvas id="canvas" width="500" height="500"></canvas>(larghezza ed altezza sono modificabili anche via CSS oltre che JS).
A questo punto passiamo a Javascript. isogna eseguire prima di tutto queste due istruzioni:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");L’oggetto ctx (Context) sarà l’oggetto che ci permette di disegnare nel canvas.
Il canvas è internamente una griglia dove ogni punto ha coordinate (x,y) con la posizione (0,0) in alto a sinistra.

L’attività di disegno consiste quindi nel disegnare forme all’interno della griglia, dando le coordinate e le dimensioni.
Rettangoli
| Comando | Esempio | Note |
| ctx.fillRect(x, y, width, height); | ctx.fillRect(25, 25, 100, 100); | Crea un rettangolo (pieno) dalle coordinate x,y e dimensioni width ed height. |
| ctx.strokeRect(x, y, width, height); | ctx.strokeRect(45, 45, 60, 60); | Crea un rettangolo (vuoto) dalle coordinate x,y e dimensioni width ed height. |
| ctx.clearRect(x, y, width, height); | ctx.clearRect(50, 50, 50, 50); |
Se si eseguono le seguenti 3 istruzioni:
ctx.fillRect(25, 25, 100, 100);
ctx.strokeRect(45, 45, 60, 60);
ctx.clearRect(50, 50, 50, 50);Spiegazione:
– fill crea un rettangolo pieno
– stroke un rettangolo pieno
– clear cancella tutto quanto c’è nel rettangolo
Cosa si ottiene?

Notare che clearRect può essere usato per cancellare tutto il canvas.
Disegnare forme con path
E’ possibile disegnare forme complesse utilizzando il path.
Vediamo un esempio: se voglio disegnare una linea che va da 100, 100 a 300, 300 uso queste istruzioni:
ctx.beginPath();
ctx.moveTo(100,100);
ctx.lineTo(300,300);
ctx.moveTo(100,300);
ctx.lineTo(300, 300);
ctx.moveTo(100,100);
ctx.lineTo(100, 300);
ctx.lineWidth = 2;
ctx.stroke();Si dovrebbe ottenere un triangolo rettangolo.
Riassumendo:
– beginPath da inizio al disegno;
– moveTo sposta la “penna” su una coordinata;
– lineTo disegna una linea dalla coordinata attuale alla nuova coordinata;
– lineWidth da la dimensione della linea (istruzione non obbligatoria);
– stroke esegue la “commit”, ovvero rende il disegno effettivo.
Archi, cerchi e circonferenze
Vediamo ora come si disegna una circonferenza:
ctx.beginPath();
ctx.arc(250, 250, 200, 0, 2 * Math.PI);
ctx.lineWidth = 1;
ctx.stroke();Come si può vedere, arc riceve 5 parametri:
- le coordinate x ed y;
- la dimensione del raggio;
- gli ultimi due parametri sono l’angolo di inizio e di fine dell’arco: nel caso del cerchio sono 0 e 2π.
Se invece vogliamo un cerchio pieno usiamo fill.
ctx.beginPath();
ctx.arc(250, 250, 50, 0, 2 * Math.PI);
ctx.lineWidth = 1;
ctx.fill();Riassumendo:
– stroke indica un disegno solo del contorno;
– fill indica un disegno della forma piena.
Se vogliamo disegnare più forme diverse con strutture diverse è sufficiente che replichiamo la struttura:
ctx.beginPath();
...
ctx.stroke() o ctx.fill();I colori
Si può disegnare a colori con queste istruzioni:
ctx.fillStyle = "#ff0000";
ctx.strokeStyle = "#00cc22";
dove il codice altro non è che la terna RGB esadecimale.
Testo
E’ possibile inserire anche del testo:
ctx.font = "30px Arial";
ctx.fillText("Hello World", 10, 480);Disegno di istogrammi
Coi Canvas è possibile creare un diagramma ad istogrammi.
Come base dati usiamo un array di valori.
Per la sua rappresentazione creiamo le seguenti variabili:
- width, height: altezza e larghezza del diagramma (ad esempio le dimensioni del canvas)
- box_width: dimensione del box in cui verrà inserito l’istogramma
- isto_width: larghezza dell’istogramma (che deve essere inferiore a quella di box_width)
- unit_height: altezza unitaria dell’istogramma
- max: valore massimo presente nell’istogramma
- isto_height: altezza del singolo istogramma
Qui il codice:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const YELLOW = "#eeee00";
const BLACK = "#000000";
const GREEN = "#184a03";
const GRAY = "#b0b0b0";
const createDiagram = (list) => {
const width = canvas.width;
const height = canvas.height;
const box_width = width / list.length;
const isto_width = box_width * 0.9;
const max = Math.max(...list);
const unit_height = height / max;
list.forEach((value, index) => {
const isto_height = unit_height*value;
ctx.fillStyle= YELLOW;
ctx.fillRect(box_width*index, height-isto_height, isto_width, isto_height);
ctx.fillStyle = BLACK;
ctx.font = "20px Arial";
ctx.fillText(value, box_width*index+box_width/2, height-20);
})
}qui un esempio di utilizzo:
createDiagram([3,4,5,6,3,2,1]); Moto uniforme di un oggetto
In questa sezione useremo il canvas per svolgere una animazione in cui muoviamo un oggetto sul canvas, con moto uniforme.
Ma come si simula il movimento? Tramite una animazione.
L’algoritmo di animazione prevede i seguenti passaggi:
- calcolo della posizione degli oggetti che vogliamo animare. Il calcolo viene poi salvato in variabili che restano in memoria e che rappresentano lo stato logico della simulazione.
- rappresentazione degli oggetti usando la libreria Canvas.
Questo in un ciclo infinito.
Per mostrare questo comportamento simuleremo il movimento rettilineo di una pallina sul canvas, a velocità a costante, ovvero con moto rettilineo uniforme, e simulando urti elastici (cioè senza perdita di velocità) con le pareti.
Creiamo quindi un componente Ball, che rappresenta una pallina che vogliamo far muovere nel canvas.
const createBallComponent = (angle, speed, r, startx, starty, width, height) => {
let vx = speed * Math.cos(angle);
let vy = speed * Math.sin(angle);
let x = startx;
let y = starty;
let radius = r;
const calc = () => {
const dt = 1 / FPS;
let newx = x + vx * dt;
let newy = y + vy * dt;
if (newx > width - radius || newx < radius) {
vx = -vx;
}
if (newy > height - radius || newy < radius) {
vy = -vy;
} else {
x = newx;
y = newy;
}
}
return {
x: () => x,
y: () => y,
radius: () => r,
calc: calc
}
}- Per rappresentare le componenti orizzontale e verticale del movimento a partire dall’angolo, si procede mediante il calcolo trigonometrico di seno (per la componente verticale) e coseno (per quella orizzontale). Queste saranno alla base del calcolo della velocità verticale e di quella orizzontale:
- Queste due componenti servono per stabilire, ad ogni aggiornamento (funzione calc), la nuova posizione della pallina. Questa viene calcolata tenendo conto della posizione precedente e della velocità (orizzontale e verticale) moltiplicata per la durata del frame, secondo la legge oraria del moto:
- occorre verificare che la pallina non raggiunga la parete, altrimenti va invertita la direzione di velocità (vx o vy);
- infine viene aggiornata la posizione
La funzione calc() verrà richiamata nel ciclo principale, che vedremo sotto.
Prima però dobbiamo scrivere il componente che si occupa dell’aggiornamento del canvas:
const createDrawComponent = (canvas) => {
const ctx = canvas.getContext("2d");
return {
render: (ball) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(ball.x(), ball.y(), ball.radius(), 0, 2 * Math.PI);
ctx.lineWidth = 1;
ctx.fill();
},
}
}Infine possiamo vedere il ciclo principale di animazione:
const BALLSIZE = 5;
const SPEED = 100;
const FPS = 50;
((canvas) => {
const startX = canvas.width/2 ;
const startY = canvas.height/2 ;
const startAngle = Math.random() * 360 * (Math.PI / 180);
const ball = createBallComponent(startAngle, SPEED, BALLSIZE, startX, startY, canvas.width, canvas.height);
const drawComponent = createDrawComponent(canvas);
const refresh = () => {
ball.calc();
drawComponent.render(ball);
setTimeout(() => {
refresh();
}, 1000/FPS);
}
refresh();
})(document.getElementById("canvas"));- il ciclo refresh() prevede due azioni:
- ball.calc() -> calcola la nuova posizione della pallina
- drawComponent.render() -> aggiorna il canvas
- setTimeout con un tempo che dipende dalla velocità di refresh (1000 ms / FPS, frame per second)
In conclusione abbiamo separato la parte logica (il refresh della posizione) dalla sua rappresentazione (render). Questa è una buona prassi in qualsiasi progetto, e particolarmente utile nelle animazioni.
Esercizio: modificare il codice inserendo DUE palline e vedere cosa succede.
[1] Il “coding creativo” è l’uso della programmazione per produrre applicazioni visuali, che mostrano disegni ed animazioni: possono essere sfondi, salvaschermi, scritte in movimento, ecc. tutte prodotte da algoritmi generativi.
[2] il formato SVG permette di disegnare oggetti vettoriali tramite una descrizione testuale in XML
- ne esiste una versione 3d, che usa le librerie WebGL ↩︎
