La API Canvas 2D de JavaScript permite dibujar gráficos, imágenes, texto y formas en el navegador de forma inmediata (immediate mode): en lugar de mantener una lista de objetos como SVG, dibujas directamente sobre un bitmap pixel a pixel. Esto la hace idónea para animaciones complejas, juegos 2D, procesamiento de imágenes y cualquier cosa donde necesites control total sobre cada pixel renderizado.
Configurar el canvas y el contexto
El punto de entrada es el elemento <canvas> y su método getContext('2d'). Los tamaños del canvas en CSS y en atributos son independientes: los atributos definen la resolución real del bitmap, el CSS define el tamaño visual.
const canvas = document.getElementById('mi-canvas');
const ctx = canvas.getContext('2d');
// Para pantallas de alta densidad (Retina)
const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;
ctx.scale(dpr, dpr); // escalar el contexto para que el código use píxeles CSS
Formas básicas: rect, arc y paths
// Rectángulo relleno ctx.fillStyle = '#3498db'; ctx.fillRect(10, 10, 200, 100); // Rectángulo con borde ctx.strokeStyle = '#e74c3c'; ctx.lineWidth = 3; ctx.strokeRect(220, 10, 200, 100); // Círculo ctx.beginPath(); ctx.arc(150, 200, 50, 0, Math.PI * 2); ctx.fillStyle = '#2ecc71'; ctx.fill(); // Path personalizado (triángulo) ctx.beginPath(); ctx.moveTo(300, 250); ctx.lineTo(250, 150); ctx.lineTo(350, 150); ctx.closePath(); ctx.fillStyle = '#9b59b6'; ctx.fill();
save/restore: aislar transformaciones y estilos
ctx.save() y ctx.restore() guardan y restauran el estado completo del contexto (transformaciones, estilos, clip). Úsalos siempre que apliques transformaciones temporales para no contaminar el estado global:
function dibujarRotado(ctx, x, y, ancho, alto, angulo) {
ctx.save();
ctx.translate(x + ancho / 2, y + alto / 2); // mover al centro del rectángulo
ctx.rotate(angulo);
ctx.fillStyle = '#e67e22';
ctx.fillRect(-ancho / 2, -alto / 2, ancho, alto);
ctx.restore(); // volver al estado anterior
}
dibujarRotado(ctx, 100, 100, 120, 60, Math.PI / 6); // 30 grados
dibujarRotado(ctx, 300, 100, 80, 80, Math.PI / 4); // 45 grados
Game loop con requestAnimationFrame
requestAnimationFrame ejecuta la función de animación justo antes del siguiente repintado del navegador, sincronizando el juego o animación con la frecuencia de refresco de la pantalla (60Hz, 120Hz, etc.):
class Pelota {
constructor(x, y, radio, vx, vy) {
this.x = x; this.y = y;
this.radio = radio;
this.vx = vx; this.vy = vy;
}
actualizar(ancho, alto) {
this.x += this.vx;
this.y += this.vy;
if (this.x - this.radio < 0 || this.x + this.radio > ancho) this.vx *= -1;
if (this.y - this.radio < 0 || this.y + this.radio > alto) this.vy *= -1;
}
dibujar(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radio, 0, Math.PI * 2);
ctx.fillStyle = '#e74c3c';
ctx.fill();
}
}
const pelota = new Pelota(200, 200, 20, 3, 2);
function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // limpiar frame anterior
pelota.actualizar(canvas.clientWidth, canvas.clientHeight);
pelota.dibujar(ctx);
requestAnimationFrame(loop); // solicitar el siguiente frame
}
requestAnimationFrame(loop);
Sprite animation con drawImage
ctx.drawImage permite dibujar una imagen o un recorte de ella (sprite sheet), fundamental para animaciones 2D con fotogramas:
const spriteSheet = new Image();
spriteSheet.src = '/assets/personaje.png';
const FRAME_WIDTH = 64;
const FRAME_HEIGHT = 64;
const TOTAL_FRAMES = 8;
let frameActual = 0;
let lastTime = 0;
const FPS_ANIMACION = 12;
spriteSheet.onload = () => requestAnimationFrame(animarSprite);
function animarSprite(tiempo) {
if (tiempo - lastTime > 1000 / FPS_ANIMACION) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)
ctx.drawImage(
spriteSheet,
frameActual * FRAME_WIDTH, 0, // recorte en el sprite sheet
FRAME_WIDTH, FRAME_HEIGHT,
100, 100, // posición en el canvas
FRAME_WIDTH * 2, FRAME_HEIGHT * 2 // tamaño escalado x2
);
frameActual = (frameActual + 1) % TOTAL_FRAMES;
lastTime = tiempo;
}
requestAnimationFrame(animarSprite);
}
OffscreenCanvas con Web Workers
OffscreenCanvas permite renderizar en un Worker, liberando el hilo principal de trabajo de pintura costoso:
// main.js
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('./render-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
// render-worker.js
self.onmessage = ({ data: { canvas } }) => {
const ctx = canvas.getContext('2d');
let frame = 0;
function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = `hsl(${frame % 360}, 70%, 50%)`;
ctx.fillRect(50, 50, 200, 200);
frame++;
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
};
La API Canvas 2D tiene más de 30 métodos y propiedades de estado. Los que más se usan son los que cubren este artículo: las formas básicas, las transformaciones con save/restore, el game loop con requestAnimationFrame y la manipulación de imágenes con drawImage. Para escenas 3D, la API WebGL (y WebGPU en navegadores modernos) ofrece acceso a la GPU directamente desde JavaScript.
