Saltar al contenido

El viewBox y qué hace exactamente

Entender el viewBox como la transformación implícita que relaciona el lienzo interno con el hueco en la página, y usarla para escalar, encuadrar y hacer zoom.

Los cuatro números

XML
<svg viewBox="0 0 24 24" width="48" height="48"> ... </svg>

Los cuatro valores son, en orden, min-x, min-y, ancho y alto, y describen un rectángulo en unidades de usuario, el sistema de coordenadas interno del dibujo.

Frente a ese rectángulo hay otro: el viewport, que es el hueco que el elemento ocupa en la página. Ese hueco sale de los atributos width y height, o del CSS que le apliques, o del contenedor cuando el SVG está en línea y no tiene tamaño propio.

El viewBox declara que ese rectángulo interno debe verse ocupando ese hueco. Todo lo demás es consecuencia.

En el ejemplo de arriba, un cuadrado de 24 por 24 unidades se muestra en 48 por 48 píxeles: el factor de escala es dos. Un circle con r="12" se verá con radio de 24 píxeles, sin que su atributo cambie. Las unidades del dibujo dejan de tener relación con los píxeles, y eso es exactamente lo que se quiere: el dibujo se describe una vez, en las unidades que le vengan bien, y se muestra al tamaño que haga falta.

La transformación implícita

Con preserveAspectRatio en su valor por defecto —que se estudia en la lección siguiente— la transformación se calcula así:

JS
// viewport: el hueco en pixeles. vb: los cuatro numeros del viewBoxfunction transformacionDeViewBox(viewport, vb) {  const sx = viewport.ancho / vb.ancho;  const sy = viewport.alto / vb.alto;  const escala = Math.min(sx, sy);                 // uniforme: el menor de los dos  const tx = (viewport.ancho - vb.ancho * escala) / 2 - vb.x * escala;  const ty = (viewport.alto - vb.alto * escala) / 2 - vb.y * escala;  return { escala, tx, ty };                        // equivale a translate(tx,ty) scale(escala)}

Tres cosas se leen directamente de esa función.

La escala es uniforme y se queda con la menor de las dos proporciones. Por eso el contenido nunca se deforma por defecto, y por eso si las proporciones del viewBox y del hueco no coinciden sobra espacio en un eje.

El sobrante se reparte por igual a los dos lados, que es lo que centra el dibujo.

min-x y min-y desplazan el origen. Un viewBox="-50 -50 100 100" pone el origen de coordenadas en el centro del dibujo, lo cual es comodísimo para cualquier cosa radial: un reloj, un gráfico de tarta, una rosa de los vientos. Las coordenadas negativas dejan de ser un problema y pasan a ser la mitad del lienzo.

XML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 -50 100 100" width="200" height="200">  <circle r="48" fill="none" stroke="#89b4fa" stroke-width="2"/>  <line x1="0" y1="0" x2="0" y2="-38" stroke="#cdd6f4" stroke-width="3"/>  <line x1="0" y1="0" x2="26" y2="0" stroke="#cdd6f4" stroke-width="2"/>  <circle r="3" fill="#f38ba8"/></svg>

Sin el viewBox centrado, ese mismo reloj exigiría sumar 50 a cada coordenada de cada línea.

Encuadrar, escalar y hacer zoom

De la transformación salen tres usos que son la razón de existir del atributo.

Adaptarse al contenedor. Un SVG con viewBox y sin width ni height tiene proporción intrínseca pero no tamaño, así que ocupa el ancho disponible y calcula el alto. Es el comportamiento que se quiere el noventa por ciento de las veces:

HTML
<div style="max-width:640px">  <svg viewBox="0 0 16 9" style="width:100%;height:auto;display:block">    <rect width="16" height="9" fill="#313244"/>    <circle cx="8" cy="4.5" r="3" fill="#a6e3a1"/>  </svg></div>

El display: block no es decorativo: sin él, el SVG es un elemento en línea y arrastra el espacio bajo la línea base, que aparece como unos píxeles de hueco misterioso bajo la imagen.

Ajustar el encuadre al contenido real. Si el dibujo no llena su viewBox, aparece con márgenes que nadie pidió. La medida exacta se obtiene del propio DOM:

JS
const svg = document.querySelector('svg');const b = svg.getBBox();                 // caja del contenido en unidades de usuarioconst m = 4;                             // margen en las mismas unidadessvg.setAttribute('viewBox', `${b.x - m} ${b.y - m} ${b.width + m * 2} ${b.height + m * 2}`);

getBBox ignora el grosor del trazo, así que el margen tiene que cubrir al menos la mitad del trazo más grueso o las líneas de los bordes saldrán cortadas.

Zoom y desplazamiento. Cambiar el viewBox es la forma más barata de hacer zoom en SVG: no hay que transformar nada, solo escribir cuatro números.

JS
const svg = document.querySelector('#mapa');let vista = { x: 0, y: 0, ancho: 800, alto: 600 };function aplicar() {  svg.setAttribute('viewBox', `${vista.x} ${vista.y} ${vista.ancho} ${vista.alto}`);}svg.addEventListener('wheel', e => {  e.preventDefault();  const caja = svg.getBoundingClientRect();  // Punto bajo el cursor en unidades de usuario  const ux = vista.x + (e.clientX - caja.left) / caja.width * vista.ancho;  const uy = vista.y + (e.clientY - caja.top) / caja.height * vista.alto;  const factor = Math.exp(e.deltaY * 0.0015);  vista.ancho *= factor;  vista.alto *= factor;  // Mantener bajo el cursor el mismo punto del dibujo  vista.x = ux - (e.clientX - caja.left) / caja.width * vista.ancho;  vista.y = uy - (e.clientY - caja.top) / caja.height * vista.alto;  aplicar();}, { passive: false });

Ese cálculo —convertir el punto del cursor a unidades de usuario, escalar, y reposicionar para que el punto siga bajo el cursor— es el zoom centrado en el puntero, y es el mismo razonamiento que en canvas resuelve la matriz inversa según invertir la transformación. Aquí sale más barato porque el navegador rehace el dibujo por ti.

Los errores clásicos

Creer que el viewBox recorta. No recorta él: recorta el elemento. La hoja de estilos del navegador aplica overflow: hidden al svg raíz, y por eso lo que queda fuera del rectángulo no se ve. Se puede cambiar con overflow: visible, y entonces el contenido se sale del hueco, lo que es útil para efectos que sobresalen y muy confuso si ocurre sin querer.

Poner width y height en el marcado y también en el CSS. Gana el CSS, y el atributo se queda como tamaño por defecto para cuando el CSS no diga nada. No es un error en sí, pero explica por qué a veces cambiar el atributo no hace nada.

Quitar el viewBox al optimizar. Algunas herramientas de optimización eliminan el viewBox cuando hay width y height, y con eso el SVG deja de escalar: se queda clavado a su tamaño y cualquier CSS que intente cambiarlo produce un dibujo recortado en vez de escalado. Es una de las razones por las que hay que revisar la configuración de esas herramientas.

Confundir las unidades del viewBox con píxeles. No lo son. Si tu viewBox es de 1000 unidades de ancho y el hueco mide 200 píxeles, un objeto de 10 unidades mide 2 píxeles. Todo el dibujo interno vive en su propia escala, y esa independencia es justamente lo que hace que el mismo fichero sirva para un icono y para un cartel.