Saltar al contenido

img, object o en línea: qué permite cada forma de incluir un SVG

La decisión que determina qué CSS se aplica, qué scripts corren y qué recursos se cargan, y por qué tu hoja de estilos no afecta al SVG que acabas de insertar.

Las tres formas

Como imagen. El SVG es el origen de una etiqueta de imagen, o el valor de una propiedad de CSS como background-image o mask-image.

HTML
<img src="/iconos/mapa.svg" alt="Mapa de la zona" width="48" height="48">

El navegador lo trata como una imagen de tamaño arbitrario: lo descarga, lo dibuja en su caja y ahí acaba la relación. Se cachea como cualquier imagen, admite loading="lazy", y su contenido está completamente aislado de la página.

Como documento embebido. Con object, iframe o el heredado embed.

HTML
<object data="/graficos/ventas.svg" type="image/svg+xml"        width="600" height="400" aria-label="Grafico de ventas trimestrales">  <img src="/graficos/ventas.png" alt="Grafico de ventas trimestrales"></object>

Aquí sí hay un documento de verdad, con su propio árbol, sus propios estilos y sus propios scripts, dentro de la página pero separado de ella. Lo que va entre las etiquetas de object es contenido alternativo que se muestra si el recurso no carga.

En línea. El marcado del SVG vive dentro del HTML.

HTML
<svg viewBox="0 0 24 24" width="24" height="24" fill="none"     stroke="currentColor" stroke-width="2" aria-hidden="true">  <path d="M3 12h18M12 3v18"/></svg>

No hay fichero, no hay petición y no hay frontera: esos elementos son nodos del mismo documento que todo lo demás, con las capacidades completas descritas en SVG es XML en el DOM.

Por qué el CSS de la página no llega

Un SVG cargado como imagen se dibuja en un contexto aparte al que la cascada de tu página no entra. No es que el selector no lo alcance: es que hay dos documentos, y las hojas de estilo de uno no se aplican al otro. Por eso este código, que todo el mundo escribe alguna vez, no hace absolutamente nada:

CSS
/* No funciona: el path esta en otro documento */img[src$=".svg"] path { fill: red; }

La misma frontera explica las otras tres sorpresas.

currentColor no funciona. Dentro de la imagen no hay ningún color heredado de tu página, así que currentColor se resuelve contra el valor por defecto del propio documento SVG, que es negro salvo que el fichero diga otra cosa.

Las fuentes externas no se cargan. Un SVG en modo imagen no puede pedir recursos externos, así que la fuente declarada con @font-face no llega y el texto se dibuja con la de reserva, normalmente con otra anchura y descolocando el diseño. Es la causa clásica de un SVG exportado que se ve perfecto al abrirlo directamente y roto dentro de la página. Las salidas son dos: incrustar la fuente en el propio fichero como URI de datos, o convertir el texto a trazados al exportar, con lo que deja de ser texto.

Los scripts no se ejecutan. Nada de JavaScript dentro de un SVG cargado como imagen. Y esto no es una limitación molesta sino la característica que lo hace utilizable: es lo que permite mostrar un SVG subido por un usuario sin abrir un agujero de seguridad.

La tabla de decisión

Capacidad Como imagen Con object En línea
El CSS de la página lo estila No No Sí
currentColor sigue al texto No No Sí
Las variables CSS de la página llegan No No Sí
El CSS dentro del fichero funciona Sí Sí Sí, y se mezcla con el de la página
Animaciones declarativas del fichero Sí Sí Sí
Scripts dentro del fichero No Sí Sí, con los permisos de la página
Recursos externos: fuentes, imágenes No Sí Sí
Accesible desde el JavaScript de la página No Sí, con contentDocument Sí, directamente
Se cachea por separado Sí Sí No: viaja en el HTML
Petición adicional Sí Sí No
Seguro para contenido ajeno Sí Con aislamiento No
Texto alternativo alt Contenido de reserva y aria-label title y desc internos

Traducido a recomendaciones, sin ambigüedad:

Iconos que deben tomar el color del texto: en línea. Es el único caso en el que currentColor funciona, y es la razón principal por la que los sistemas de iconos modernos son en línea.

