frontend / html
Canvas 2D : bases
Explication
Ce que vous allez apprendre
- Récupérer un contexte de dessin avec
canvas.getContext("2d") - Dessiner des formes de base (rectangles, cercles, chemins) et comprendre l'état persistant du contexte
- Animer un canvas proprement avec
requestAnimationFrameplutôt quesetInterval - Corriger le flou d'un canvas sur les écrans à haute densité de pixels (Retina)
- Distinguer un canvas (pixels) d'un SVG (éléments manipulables du DOM)
Dans quel contexte ?
Un développeur intègre un mini-jeu de dessin sur atelier-creatif.html et anime le déplacement d'une balle avec setInterval(dessiner, 16). Sur certains ordinateurs, l'animation saccade visiblement, et elle continue même quand l'utilisateur change d'onglet, consommant du CPU pour rien. En remplaçant setInterval par requestAnimationFrame(dessiner), l'animation se synchronise avec le taux de rafraîchissement réel de l'écran et se met en pause automatiquement dès que l'onglet passe en arrière-plan.
Étape 1 : une exception dans tout ce que vous avez appris
Contrairement à tout ce que vous avez vu jusqu'ici en HTML — où l'on décrit des éléments structurés comme un paragraphe ou une image — le <canvas> est une toile totalement vide au départ. Rien n'existe tant que du JavaScript ne l'a pas explicitement dessiné.
Étape 2 : récupérer son pinceau, le contexte
Pour dessiner, il faut d'abord récupérer un "contexte" avec getContext("2d"), qui expose toutes les commandes de dessin : couleurs, formes, texte, images.
Étape 3 : un détail important sur ce contexte
Ce contexte fonctionne comme un état qui persiste entre les instructions. Définir ctx.fillStyle = "bleu" colore TOUTES les formes suivantes en bleu, jusqu'à ce qu'on le change à nouveau — un piège si on l'oublie.
Piège fréquent
Le contexte Canvas est un état partagé et persistant : oublier de réinitialiser ctx.fillStyle avant de dessiner une nouvelle forme lui fait hériter de la couleur de la forme précédente. Pensez le contexte comme un pinceau qu'on ne repose jamais tout seul entre deux dessins.
Étape 4 : au-delà des formes basiques, les chemins
Une fois les rectangles et cercles maîtrisés, presque toute forme complexe se construit avec un "chemin" : on pose des points, on trace des lignes entre eux, puis on referme et on remplit. Un peu comme dessiner au crayon sans lever la main.
Étape 5 : le piège des animations mal faites
Il reste un piège très fréquent chez les débutants : animer un canvas avec setInterval. Résultat : des animations saccadées, non synchronisées avec l'écran, qui continuent même quand l'onglet n'est pas visible.
Étape 6 : la bonne façon d'animer
Voici la solution : requestAnimationFrame. Le navigateur appelle la fonction juste avant chaque rafraîchissement d'écran, et la met en pause automatiquement si l'onglet passe en arrière-plan.
| Méthode d'animation | Synchronisée à l'écran ? | Pause en arrière-plan ? |
|---|---|---|
setInterval | Non | Non (continue de tourner) |
requestAnimationFrame | Oui | Oui, automatiquement |
Bonne pratique
Utilisez systématiquement requestAnimationFrame pour toute animation Canvas, et rappelez-la vous-même récursivement à la fin de chaque frame dessinée, plutôt que de fixer un intervalle de temps arbitraire.
Un dernier piège, la netteté sur écran Retina
Sans ajustement, un canvas paraît flou sur les écrans à haute densité de pixels. La solution consiste à dessiner à une résolution plus élevée que celle affichée, puis à la réduire visuellement.
Et ensuite ?
Une fois Canvas maîtrisé, la prochaine leçon montre son cousin très différent : le SVG, où chaque forme reste un vrai élément manipulable du DOM.
Commandes & code
Canvas 2D : bases
Dessiner des formes, images et animations pixel par pixel, directement dans le DOM.
<canvas id="scene" width="600" height="400"></canvas>const canvas = document.getElementById("scene");
const ctx = canvas.getContext("2d");
// Le contexte "2d" expose toute l'API de dessin ; "webgl" donnerait un contexte 3D différent
// --- Formes de base ---
ctx.fillStyle = "#2563eb";
ctx.fillRect(20, 20, 150, 100); // rectangle plein
ctx.strokeStyle = "#111";
ctx.lineWidth = 3;
ctx.strokeRect(200, 20, 150, 100); // rectangle contour seul
ctx.clearRect(220, 40, 40, 40); // efface une zone (transparence, pas blanc)
// --- Chemins (paths) : la brique de base pour toute forme complexe ---
ctx.beginPath();
ctx.moveTo(400, 20);
ctx.lineTo(500, 20);
ctx.lineTo(450, 120);
ctx.closePath();
ctx.fillStyle = "#22c55e";
ctx.fill();
// --- Cercle ---
ctx.beginPath();
ctx.arc(100, 250, 50, 0, Math.PI * 2); // centre x,y / rayon / angle début-fin en radians
ctx.fillStyle = "#f59e0b";
ctx.fill();// --- Texte ---
ctx.font = "bold 24px system-ui";
ctx.fillStyle = "#111";
ctx.textAlign = "center";
ctx.fillText("Technologik", 300, 350);
// --- Images ---
const image = new Image();
image.src = "/img/logo.png";
image.onload = () => {
// dessiner AVANT le onload produit une image vide : toujours attendre le chargement
ctx.drawImage(image, 450, 250, 100, 100);
};// --- État du contexte : save/restore pour isoler des transformations ---
ctx.save(); // empile l'état courant (couleurs, transformations, clip...)
ctx.translate(300, 200);
ctx.rotate(Math.PI / 4); // 45°, en radians toujours
ctx.fillStyle = "#ef4444";
ctx.fillRect(-25, -25, 50, 50); // dessiné autour de l'origine translatée/tournée
ctx.restore(); // revient exactement à l'état d'avant save()// --- Boucle d'animation propre : requestAnimationFrame, jamais setInterval ---
let x = 0;
function animer() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // repeindre tout à chaque frame
ctx.fillStyle = "#2563eb";
ctx.fillRect(x, 180, 40, 40);
x = (x + 2) % canvas.width;
requestAnimationFrame(animer);
// synchronisé avec le taux de rafraîchissement de l'écran,
// et automatiquement mis en pause si l'onglet n'est pas visible
}
requestAnimationFrame(animer);// --- Canvas haute densité (écrans Retina) ---
function redimensionnerHiDPI(canvas, ctx) {
const ratio = window.devicePixelRatio || 1;
const { width, height } = canvas.getBoundingClientRect();
canvas.width = width * ratio;
canvas.height = height * ratio;
ctx.scale(ratio, ratio); // remet les coordonnées de dessin en pixels CSS
}Résumé
beginPath()avant chaque nouvelle forme composée, sinon les tracés précédents se mélangent.save()/restore()isolent des transformations (translate,rotate,scale) sans affecter le reste.requestAnimationFrameremplacesetIntervalpour toute animation Canvas.- Sur écran Retina, dessiner en résolution native puis
ctx.scale()évite le flou.
Exercices pratiques
Mission : réparer l'animation saccadée d'atelier-creatif.html
Objectif : Corriger une animation de balle qui saccade et continue de tourner en arrière-plan, en migrant vers la bonne API d'animation.
Contexte
Sur atelier-creatif.html, un mini-jeu de dessin anime le déplacement d'une balle avec setInterval(dessiner, 16). Sur certains ordinateurs, l'animation saccade visiblement, et elle continue même quand l'utilisateur change d'onglet, consommant du CPU pour rien. Le code de dessiner() oublie aussi d'effacer le canvas avant de redessiner la balle à sa nouvelle position.
Ta mission : corriger le mécanisme d'animation.