Etiqueta canvas en HTML: dibujo con JavaScript y accesibilidad
La etiqueta <canvas> proporciona un mapa de bits sobre el que JavaScript puede dibujar gráficos. Sirve, por ejemplo, para representar datos como barras, dibujar las escenas de un juego o generar una ilustración que cambia con los datos. Úsala cuando necesites producir el dibujo mediante código. El HTML crea el lienzo; las instrucciones de dibujo crean su imagen. Esta función se define en el HTML Living Standard.
Ejemplo mínimo: un lienzo y un rectángulo
Este HTML reserva un lienzo de 300 por 150 unidades e incluye una alternativa textual:
<canvas id="grafico" width="300" height="150">
Gráfico de ejemplo: un rectángulo azul.
</canvas>
La etiqueta sola no pinta el rectángulo. Para verlo, coloca este script inmediatamente después del elemento, dentro del cuerpo de un documento HTML:
<script>
(() => {
const lienzo = document.getElementById("grafico");
if (!(lienzo instanceof HTMLCanvasElement)) return;
const contexto = lienzo.getContext("2d");
if (!contexto) return;
contexto.fillStyle = "#155eab";
contexto.fillRect(20, 20, 120, 70);
})();
</script>
El resultado es un rectángulo azul de 120 por 70 unidades, situado a 20 unidades del borde izquierdo y del superior. El resto del bitmap sigue transparente. No necesitas imágenes ni un servidor para probarlo: guarda el documento como HTML y ábrelo en el navegador.
La función se ejecuta una vez y mantiene sus variables dentro de su propio ámbito. Si retiras el script, desaparece el dibujo. Si retiras el elemento, la comprobación inicial evita intentar dibujar sobre un objeto inexistente. Usa otro ID si copias el ejemplo varias veces en una página.
Sintaxis y contenido permitido
<canvas> necesita etiqueta de apertura y de cierre: <canvas>…</canvas>. No es un elemento vacío y escribir <canvas /> no lo cierra en HTML. Puede aparecer donde se admite contenido incrustado, por ejemplo dentro de un <div> o una figura.
Su contenido es transparente: la alternativa interior debe ser válida en el contexto que rodea al lienzo. En un párrafo, por ejemplo, no puedes introducir una tabla dentro del canvas. También hay restricciones para los descendientes interactivos; el estándar permite excepciones concretas como enlaces y botones, pero no cualquier control. Consulta el modelo de contenido de canvas si diseñas una alternativa interactiva.
El texto entre las etiquetas comunica el propósito del dibujo. No es texto que JavaScript pinte en la imagen ni una explicación visible permanente junto a ella. Con scripts habilitados, la ausencia de dibujo o un contexto no disponible no hace aparecer automáticamente esa alternativa. Por eso el ejemplo práctico conserva los datos fuera del canvas.
Atributos HTML, CSS y API de JavaScript
| Recurso | Qué controla |
|---|---|
Atributo width | Ancho interno del bitmap. Valor predeterminado: 300. |
Atributo height | Alto interno del bitmap. Valor predeterminado: 150. |
Atributos globales id, class o hidden | Identificación, selección para estilos y ocultación del elemento. |
Propiedades CSS width y height | Tamaño con el que se presenta el lienzo en la página. |
Método getContext("2d") | Solicita un contexto de dibujo bidimensional. |
Propiedad fillStyle y método fillRect() | Eligen el relleno y dibujan un rectángulo mediante JavaScript. |
Los atributos de dimensiones aceptan enteros no negativos, sin unidades: width="360", no width="360px". Sin ellos, el tamaño interno es 300 × 150. Una dimensión de cero no deja una superficie útil para este ejemplo. Son reglas del estándar de canvas.
getContext() devuelve un objeto de dibujo o null si no se puede obtener ese contexto. Compruébalo antes de llamar a sus métodos. Pedir un tipo distinto después de vincular otro contexto al mismo canvas también puede devolver null; obtener un contexto 2D no garantiza disponibilidad de WebGL o WebGPU. Véase MDN sobre getContext.
fillRect(x, y, ancho, alto) dibuja con el relleno actual. En el ejemplo, x y y sitúan la esquina superior izquierda; los otros dos números dan el tamaño. Son parámetros de una función, no atributos de la etiqueta. MDN explica fillRect.
Ejemplo práctico: tres barras con una tabla equivalente
Imagina que quieres comparar las ventas de tres materiales. La tabla será la fuente de los datos y permanecerá disponible; el gráfico añadirá una comparación visual. Son cantidades ficticias para aprender, sin envío ni almacenamiento.
Guarda todo este código en barras.html y abre el archivo. Incluye HTML, estilos y JavaScript; no requiere recursos externos. Los IDs y selectores de la demostración usan el prefijo canvas-ventas para evitar mezclarse con otros ejemplos.
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ventas de materiales: tabla y gráfico</title>
<style>
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
#canvas-ventas-demo canvas {
max-width: 100%;
height: auto;
}
#canvas-ventas-demo table {
border-collapse: collapse;
}
#canvas-ventas-demo th,
#canvas-ventas-demo td {
padding: 0.5rem;
border: 1px solid #64748b;
text-align: left;
}
</style>
</head>
<body>
<h1>Ventas de materiales</h1>
<section id="canvas-ventas-demo" aria-labelledby="canvas-ventas-titulo">
<h2 id="canvas-ventas-titulo">Comparación de unidades vendidas</h2>
<p class="canvas-ventas-estado">
Consulta los datos en la tabla. El gráfico necesita JavaScript
y un contexto de dibujo disponible.
</p>
<canvas id="canvas-ventas" width="360" height="210"
hidden aria-hidden="true">
Ventas: 12 cuadernos, 18 lápices y 9 carpetas.
</canvas>
<table id="canvas-ventas-datos">
<caption>Unidades vendidas por material</caption>
<thead>
<tr>
<th scope="col">Material</th>
<th scope="col">Unidades</th>
</tr>
</thead>
<tbody>
<tr><th scope="row">Cuadernos</th><td>12</td></tr>
<tr><th scope="row">Lápices</th><td>18</td></tr>
<tr><th scope="row">Carpetas</th><td>9</td></tr>
</tbody>
</table>
</section>
<script>
(() => {
const ejemplo = document.getElementById("canvas-ventas-demo");
if (!ejemplo) return;
const lienzo = ejemplo.querySelector("canvas");
if (!(lienzo instanceof HTMLCanvasElement)) return;
const contexto = lienzo.getContext("2d");
if (!contexto) return;
const filas = ejemplo.querySelectorAll("tbody tr");
const datos = Array.from(filas, (fila) => ({
nombre: fila.cells[0].textContent.trim(),
valor: Number(fila.cells[1].textContent)
}));
const maximo = Math.max(...datos.map((dato) => dato.valor));
const masVendidos = datos
.filter((dato) => dato.valor === maximo)
.map((dato) => dato.nombre)
.join(", ");
function dibujar() {
contexto.fillStyle = "#ffffff";
contexto.fillRect(0, 0, lienzo.width, lienzo.height);
contexto.font = "14px system-ui";
const anchoDisponible = lienzo.width - 135;
const espacioFila = (lienzo.height - 30) / datos.length;
datos.forEach((dato, indice) => {
const y = 30 + indice * espacioFila;
const ancho = Math.round(dato.valor / maximo * anchoDisponible);
contexto.fillStyle = "#155eab";
contexto.fillRect(90, y, ancho, 28);
contexto.fillStyle = "#172033";
contexto.fillText(dato.nombre, 10, y + 20);
contexto.fillText(String(dato.valor), 95 + ancho, y + 20);
});
}
dibujar();
lienzo.hidden = false;
ejemplo.querySelector(".canvas-ventas-estado").textContent =
`Mayor cantidad vendida: ${maximo} unidades (${masVendidos}). ` +
"La tabla contiene todos los datos.";
})();
</script>
</body>
</html>
Se ven tres barras horizontales: lápices es la más larga; cuadernos mide aproximadamente dos tercios de esa barra y carpetas, la mitad. Todas usan la misma escala y muestran nombre y cantidad. No necesitas distinguir colores para interpretar la comparación.
El script lee la tabla, calcula el máximo y convierte cada cantidad en un ancho proporcional. fillText() añade las etiquetas al bitmap. El resumen usa esos mismos datos e incluye todos los materiales que compartan la mayor cantidad. aria-hidden="true" excluye esta representación visual repetida del árbol de accesibilidad: los encabezados, el resumen y la tabla exterior transmiten su información.
Para modificar los datos, edita las celdas de la tabla y actualiza el resumen interior del canvas; después recarga. Este código está preparado para tres cantidades positivas. Si añades ceros, negativos u otras filas, adapta la escala y el espacio antes de reutilizarlo.
Pruebas que puedes repetir
Para comprobar que el resumen coincide con las barras, cambia la cantidad de cuadernos de 12 a 30 y recarga. Cuadernos tendrá la barra más larga y el mensaje indicará 30 unidades. Si cambias después lápices a 30, el resumen nombrará ambos materiales.
- Deshabilita JavaScript y recarga: el canvas sigue oculto, pero puedes leer las tres cantidades en la tabla y el mensaje inicial.
- Para simular un contexto no disponible, cambia
getContext("2d")porgetContext("contexto-inexistente")y recarga. El método devuelvenull; el script termina sin mostrar un lienzo vacío. Restaura después"2d". - Cambia el atributo
width="360"porwidth="420"y recarga. Se recalculan las barras para el nuevo ancho. Estrecha la ventana:max-width: 100%yheight: autoreducen la presentación conservando sus proporciones. - Con el dibujo visible, ejecuta
document.getElementById("canvas-ventas").width = 420en la consola. El bitmap se borra. Recarga para que el script vuelva a dibujar. - Retira el canvas del HTML y recarga. La tabla y su información siguen disponibles; el script termina al no encontrar el lienzo.
El ejemplo no tiene controles interactivos ni necesita hacer enfocable el canvas. Si lo amplías con acciones, coloca botones HTML visibles y utilizables por teclado fuera del lienzo.
Dimensiones internas, CSS y nitidez
El tamaño interno y el mostrado pueden ser distintos. Un bitmap de 300 × 150 presentado a 600 × 300 conserva su proporción, pero amplía sus píxeles y puede verse borroso. Presentarlo a 300 × 300 cambia la proporción y deforma el dibujo. MDN describe la diferencia entre CSS y dimensiones del canvas.
Cambiar lienzo.width o lienzo.height reinicia el contexto: borra el dibujo y restablece propiedades como el relleno y la fuente. Ocurre incluso al asignar el mismo valor que ya tenía. Define las dimensiones antes de dibujar; si las cambias después, vuelve a establecer el estado necesario y ejecuta tus instrucciones de dibujo. Esta es la causa del borrado en la cuarta prueba. Véase MDN sobre la propiedad width.
Como ampliación opcional, las pantallas de alta densidad pueden necesitar más píxeles internos que píxeles CSS. Una estrategia es multiplicar las dimensiones internas por window.devicePixelRatio, mantener el tamaño CSS deseado y escalar el contexto para seguir dibujando en las coordenadas originales. Hay que repetir la configuración y el dibujo cuando cambie la escala o el tamaño. MDN muestra esta técnica con devicePixelRatio; no hace falta incorporarla para entender estos ejemplos.
Accesibilidad y elección de alternativas
El bitmap no conserva las relaciones de los datos ni convierte un rectángulo dibujado en un elemento HTML. Una descripción como «gráfico de ventas» no basta para recuperar nombres, cifras y comparación. El caso práctico ofrece una tabla con encabezados y un resumen fuera del lienzo, disponibles también si falla el dibujo. MDN explica los límites de accesibilidad de canvas.
No dibujes el texto principal, encabezados o botones de la página para sustituir su HTML. Mantener esos elementos permite seleccionar texto, ampliar su presentación y usar controles nativos. Este ejemplo es estático; si añades animación, contempla prefers-reduced-motion y permite reducir o detener el movimiento.
Elige según lo que necesites representar:
<img>incorpora una imagen ya preparada, con su atributoalt; es suficiente para una ilustración estática.<table>expresa relaciones entre filas y columnas. Puede ser el contenido principal aunque añadas un gráfico.- SVG permite describir formas vectoriales como elementos; resulta útil para dibujos que deben escalar sin depender de un bitmap.
<figure>puede agrupar el gráfico y una leyenda; no ejecuta instrucciones de dibujo.<script>incorpora o carga JavaScript. El canvas es la superficie sobre la que ese código trabaja.
Preguntas y errores frecuentes
¿Canvas dibuja sin JavaScript?
La etiqueta por sí sola no ejecuta instrucciones de dibujo. En estos ejemplos, el rectángulo y las barras necesitan el script. Conserva una alternativa útil: no pongas todos los datos únicamente en píxeles ni supongas que el texto interior se verá siempre que el script falle.
¿Por qué no aparece el gráfico?
Comprueba que el ID coincida, que el script se ejecute después de crear el elemento y que getContext("2d") no devuelva null. Revisa la consola por si hay errores. Después confirma que las dimensiones sean positivas, que las coordenadas estén dentro del lienzo y que no hayas cambiado width o height después de dibujar.
¿Por qué se ve borroso o deformado?
Compara los atributos internos con el ancho y alto CSS. Mantén la misma proporción para evitar deformaciones; si amplías un bitmap pequeño, revisa su resolución y la densidad de píxeles. Cambiar solo CSS no añade detalle al dibujo original.
Compatibilidad y fuentes consultadas
Consulta realizada el 3 de octubre de 2026. MDN clasifica el elemento <canvas>, el contexto 2D mediante getContext() y fillRect() como ampliamente disponibles. Eso no extiende automáticamente su soporte a cada contexto u opción de la API. Las comprobaciones de esta guía usan dibujo 2D básico y conservan los datos cuando no se obtiene el contexto.
Las referencias de compatibilidad y comportamiento son MDN: canvas, MDN: getContext y MDN: fillRect. El significado, contenido permitido y atributos se contrastaron con el HTML Living Standard.