Saltar al contenido

SVG es XML en el DOM, no un formato de imagen

Situar SVG como lo que es: un lenguaje de marcado con sus reglas de XML, su espacio de nombres y sus elementos vivos en el árbol del documento.

No es un formato de imagen

Un PNG contiene píxeles. Un SVG contiene esto:

XML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">  <title>Un circulo con borde</title>  <circle cx="50" cy="50" r="40" fill="#89b4fa" stroke="#1e66f5" stroke-width="4"/></svg>

Eso es un árbol de nodos. El navegador lo analiza con el mismo tipo de analizador que usa para el HTML, construye un DOM, resuelve el CSS que le corresponda, calcula geometría y pinta. El resultado es una imagen, pero el objeto que hay en memoria no es una imagen: es un documento.

Las consecuencias prácticas de esa diferencia forman la lista completa de por qué se elige SVG:

Cada forma es un elemento. Se puede seleccionar con querySelector, se le pueden poner clases, se le puede añadir un escuchador de eventos y aparece en el inspector con su caja y sus estilos.

El CSS se aplica de verdad. No un CSS especial: el mismo, con la misma cascada, la misma herencia y las mismas variables. Un :hover sobre un path funciona porque el path es un elemento.

El texto sigue siendo texto. Se busca con la búsqueda del navegador, se selecciona, se copia y lo lee un lector de pantalla.

El fichero es texto plano. Se compara en un control de versiones, se genera desde una plantilla del servidor, se comprime muy bien con gzip y se puede editar con un editor de texto.

Frente al canvas, que es el modelo inmediato del que sale la amnesia total descrita en el modo inmediato, SVG es el modelo retenido: el navegador guarda el árbol y lo vuelve a pintar cuando algo cambia, sin que tú tengas que redibujar nada. Cambias un atributo y la pantalla se actualiza.

Las reglas de XML que te van a morder

El HTML es tolerante por diseño. El SVG viene del XML, que no lo es en absoluto, y aunque el analizador de HTML disimula parte de la diferencia, hay cuatro reglas que acaban apareciendo.

Todo elemento se cierra. No hay etiquetas opcionales ni implícitas. <circle/> o <circle></circle>, pero nunca un <circle> suelto.

Los nombres distinguen mayúsculas de minúsculas. Y varios atributos importantes son camelCase: viewBox, preserveAspectRatio, patternUnits, gradientUnits, clipPathUnits, markerWidth, textLength, lengthAdjust. Escribir viewbox en minúsculas es un atributo distinto que no existe y que el navegador ignora en silencio.

Aquí hay un matiz que confunde a mucha gente: dentro de un documento HTML, el analizador corrige la capitalización de una lista conocida de atributos de SVG, así que <svg viewbox="0 0 24 24"> escrito a mano en el HTML funciona. Esa amabilidad desaparece en cuanto el mismo marcado vive en un fichero .svg, o en cuanto lo asignas desde JavaScript con setAttribute, donde la cadena se usa literalmente. Escribir siempre la forma correcta es la única política que no falla.

Los caracteres reservados van escapados. Un & suelto en un texto rompe el documento entero; en un fichero SVG independiente, un error de sintaxis produce una pantalla de error del analizador de XML en lugar de un dibujo.

El orden es el orden de pintado. No hay z-index en SVG: lo que va después se pinta encima. Reordenar es mover nodos en el árbol.

El espacio de nombres

XML permite mezclar vocabularios en un mismo documento, y para distinguirlos usa espacios de nombres. El de SVG es http://www.w3.org/2000/svg, y de ahí salen dos obligaciones muy concretas.

En un fichero .svg independiente, el atributo xmlns es obligatorio. Sin él, el documento no es SVG para el navegador y no se dibuja nada. Dentro de un documento HTML no hace falta, porque el analizador de HTML asigna el espacio de nombres automáticamente al ver la etiqueta de apertura.

Al crear elementos por código hay que usar la función con espacio de nombres.

JS
// Correctoconst svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');const circulo = document.createElementNS('http://www.w3.org/2000/svg', 'circle');circulo.setAttribute('cx', '50');circulo.setAttribute('cy', '50');circulo.setAttribute('r', '40');svg.appendChild(circulo);document.body.appendChild(svg);

Existe también setAttributeNS, que solo hace falta para los pocos atributos que pertenecen a otro espacio de nombres. El caso que queda es el heredado xlink:href, sustituido en SVG 2 por un href normal que ya funciona en todos los navegadores actuales; si mantienes código antiguo, elemento.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', valor) es la forma correcta de escribirlo.

Elementos SVG en el árbol

Un elemento SVG es un SVGElement, no un HTMLElement, y esa diferencia de interfaz explica un puñado de comportamientos que sorprenden.

Lo que quieres En HTML En SVG
Leer o cambiar clases className o classList classList (nunca className)
Estilos en línea elemento.style elemento.style, igual
Datos propios dataset dataset, igual
Medir la caja getBoundingClientRect getBoundingClientRect en píxeles de pantalla, o getBBox en unidades de usuario
Eventos addEventListener addEventListener, igual
Foco tabindex tabindex, funciona en SVG 2

La fila de la medición es la que más se usa. getBBox devuelve la caja del contenido en unidades del sistema de coordenadas del elemento, sin contar trazo ni filtros, y es la herramienta para ajustar un viewBox al contenido real o para colocar una etiqueta. getBoundingClientRect devuelve píxeles de pantalla, ya con todas las transformaciones aplicadas, y sirve para colocar un elemento HTML encima.

JS
const p = document.querySelector('#trazado');const caja = p.getBBox();            // unidades de usuario, sin trazoconst enPantalla = p.getBoundingClientRect();   // pixeles CSS, con transformaciones

Y la consecuencia de todo el capítulo, que conviene fijar antes de seguir: un SVG que forma parte del documento tiene todas las capacidades que acabamos de enumerar; un SVG cargado como imagen no tiene ninguna. Esa bifurcación es el asunto de la última lección del nivel, y es la fuente número uno de confusión con SVG.