Saltar al contenido

gradientUnits: los dos sistemas de coordenadas

objectBoundingBox frente a userSpaceOnUse, por qué un gradiente desaparece sobre una línea horizontal, por qué un degradado a 45 grados se deforma, y cuándo usar cada sistema.

Los dos sistemas

gradientUnits acepta dos valores y su valor por omisión es objectBoundingBox.

Con objectBoundingBox, las coordenadas del gradiente (x1, y1, x2, y2 en el lineal; cx, cy, r, fx, fy en el radial) se interpretan como fracciones de la caja envolvente del elemento que lo usa. El 0 es el borde izquierdo o superior de esa caja; el 1 es el derecho o el inferior. Da igual dónde esté la forma y cuánto mida: el gradiente se adapta.

Con userSpaceOnUse, las coordenadas se interpretan en el sistema de coordenadas del usuario vigente en el elemento que referencia el gradiente. Son unidades absolutas del viewBox, igual que las de cualquier forma.

HTML
<svg viewBox="0 0 220 100" width="440">  <defs>    <!-- Se adapta a cada forma -->    <linearGradient id="caja">      <stop offset="0" stop-color="#89b4fa" /><stop offset="1" stop-color="#f38ba8" />    </linearGradient>    <!-- Coordenadas absolutas del lienzo -->    <linearGradient id="lienzo" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="220" y2="0">      <stop offset="0" stop-color="#89b4fa" /><stop offset="1" stop-color="#f38ba8" />    </linearGradient>  </defs>  <g fill="url(#caja)">    <rect x="10" y="10" width="40" height="30" />    <rect x="10" y="55" width="90" height="30" />  </g>  <g fill="url(#lienzo)">    <rect x="120" y="10" width="40" height="30" />    <rect x="120" y="55" width="90" height="30" />  </g></svg>

La diferencia es inmediata al mirarlo. En el grupo de la izquierda, cada rectángulo tiene el degradado completo: el pequeño va de azul a rojo en 40 unidades, el grande en 90. En el de la derecha, los dos comparten un único degradado que recorre todo el lienzo, así que el rectángulo pequeño solo muestra el trozo que le toca por su posición.

Ninguno de los dos es «el correcto»: responden a intenciones distintas. Si cada forma debe tener su propio degradado completo, objectBoundingBox. Si varias formas deben compartir un degradado continuo, como las barras de un gráfico teñidas por altura, userSpaceOnUse.

El fallo número uno: el gradiente que no se ve

La especificación dice, sin adornos, que cuando gradientUnits es objectBoundingBox y la caja envolvente del elemento tiene anchura o altura cero, el gradiente no se renderiza. El elemento se pinta como si tuviera fill: none.

¿Qué elementos tienen una caja envolvente con una dimensión nula? Más de los que parece:

  • Un line horizontal o vertical.
  • Un path que solo contiene un segmento horizontal o vertical.
  • Un rect con height="0", que aparece cuando un dato vale cero.
  • Una polyline cuyos puntos están todos en la misma fila.

Y el caso que más veces se sufre: un trazo. Aplicar stroke="url(#g)" a un line horizontal. La caja envolvente sigue siendo la geométrica, sin trazo, así que tiene altura cero y el gradiente desaparece. La línea sale invisible y nadie entiende por qué, porque el mismo gradiente funciona perfectamente en el rectángulo de al lado.

HTML
<svg viewBox="0 0 200 60" width="400">  <defs>    <linearGradient id="g1">      <stop offset="0" stop-color="#89b4fa" /><stop offset="1" stop-color="#f38ba8" />    </linearGradient>    <linearGradient id="g2" gradientUnits="userSpaceOnUse" x1="20" y1="0" x2="180" y2="0">      <stop offset="0" stop-color="#89b4fa" /><stop offset="1" stop-color="#f38ba8" />    </linearGradient>  </defs>  <!-- Invisible: caja de altura cero -->  <line x1="20" y1="20" x2="180" y2="20" stroke="url(#g1)" stroke-width="6" />  <!-- Visible -->  <line x1="20" y1="45" x2="180" y2="45" stroke="url(#g2)" stroke-width="6" /></svg>

El diagnóstico es de diez segundos: si un gradiente no se ve, cambia gradientUnits a userSpaceOnUse y pon coordenadas absolutas. Si aparece, la causa era la caja degenerada y ya sabes por qué.

Y una vez sabido, la regla preventiva: toda forma que pueda ser plana usa userSpaceOnUse. Líneas, ejes, separadores, barras que pueden valer cero.

El fallo número dos: el ángulo que se deforma

Con objectBoundingBox, el gradiente se define en un cuadrado unitario que después se escala para encajar en la caja del elemento. Ese escalado es independiente en cada eje, así que si la caja no es cuadrada, el espacio del gradiente se deforma.

Un vector de (0,0) a (1,1) es una diagonal a 45 grados en el cuadrado unitario. Aplicado a un rectángulo de 200 por 50, el escalado multiplica por 200 en x y por 50 en y, y la diagonal resultante forma un ángulo de unos 14 grados, no 45. Además, las bandas de color no son perpendiculares al vector: también se han inclinado.

HTML
<svg viewBox="0 0 220 80" width="440">  <defs>    <linearGradient id="deforme" x1="0" y1="0" x2="1" y2="1">      <stop offset="0" stop-color="#a6e3a1" /><stop offset="1" stop-color="#cba6f7" />    </linearGradient>    <linearGradient id="recto" gradientUnits="userSpaceOnUse"                    x1="10" y1="10" x2="70" y2="70">      <stop offset="0" stop-color="#a6e3a1" /><stop offset="1" stop-color="#cba6f7" />    </linearGradient>  </defs>  <rect x="10"  y="10" width="90" height="60" fill="url(#deforme)" />  <rect x="120" y="10" width="90" height="60" fill="url(#recto)" /></svg>

El de la izquierda tiene el degradado inclinado y aplastado; el de la derecha, a 45 grados de verdad.

Este comportamiento no es un fallo: es la definición de un sistema de coordenadas relativo, y es lo que hace que el gradiente se adapte a cualquier tamaño. Solo hay que saber que el ángulo no se conserva, y que si el ángulo importa, hace falta userSpaceOnUse.

Hay una tercera vía para el caso concreto de querer 45 grados reales en unidades de caja: gradientTransform con una rotación, combinado con un vector horizontal. La rotación se aplica en el espacio del gradiente antes del escalado, así que sigue deformándose. No hay atajo: si quieres control angular, usa el espacio de usuario.

El criterio

Situación Sistema Motivo
Cada forma con su degradado completo objectBoundingBox Se adapta al tamaño sin recalcular
Varias formas con un degradado continuo userSpaceOnUse Un solo espacio compartido
Trazo de una línea o de una forma plana userSpaceOnUse Evita la caja degenerada
El ángulo debe ser exacto userSpaceOnUse El otro sistema deforma
El elemento cambia de tamaño y el degradado debe seguir objectBoundingBox Es relativo por definición
Barras de un gráfico teñidas por altura global userSpaceOnUse El color debe depender del valor, no de la barra

Y una advertencia sobre userSpaceOnUse que se paga cuando el gráfico es responsivo: las coordenadas son absolutas en el viewBox, así que si el viewBox cambia, hay que recalcularlas. Si tu gráfico se redimensiona cambiando el viewBox en lugar de la caja CSS, el gradiente hay que regenerarlo. Es el precio del control.