Máscaras, degradados y animaciones con SMIL en un SVG

Ya hemos hablado en otros artículos sobre cómo animar elementos web, y hemos hecho hincapié en que hay diferentes formas y herramientas disponibles que debemos conocer antes de empezar a trabajar en una animación. Cada una tiene sus pros y sus contras, por eso insistimos en que hay que conocerlas bien para poder optimizar mejor nuestro tiempo. Pues bien, cuando queremos animar un SVG, una de éstas opciones (poco utilizadas) es hacerlo con SMIL.

AVISO: No está clara la evolución de SMIL. Tiene una amplio soporte pero podría perder peso en el futuro si los navegadores se decantasen por el uso de animaciones con CSS y de Web Animations API

SMIL es el acrónimo de Synchronized Multimedia Integration Language, y es un conjunto de etiquetas y atributos especiales que añadidas dentro de nuestros <svg> nos van a permitir animar, transicionar o interpolar los valores de los atributos de las etiquetas que lo conforman. La especificación es muy amplia y no vamos a listar sus propiedades y valores pero te recomendamos leer artículo de Sara Soueidan para CSS-TRICKS. Nosotros vamos a desgranar esta demo para aclarar su funcionamiento y ya puestos para explicar cómo aplicar máscaras y degradados dentro de un <svg>.

Animación de una tarjeta con texto con colores hecha con SVG + SMIL

Punto de partida

Vamos a crear un <svg> que contenga algún texto para hacer uso de <text> y <tspan>, y una etiqueta <defs> en la que definiermos nuestros degradados y máscaras (<linearGradient> y <mask>). En nuestro caso vamos a utilizar dos elementos textuales, dos degradados y una máscara compuesta por cien círculos de diferentes tamaños, por eso para escribirlo y manejarlo más cómodamente vamos a utilizar PUGJS para poder hacer uso de algo de JavaScript y escribir bucles y funciones aritméticas.

Crear degradados y máscaras

Vamos a crear dos degradados lineales usando dos colores (inicio y fin) y a asignarles una ID única para poder identificarlos, llamarlos y aplicarlos sobre los elementos que queramos, que en nuestro caso será sobre <text> usando el atributo [fill].

<defs>
  <linearGradient id="gradientMamut" gradientTransform="rotate(0)">
    <stop offset="0.5" stop-color="#ff0066"></stop>
    <stop offset="0.95" stop-color="#17e2c6"></stop>
  </linearGradient>
</defs>

<text x="40" y="75" fill="url(#gradientMamut)">

A continuación y dentro de la misma etiqueta de definición creamos la máscara y le asignamos otra ID para poderla aplicar sobre otros elementos como hicimos anteriormente.

<defs>
  ...
  <mask id="ballons"></mask>
</defs>

<text x="40" y="75" fill="url(#gradientMamut)" mask="url(#ballons)">

Vamos a utilizar un bucle while para crear los círculos que van a conformar la máscara y para dinamizarlos vamos a apoyarnos en un par de funciones con las que obtendremos valores aleatorios, de esta manera cada círculo tendrá los mismos atributos pero con valores distintos y cada vez que se inicialice el resultado será diferente aunque parecido.

  var getRandom = (factor, addition = 0) => { 
    return Math.floor(Math.random() * factor + addition) 
  }
  var getAlpha = (factor) => {
    return Number((Math.random() * factor) / 100).toPrecision(1)
  }

  while i < 100
    - var positionX = getRandom(130)
    - var positionY = getRandom(10, 90)
    - var radius = getRandom(13, 1)
    - var duration = getRandom(5, (20 - radius))
    - var red = getRandom(255)
    - var green = getRandom(255)
    - var blue = getRandom(255)
    - var alpha = getAlpha(90)
    
    circle(fill="rgba("+red+", "+green+", "+blue+", "+alpha+")" cx=positionX cy=positionY r=radius)
<defs>
  ...
  <mask id="ballons">
    <circle fill="rgba(64, 123, 91, 0.6)" cx="124" cy="91" r="6"></circle>
    <circle fill="rgba(190, 99, 182, 0.7)" cx="44" cy="98" r="6"></circle>
    <circle fill="rgba(61, 250, 71, 0.7)" cx="46" cy="95" r="12"></circle>
    <circle fill="rgba(63, 100, 183, 0.05)" cx="53" cy="95" r="11"></circle>
    <circle fill="rgba(52, 189, 254, 0.2)" cx="47" cy="99" r="7"></circle>
  </mask>
</defs>

<text x="40" y="75" fill="url(#gradientMamut)" mask="url(#ballons)">

Secuenciando la animación

La animación constará de varios pasos:

Animación con CSS

Para presentar el elemento en pantalla vamos a crear una sencilla animación CSS que se dispara tras cargarse el documento y que cambiará la opacidad y posición del elemento para que se haga visible suavemente.

@keyframes show {
  0% {
    opacity: 0;
    transform: translate(0, 2rem);
  }
  100% {
    opacity: 1;
    transform: none;
  }
}

Animación con SMIL

Para animar las etiquetas que conforman nuestro <svg> vamos a utilizar la etiqueta <animate> a la que le podemos declarar los atributos que controlan la animación, siendo los más recurrentes:

Así que animaremos los elementos de texto

<text x="40" y="75" mask="url(#ballons)" fill="url(#gradientMamut)">
  <tspan dx="-5">yo</tspan>
  <animate attributeName="opacity" values="0;1" dur="700ms" repeatCount="1" calcMode="paced"></animate>
  <animate attributeName="y" values="80;75" dur="750ms" repeatCount="1" calcMode="paced"></animate>
</text>

Los puntos de parada de los colores de cada degradado

<linearGradient id="gradientMamut" gradientTransform="rotate(0)">
  <stop offset="0.5" stop-color="#ff0066">
    <animate attributeName="offset" values="0.5;0.1;0.5" repeatCount="indefinite" dur="10s" calcMode="paced"></animate>
  </stop>
  <stop offset="0.95" stop-color="#17e2c6">  
    <animate attributeName="offset" values="0.95;0.99;0.95" repeatCount="indefinite" dur="10s" calcMode="paced"></animate>
  </stop>
</linearGradient>

Y cada círculo dentro de la máscara, su posición final en ‘Y’ con el valor aleatorio que obtuvimos, su duración que también será aleatoria y su momento de inicio que será incremental.

<circle fill="rgba(78, 196, 61, 0.7)" cx="1" cy="90" r="11">
  <animate attributeName="cy" attributeType="XML" values="0;92" dur="10s" repeatCount="indefinite" begin="0.42000000000000004s" calcMode="paced"></animate>
</circle>

Finalmente con la combinación de todo lo anterior, podemos realizar una alegre y colorida tarjeta animada con únicamente una etiqueta HTML, muy poquito CSS y nada de JavaScript.

Conclusión

A pesar de haber combinado técnicas complejas y poco habituales al animar una máscara aplicada a un SVG, apenas hemos descubierto la potencia de SMIL, y no lo vamos a descubrir si de vez en cuando no probamos alternativas al modo y entorno en el que solemos movernos y sentirnos cómodos. A menudo resulta difícil sacar el tiempo y el empuje pero solemos encontrarnos con sorpresas agradables al final del camino.

Para saber más

comments powered by Disqus

Si te ha parecido interesante

Tanto si tienes alguna duda o quieres charlar sobre este tema, como si el contenido o nuestros perfiles te parecen interesantes y crees que pdemos hacer algo juntos, no dudes en ponerte en contacto con nosotros a través de twitter o en el email hola@mamutlove.com

Actualmente estamos abiertos a nuevas propuestas para colaborar