Ilustraciones, logotipos y fondos decorativos: como imagen. Se cachean, no engordan el HTML, se pueden cargar de forma diferida y no necesitan estilado.

Gráficos con interacción propia y autocontenida, sobre todo si vienen de un tercero o de otro equipo: con object. Corren sus scripts, no contaminan tu página y no la contaminan con sus identificadores.

Gráficos que la aplicación tiene que manipular: en línea, generados o modificados desde tu código.

SVG subido por usuarios: como imagen, siempre.

Un apunte sobre object que se pasa por alto: desde la página sí se puede llegar a su interior, si el fichero es del mismo origen, y eso permite estilarlo o manipularlo después de cargado.

JS
const obj = document.querySelector('object');obj.addEventListener('load', () => {  const doc = obj.contentDocument;            // tambien obj.getSVGDocument()  if (!doc) return;                            // otro origen: no hay acceso  const estilo = doc.createElementNS('http://www.w3.org/2000/svg', 'style');  estilo.textContent = '.barra { fill: #a6e3a1 }';  doc.documentElement.appendChild(estilo);  doc.querySelectorAll('.barra').forEach(b => {    b.addEventListener('click', () => console.log(b.dataset.valor));  });});

Es la vía intermedia entre el aislamiento total de la imagen y la ausencia de aislamiento del SVG en línea, y está infrautilizada.

Los dos problemas del SVG en línea

El SVG en línea lo permite todo, y a cambio tiene dos inconvenientes propios que hay que gestionar.

No se cachea y se repite. El marcado viaja dentro del HTML, así que se descarga con cada página y ocupa sitio en el documento. Con un icono de diez líneas da igual; con un mapa de cuatrocientos kilobytes repetido en cada página es un problema de peso y de tiempo de análisis. Y si el mismo icono aparece treinta veces en una lista, el marcado se duplica treinta veces, con su coste en nodos del DOM. La solución dentro del propio documento es definir el dibujo una vez y referenciarlo desde cada aparición, que es todo un tema en sí mismo.

Los identificadores son globales. Esto sorprende y rompe cosas de verdad. Los id de un SVG en línea comparten espacio con los de todo el documento HTML, y las referencias internas —un gradiente, una máscara, un recorte, un marcador— se resuelven por id. Si insertas dos veces el mismo SVG con un gradiente llamado degradado, el segundo tiene un identificador duplicado y ambos elementos usarán el primero, porque la búsqueda devuelve el primero que encuentra. El resultado es una figura que aparece con el color de otra, y es de esos fallos que uno tarda en creerse.

HTML
<!-- Los dos usaran el gradiente del PRIMERO --><svg viewBox="0 0 100 100"><defs><linearGradient id="g">…</linearGradient></defs>  <rect width="100" height="100" fill="url(#g)"/></svg><svg viewBox="0 0 100 100"><defs><linearGradient id="g">…</linearGradient></defs>  <rect width="100" height="100" fill="url(#g)"/></svg>

La solución es prefijar los identificadores con algo único por instancia en el momento de insertar:

JS
let contador = 0;function insertarSvg(contenedor, marcado) {  const sufijo = '-i' + (++contador);  const doc = new DOMParser().parseFromString(marcado, 'image/svg+xml');  const raiz = doc.documentElement;  // Renombrar los id y las referencias url(#...) y href="#..."  raiz.querySelectorAll('[id]').forEach(el => {    const viejo = el.id;    const nuevo = viejo + sufijo;    el.id = nuevo;    raiz.querySelectorAll('*').forEach(otro => {      for (const atributo of otro.attributes) {        if (atributo.value.includes('#' + viejo)) {          atributo.value = atributo.value.replaceAll('#' + viejo, '#' + nuevo);        }      }    });  });  contenedor.appendChild(document.importNode(raiz, true));}

Ese recorrido es cuadrático y solo vale para insertar unos pocos SVG; para un sistema de iconos completo, lo que se hace es generar los identificadores únicos en el momento de construir la página, no en el navegador. Y la observación general que conviene llevarse: cualquier cosa que dentro de un SVG se refiera a otra por id deja de ser autocontenida en cuanto el SVG se pone en línea, y eso incluye gradientes, patrones, máscaras, recortes, filtros y marcadores.