Saltar al contenido

radialGradient y spreadMethod

El círculo final, el punto focal y el radio focal, qué hacen pad, reflect y repeat, y cómo se construye un degradado cónico que SVG no tiene.

El círculo final y el punto focal

Un radialGradient tiene dos geometrías: el círculo final, definido por cx, cy y r, que marca dónde está la parada de offset 1; y el punto focal, definido por fx, fy y fr, que marca dónde está la parada de offset 0.

Por omisión el foco coincide con el centro (fx y fy toman el valor de cx y cy) y fr vale cero, lo que produce el degradado radial simétrico de toda la vida. Los valores por defecto del círculo final son cx="50%" cy="50%" r="50%".

HTML
<svg viewBox="0 0 320 110" width="640">  <defs>    <radialGradient id="simple">      <stop offset="0" stop-color="#f9e2af" /><stop offset="1" stop-color="#f38ba8" />    </radialGradient>    <radialGradient id="foco" fx="0.3" fy="0.3">      <stop offset="0" stop-color="#f9e2af" /><stop offset="1" stop-color="#f38ba8" />    </radialGradient>    <radialGradient id="anillo" fr="0.35">      <stop offset="0" stop-color="#f9e2af" /><stop offset="1" stop-color="#f38ba8" />    </radialGradient>  </defs>  <circle cx="55"  cy="55" r="45" fill="url(#simple)" />  <circle cx="160" cy="55" r="45" fill="url(#foco)" />  <circle cx="265" cy="55" r="45" fill="url(#anillo)" /></svg>

El primero es el degradado neutro. El segundo tiene el punto de luz arriba a la izquierda, con las bandas de color deformándose alrededor de él: es el aspecto de una esfera iluminada, y es la razón de que fx y fy existan. Mover el foco es mucho más convincente que mover el centro entero, porque el borde exterior se queda quieto.

El tercero usa fr, el radio focal, que es una adición de SVG 2 disponible hoy en los tres motores. Con fr="0.35", todo el interior de ese radio es del color de la primera parada, y el degradado ocurre entre fr y r. Es la forma correcta de hacer un halo alrededor de un objeto sólido, y evita el apaño de meter dos paradas al mismo offset.

La restricción que hay que respetar: el punto focal debe estar dentro del círculo final. Si fx, fy cae fuera, el comportamiento no es el que esperas; SVG 1.1 lo desplazaba al borde y SVG 2 cambió la definición, así que el resultado no es idéntico entre motores. Mantén el foco dentro y no tendrás el problema.

spreadMethod

spreadMethod decide qué pasa fuera del rango del gradiente: más allá del final del vector en el lineal, más allá del círculo final en el radial. Acepta tres valores.

pad es el valor por omisión: el color de la última parada se extiende indefinidamente. Es lo que quieres el noventa por ciento de las veces.

reflect repite el gradiente en espejo, alternando dirección. Produce bandas simétricas sin costura.

repeat repite el gradiente desde el principio en cada ciclo, lo que produce un salto duro en cada repetición salvo que la primera y la última parada sean del mismo color.

HTML
<svg viewBox="0 0 320 80" width="640">  <defs>    <linearGradient id="p" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="20" y2="0"                    spreadMethod="pad">      <stop offset="0" stop-color="#89b4fa" /><stop offset="1" stop-color="#cba6f7" />    </linearGradient>    <linearGradient id="rf" href="#p" spreadMethod="reflect" />    <linearGradient id="rp" href="#p" spreadMethod="repeat" />  </defs>  <rect x="0"   y="5"  width="100" height="20" fill="url(#p)" />  <rect x="0"   y="30" width="100" height="20" fill="url(#rf)" />  <rect x="0"   y="55" width="100" height="20" fill="url(#rp)" /></svg>

Nótese que los tres heredan las paradas del primero con href, y que el vector es de solo 20 unidades sobre un rectángulo de 100: sin eso, no habría nada fuera del rango y spreadMethod no tendría efecto.

spreadMethod con reflect y un vector corto es la manera más económica de hacer un patrón de rayas suaves: dos paradas y un atributo, frente a un pattern completo. Con repeat y paradas duras, salen rayas nítidas:

HTML
<linearGradient id="rayas" gradientUnits="userSpaceOnUse"                x1="0" y1="0" x2="8" y2="8" spreadMethod="repeat">  <stop offset="0"    stop-color="#313244" />  <stop offset="0.5"  stop-color="#313244" />  <stop offset="0.5"  stop-color="#45475a" />  <stop offset="1"    stop-color="#45475a" /></linearGradient>

Rayas diagonales de 8 unidades de paso. Comparado con el pattern equivalente, pesa la mitad y no tiene el problema de las costuras.

El degradado cónico que SVG no tiene

CSS tiene conic-gradient. SVG no tiene nada equivalente, y es una carencia real porque el degradado cónico es exactamente lo que hace falta para un indicador circular de progreso, una ruleta de color o un gráfico de tarta con transición continua.

Hay tres maneras de construirlo y conviene conocer las tres porque sirven en contextos distintos.

Uno: sectores discretos. Divides el círculo en n sectores y le das a cada uno un color interpolado. Con 60 sectores el resultado es indistinguible de un cónico real a tamaños normales.

JS
function conico(cx, cy, r, colores, n = 90) {  const piezas = [];  for (let i = 0; i < n; i++) {    const a0 = (i / n) * 2 * Math.PI - Math.PI / 2;    const a1 = ((i + 1) / n) * 2 * Math.PI - Math.PI / 2;    // Un grado de solape evita las costuras del antialiasing    const a1s = a1 + 0.004;    const p = a => [cx + r * Math.cos(a), cy + r * Math.sin(a)];    const [x0, y0] = p(a0), [x1, y1] = p(a1s);    const d = `M ${cx} ${cy} L ${x0} ${y0} A ${r} ${r} 0 0 1 ${x1} ${y1} Z`;    piezas.push(`<path d="${d}" fill="${colores(i / n)}" />`);  }  return piezas.join('');}

El + 0.004 de solape es la parte que nadie te cuenta: sin él, entre sector y sector aparece una línea del color del fondo, porque el antialiasing de dos bordes adyacentes no llega a cubrir el píxel entero. Con el solape desaparece.

Dos: una imagen de fondo CSS. Si el SVG está en línea, un foreignObject con un div que tenga background: conic-gradient(...) funciona. Es una solución fea que resuelve el caso, y no sobrevive a servir el SVG como imagen.

Tres: usar el cónico de CSS y el SVG solo como máscara. Un div con conic-gradient de fondo, recortado con mask-image apuntando a la forma del SVG. Es la solución más limpia cuando estás en una página web y no en un fichero SVG autocontenido.