Saltar al contenido

symbol y su propio viewBox

Por qué symbol es el contenedor correcto de un sprite: viewBox individual por icono, preserveAspectRatio por instancia, y tamaño controlado desde el use.

Un mini documento por icono

symbol no se renderiza nunca por sí solo, igual que defs. Su particularidad es que acepta viewBox y preserveAspectRatio, y que cuando se instancia con use se comporta como si fuera un elemento svg anidado: establece un viewport nuevo, con las dimensiones que le dé el use, y ajusta su viewBox dentro.

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true">  <symbol id="i-24" viewBox="0 0 24 24">    <path d="M12 2 L22 22 L2 22 Z" fill="currentColor" />  </symbol>  <symbol id="i-16" viewBox="0 0 16 16">    <circle cx="8" cy="8" r="6" fill="currentColor" />  </symbol></svg><svg width="32" height="32" style="color:#89b4fa"><use href="#i-24" /></svg><svg width="32" height="32" style="color:#a6e3a1"><use href="#i-16" /></svg>

Los dos iconos salen del mismo tamaño en pantalla aunque uno esté dibujado en una rejilla de 24 y el otro en una de 16. Cada symbol escala su contenido a la caja que le da el use. Con un g en lugar de un symbol, el segundo icono saldría a dos tercios del tamaño del primero y habría que corregirlo con un transform="scale(1.5)" escrito a mano.

Esa es la razón concreta por la que los sprites se hacen con symbol. No es convención: es la única forma de que iconos de procedencias distintas convivan sin aritmética.

De dónde sale el tamaño de la instancia

Cuando se instancia un symbol, el tamaño del viewport que se crea se decide con esta prioridad:

  1. Los atributos width y height del use, si los tiene.
  2. Los atributos width y height del symbol, si los tiene.
  3. El cien por cien en ambas dimensiones, que se resuelve contra el viewport que contiene al use.

En la práctica el caso tres es el que se usa: el symbol no declara tamaño, el use tampoco, y el icono llena el svg que lo envuelve. El tamaño se controla entonces desde CSS sobre ese svg exterior:

CSS
.ico { width: 1.25rem; height: 1.25rem; }.ico-grande { width: 2rem; height: 2rem; }

Es el patrón correcto porque deja el tamaño en la capa donde vive el resto de la maquetación, y porque em y rem funcionan: un icono dimensionado en em escala con el texto que lo acompaña, que es exactamente lo que quieres en un botón.

CSS
/* Icono que crece con el texto de su boton */.boton .ico { width: 1em; height: 1em; vertical-align: -0.125em; }

Ese vertical-align negativo compensa la diferencia entre la caja del icono y la línea base del texto. Sin él, el icono se alinea por su borde inferior con la línea base y queda demasiado alto. El valor exacto depende de la fuente; entre -0.1em y -0.15em funciona con casi todas.

preserveAspectRatio por instancia

symbol acepta preserveAspectRatio, y use puede sobrescribirlo. Eso permite que la misma forma se ajuste de maneras distintas según dónde se use.

El valor por omisión es xMidYMid meet: mantiene la proporción y encaja el contenido entero dentro de la caja, centrado. Es lo que quieres para un icono.

slice hace lo contrario: mantiene la proporción y llena la caja, recortando lo que sobra. Es lo que quieres para un patrón decorativo o una ilustración de fondo.

none deforma para llenar exactamente. Casi nunca es lo que quieres, con una excepción: un separador ondulado que debe estirarse a lo ancho del contenedor sin cambiar de altura.

HTML
<!-- El mismo simbolo, tres ajustes --><svg width="80" height="40"><use href="#i-24" /></svg><svg width="80" height="40"><use href="#i-24" preserveAspectRatio="xMidYMid slice" /></svg><svg width="80" height="40"><use href="#i-24" preserveAspectRatio="none" /></svg>

En una caja no cuadrada, el primero deja bandas vacías a los lados, el segundo recorta arriba y abajo, y el tercero achata la forma.

symbol frente a svg anidado

Hay una alternativa que hace casi lo mismo: un elemento svg dentro de defs, referenciado por use. También establece viewport, también acepta viewBox. Las diferencias:

symbol svg anidado
Se renderiza sin use No Sí
Acepta viewBox Sí Sí
Necesita defs alrededor No Sí, para no pintarse
Semántica para herramientas Es un símbolo de sprite Es un documento anidado

symbol es más limpio porque expresa la intención y no necesita el defs. La única razón para usar un svg anidado es si el contenido tiene que renderizarse también en su sitio original, y en un sprite eso no pasa nunca.

Un apunte sobre accesibilidad que se cierra en el nivel 24: un title dentro de un symbol se clona en cada árbol de sombra y se convierte en el nombre accesible de cada instancia. Eso suena bien y produce un problema, porque el mismo icono puede necesitar nombres distintos según dónde se use, y el que viene del símbolo no se puede sobrescribir. La práctica correcta es no poner title en el símbolo y poner el nombre en el svg que envuelve al use.