Accesibilidad, impresión y zoom
Los tres ejes donde el lienzo pierde de forma estructural: la semántica que el navegador no puede inventar, la resolución fija al imprimir, y las preferencias del sistema que un mapa de bits no obedece.
Estos tres ejes se deciden juntos porque tienen la misma causa: un SVG es contenido vivo que el navegador entiende y puede reinterpretar, y un lienzo es un rectángulo de píxeles opaco. Todo lo que el navegador hace por el usuario (leer en voz alta, buscar texto, traducir, forzar colores, imprimir a mayor resolución, ampliar) funciona sobre lo primero y se detiene ante lo segundo.
- Comparar lo que ofrece cada modelo en accesibilidad y qué hay que escribir a mano.
- Usar el contenido alternativo de un lienzo y el anillo de foco de su API.
- Prever el resultado de imprimir cada uno y montar la ruta de impresión correcta.
- Reaccionar a los cambios de ratio de píxeles y a las preferencias del sistema.
Accesibilidad
Un SVG en línea es un árbol de elementos, así que admite roles, nombres accesibles, descripciones, foco de teclado y estructura. Todo el patrón está en title, desc y aria-labelledby y en una gráfica de datos accesible, y no se repite aquí.
Un lienzo es un solo elemento con una imagen dentro que el navegador no puede interpretar. Lo que tiene:
Contenido alternativo dentro del elemento. Lo que escribas entre las etiquetas de apertura y cierre no se muestra visualmente, y sí forma parte del árbol de accesibilidad. Es el único canal semántico que hay:
<canvas id="grafico" width="800" height="400">
<table>
<caption>Ventas por trimestre en miles de euros</caption>
<tr><th scope="col">Trimestre</th><th scope="col">Ventas</th></tr>
<tr><td>T1</td><td>142</td></tr>
<tr><td>T2</td><td>168</td></tr>
</table>
</canvas>
Esa tabla es exactamente la alternativa canónica que se recomienda para cualquier gráfica de datos, y aquí no es una mejora: es lo único que hay. Si además pones elementos interactivos ahí dentro, entran en el orden de tabulación y puedes usar el puente que sí existe:
const boton = document.getElementById('serie-1');
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.fill();
ctx.drawFocusIfNeeded(boton); // dibuja el anillo de foco si el boton tiene el foco
drawFocusIfNeeded dibuja el indicador de foco del sistema alrededor de la ruta actual si el elemento que se le pasa está enfocado, y además informa al navegador de dónde está ese elemento en pantalla, lo que permite que un lector de pantalla mueva su cursor virtual al sitio correcto. Es una API real y soportada, y es prácticamente todo lo que existe.
Hubo una propuesta más ambiciosa, de regiones de acierto declaradas en el propio contexto, que llegó a implementarse detrás de banderas y se retiró. No la uses ni la busques.
La conclusión honesta: un lienzo puede ser accesible, y el trabajo lo haces tú entero, y lo que consigues es una alternativa paralela, no el gráfico. Un usuario de lector de pantalla no lee tu gráfico: lee la tabla que has escrito al lado. Con SVG, en cambio, se puede navegar la estructura real.
Impresión
Este es el eje donde la diferencia es más brutal y menos anticipada.
Un SVG se imprime en vectorial. El navegador rasteriza a la resolución de la impresora, que ronda los 300 o 600 puntos por pulgada. El gráfico sale nítido, el texto sale como texto, y si el PDF resultante se amplía, sigue nítido.
Un lienzo se imprime como el mapa de bits que es. Su búfer tiene, típicamente, ancho por ratio de píxeles del dispositivo, es decir, del orden de 200 puntos por pulgada equivalentes en el mejor caso. Impreso a 600, se ve pixelado. En un informe corporativo eso es un defecto visible que nadie perdona.
La mitigación existe y hay que montarla a propósito:
// Redibujar a mayor resolucion justo antes de imprimir
addEventListener('beforeprint', () => dibujarA(4)); // 4 veces el tamano CSS
addEventListener('afterprint', () => dibujarA(devicePixelRatio));
Funciona y tiene dos límites: la memoria (un lienzo a cuatro veces el tamaño consume dieciséis veces más, y hay topes de tamaño de búfer que se alcanzan antes de lo que parece), y que el texto sigue siendo píxeles, así que no se puede seleccionar ni buscar en el PDF resultante.
La alternativa buena, si la impresión importa de verdad, es tener una ruta de renderizado a SVG para imprimir y usar el lienzo solo en pantalla. Con la arquitectura de especificación separada del render que se establece en el patrón de componente de gráfico, eso es un segundo renderizador sobre la misma especificación, y se activa con una consulta de medios:
@media print {
.capa-lienzo { display: none; }
.capa-svg-impresion { display: block; }
}
Zoom, ratio de píxeles y preferencias
El zoom del navegador en un SVG es una transformación vectorial: al 300 por ciento se ve exactamente igual de nítido. En un lienzo, el zoom del navegador cambia el devicePixelRatio efectivo, y si no reaccionas, el gráfico se ve borroso hasta que algo lo redibuje.
Detectar el cambio requiere un truco, porque no hay evento de cambio de ratio. Se usa una consulta de medios de resolución que se vuelve a registrar en cada cambio:
function vigilarRatio(alCambiar) {
let mq;
function registrar() {
mq?.removeEventListener('change', manejar);
mq = matchMedia(`(resolution: ${devicePixelRatio}dppx)`);
mq.addEventListener('change', manejar, { once: true });
}
function manejar() { alCambiar(devicePixelRatio); registrar(); }
registrar();
return () => mq?.removeEventListener('change', manejar);
}
vigilarRatio(() => redimensionarYRedibujar());
La consulta describe el ratio actual, así que deja de cumplirse en cuanto cambia, y ahí salta el evento. Después hay que volver a registrar con el nuevo valor.
El modo de colores forzados, que es el que usan las personas que necesitan un contraste concreto y el que activa el alto contraste del sistema operativo, sustituye los colores del contenido por una paleta del usuario. Sobre SVG en línea funciona, porque los colores son propiedades CSS de elementos vivos. Sobre un lienzo no puede funcionar: el navegador ve una imagen y no la toca. El resultado es un gráfico con sus colores originales dentro de una página que ha cambiado por completo, y en el peor caso texto oscuro sobre un fondo que ahora es oscuro.
Se detecta y se maneja a mano:
const forzado = matchMedia('(forced-colors: active)');
forzado.addEventListener('change', redibujar);
function paleta() {
return forzado.matches
? { fondo: 'Canvas', tinta: 'CanvasText', acento: 'Highlight' }
: { fondo: '#1e1e2e', tinta: '#cdd6f4', acento: '#89b4fa' };
}
Los nombres Canvas, CanvasText y Highlight son colores del sistema definidos por CSS, y dentro de un lienzo funcionan como cualquier color con nombre. Es la única forma de que un gráfico en lienzo respete la elección del usuario.
Y lo mismo aplica, con la misma estructura, a prefers-color-scheme, a prefers-contrast y a prefers-reduced-motion: en SVG son reglas CSS, en lienzo son consultas de medios que tienes que escuchar y aplicar tú.
La comparación de accesibilidad entre los dos modelos suele reducirse a «los lectores de pantalla», y esa es solo una entrada de una lista mucho más larga. Todo lo siguiente funciona sobre SVG y no existe sobre un lienzo:
Buscar en la página. El atajo de búsqueda del navegador encuentra el texto de un text de SVG y lo resalta. En un lienzo no encuentra nada. En un panel con cincuenta etiquetas, eso es una funcionalidad que el usuario espera y que desaparece sin aviso.
Seleccionar y copiar texto. Copiar una etiqueta de un eje para pegarla en otro sitio es una acción normal. Sobre un lienzo, imposible.
Traducción automática. El traductor integrado del navegador traduce los nodos de texto del documento, incluidos los de un SVG en línea. El texto de un lienzo se queda en el idioma original en una página traducida entera, lo que produce el efecto desconcertante de un gráfico en español dentro de una página en alemán.
El escalado de fuente del sistema. Un usuario que ha subido el tamaño mínimo de letra ve el texto de la página más grande. El de un lienzo está dibujado en píxeles y no cambia.
La lupa del sistema operativo y el zoom de texto. El zoom de solo texto refluye la página y no toca el lienzo.
Las extensiones del usuario. Cualquier extensión que reescriba colores, aumente contrastes o anote la página opera sobre el DOM.
La inspección. Un compañero que abra las herramientas de desarrollo puede leer los valores de un gráfico SVG en el inspector. En un lienzo, no hay nada que inspeccionar, lo que también complica tu propia depuración.
Ninguna de estas ocho cosas aparece en un comparativa de rendimiento, y juntas son la razón por la que el enfoque híbrido gana tan a menudo: el texto casi nunca es la parte densa. Los ejes de un gráfico tienen veinte etiquetas, no veinte mil. Poner esas veinte en SVG cuesta veinte nodos, es decir, nada, y recupera de golpe las ocho funcionalidades de la lista.
La regla operativa que sale de aquí, y que es la que aplican las bibliotecas serias: si es texto, va en SVG; si son datos densos, van en lienzo. No es un compromiso, es la asignación óptima, y es el tema del último artículo de este nivel.
Coge un gráfico en lienzo que tengas y somételo a cuatro pruebas: imprímelo a PDF y amplía al 400 por ciento, activa el modo de alto contraste del sistema, busca en la página una de sus etiquetas, y sube el zoom del navegador al 250 por ciento sin recargar. Anota cuál de los cuatro te sorprende más. Después repite las cuatro con la versión SVG del mismo gráfico.