El sistema de coordenadas de usuario y las unidades
Saber qué es exactamente un número sin unidad en SVG, qué unidades admiten los atributos, y contra qué se resuelve un porcentaje, que no es lo que parece.
En SVG casi todos los números van sin unidad, y esa ausencia es una declaración: son unidades de usuario, el sistema de coordenadas que el viewBox define y la escala transforma. Poner unidades es posible y casi siempre una mala idea, salvo en un sitio concreto. Y los porcentajes, que parecen la parte fácil, se resuelven contra el viewport con una regla para las longitudes diagonales que no está en ningún tutorial y que explica resultados aparentemente absurdos.
- Definir la unidad de usuario y su relación con el píxel de CSS.
- Saber qué unidades admite un atributo y cuáles conviene evitar.
- Resolver mentalmente un porcentaje, incluido el caso de la diagonal normalizada.
- Aplicar la regla práctica de dónde poner las unidades en un proyecto.
La unidad de usuario
<circle cx="50" cy="50" r="40"/>
Esos tres números no son píxeles: son unidades de usuario. Sin ninguna transformación, una unidad de usuario equivale a un píxel de CSS, que a su vez es una unidad abstracta que el navegador convierte en píxeles físicos según la densidad de la pantalla. Con un viewBox que escale, la equivalencia cambia por el factor correspondiente, y con transformaciones anidadas cambia otra vez.
De ahí sale la propiedad más útil del sistema: el dibujo se describe en las unidades que le convengan y el tamaño en pantalla se decide en otro sitio. Un mapa puede usar metros como unidad de usuario, un gráfico puede usar euros y días, y un icono puede usar una rejilla de 24. Ninguno tiene que saber a cuántos píxeles se mostrará.
La cadena completa que convierte un número del fichero en un píxel de la pantalla tiene cuatro eslabones y conviene tenerla presente porque cualquiera de ellos puede ser el que te sorprenda:
El número del atributo está en el sistema de coordenadas de su elemento. Las transformaciones de los grupos que lo contienen lo mueven y lo escalan. La transformación del viewBox lo lleva al viewport. La densidad de pantalla lo convierte en píxeles físicos.
Unidades en los atributos
Un atributo de longitud admite un número con una unidad detrás, y las unidades son las de CSS.
| Unidad | Equivalencia | Cuándo tiene sentido |
|---|---|---|
| sin unidad | Una unidad de usuario | Casi siempre: es lo correcto por defecto |
px |
Igual que sin unidad | Nunca aporta nada frente a omitirla |
pt pc cm mm in |
Ancladas a 96 píxeles por pulgada | Documentos pensados para imprimir |
em rem ex ch |
Relativas a la tipografía | Elementos que deben crecer con el texto |
% |
Relativa al viewport | Con cuidado; ver la sección siguiente |
Dos advertencias que ahorran depuraciones.
Las unidades físicas mienten en pantalla. 1cm no mide un centímetro en tu monitor: mide 96 / 2.54 píxeles de CSS, es decir, unos 37,8. El navegador no sabe el tamaño físico de la pantalla ni la distancia a la que estás. Solo al imprimir se convierten en medidas reales, y por eso son la unidad adecuada para un SVG que va a papel y una fuente de confusión en cualquier otro caso.
Poner unidades dentro del dibujo rompe la abstracción del viewBox. Si mezclas r="40" con stroke-width="2px" no ganas nada, y si mezclas r="40" con stroke-width="2mm" obtienes un grosor que ya no escala igual que el resto y que se comporta de forma distinta al cambiar el tamaño. Dentro del dibujo, números sin unidad; las unidades van en el tamaño del elemento raíz, que es donde el SVG se encuentra con la página.
SVG 2 define además varias de esas geometrías —x, y, r, cx, cy, width, height— también como propiedades de CSS, lo que permitiría animarlas con transiciones. El soporte no es uniforme entre motores, así que como atributos siguen siendo la vía segura, y para animar geometría lo portátil es transformar en lugar de cambiar la geometría.
Los porcentajes y la diagonal normalizada
Un porcentaje en SVG no se resuelve contra el elemento padre, como en CSS, sino contra el viewport actual. Y hay tres reglas según el eje al que pertenezca la longitud:
Longitudes horizontales —x, cx, width, rx— se resuelven contra el ancho del viewport.
Longitudes verticales —y, cy, height, ry— contra el alto del viewport.
Longitudes que no son de ningún eje —r de un círculo, stroke-width, stroke-dashoffset, el radio de un gradiente radial— se resuelven contra un valor que la especificación llama diagonal normalizada:
const diagonalNormalizada = Math.sqrt(ancho * ancho + alto * alto) / Math.SQRT2;
Con un viewport de 800 por 600, la diagonal es 1000 y la diagonal normalizada es 1000 / 1,4142, es decir, unas 707 unidades. Por tanto r="50%" en ese viewport da un radio de 353,5 unidades, no de 400 ni de 300. En un viewport cuadrado la fórmula devuelve exactamente el lado, que es el caso en el que la regla pasa desapercibida; en cuanto el viewport deja de ser cuadrado, aparece un número que nadie espera.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" width="400" height="300">
<rect width="100%" height="100%" fill="#313244"/>
<circle cx="50%" cy="50%" r="50%" fill="#89b4fa" opacity="0.5"/>
<!-- El circulo NO toca los bordes verticales: su radio es 353,5, no 300 -->
</svg>
La conclusión práctica es breve: los porcentajes están bien para width y height del elemento raíz y para rectángulos de fondo, y son una mala idea para radios y grosores. Para todo lo que sea geometría del dibujo, números en unidades de usuario, que ya escalan solos.
La regla del viewport tiene una consecuencia que casi nunca se enuncia y que es la más útil de esta lección: un svg anidado establece un viewport nuevo, y a partir de ahí todos los porcentajes de su interior se resuelven contra él. Eso convierte al svg anidado en una herramienta de encapsulación que la mayoría de la gente no usa: dentro de él puedes escribir width="100%" y significará el ancho de ese hueco concreto, no el del documento entero. Es la manera limpia de construir componentes SVG reutilizables que se colocan en una posición y un tamaño arbitrarios sin recalcular ninguna coordenada interna, y es también la razón por la que symbol —que lleva su propio viewBox— funciona como funciona. Hay dos consecuencias derivadas que conviene conocer. La primera, de depuración: si un porcentaje da un número que no te cuadra, la primera pregunta no es cuánto mide el elemento padre, sino cuál es el viewport vigente, que puede ser el raíz o un anidado varias capas más arriba. Un g con un transform no crea viewport, así que no cambia nada; solo lo crean el svg raíz, un svg anidado, symbol cuando se instancia, y los elementos que definen contenido con su propio sistema como pattern o marker. La segunda, de rendimiento y corrección: los porcentajes obligan al navegador a resolver el valor contra un viewport que puede cambiar de tamaño, lo que significa que cualquier redimensionado de la ventana provoca un recálculo de esas longitudes y un repintado. Con cuatro elementos da igual; en un SVG generado con miles de nodos usando porcentajes, un simple cambio de tamaño de ventana se convierte en un trabajo enorme y visible. Un dibujo escrito en unidades de usuario y escalado por el viewBox no tiene ese problema: la transformación es una matriz y el contenido no se recalcula. Es un argumento más, y de los buenos, para que dentro del dibujo no haya ni un solo porcentaje.
La regla práctica
Toda la lección se resume en un reparto de responsabilidades que conviene adoptar sin excepciones porque hace que todo lo demás encaje.
Dentro del dibujo: números sin unidad. Todas las coordenadas, radios y grosores en unidades de usuario, con el viewBox decidiendo la escala.
En el elemento raíz: tamaño desde el CSS. Ni width ni height en el marcado si el SVG está en línea y debe adaptarse; el CSS decide, y con width: 100% y height: auto el SVG mantiene su proporción.
Nada de porcentajes en la geometría interna. Salvo el rectángulo de fondo, donde width="100%" height="100%" es idiomático y correcto.
Unidades físicas solo en documentos para imprimir, y entonces en el elemento raíz: width="210mm" height="297mm" con un viewBox que use las unidades cómodas para dibujar.
<svg xmlns="http://www.w3.org/2000/svg"
width="210mm" height="297mm" viewBox="0 0 210 297">
<!-- Una unidad de usuario es un milimetro: comodisimo para maquetar en papel -->
<rect x="20" y="20" width="170" height="40" fill="none" stroke="#000" stroke-width="0.5"/>
<text x="24" y="45" font-size="8" font-family="serif">Un titulo de 8 mm</text>
</svg>
Ese es el único caso en el que las unidades físicas son la respuesta correcta, y funciona muy bien: eliges el viewBox para que una unidad de usuario sea un milímetro, y a partir de ahí todo el dibujo se piensa en milímetros sin escribir la unidad ni una sola vez.