Saltar al contenido

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.

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:

HTML
<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:

JS
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:

JS
// Redibujar a mayor resolucion justo antes de imprimiraddEventListener('beforeprint', () => dibujarA(4));   // 4 veces el tamano CSSaddEventListener('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:

CSS
@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:

JS
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:

JS
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ú.