Máscaras, degradados y animaciones con SMIL en un SVG
Tabla de contenidos
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>
.
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:
- Mostrar el elemento en pantalla
- Mostrar cada elemento de texto
- Modificar los puntos de parada de los colores en los degradados
- Animar la posición en el eje vertical de cada círculo
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:
[attributeName]
- Para indicar qué atributo se va a modificar[values]
- Los valores -separados por comas- que van a tomar[dur]
- La duración de la animación[repeatCount]
- El número de veces que se repetirá[begin]
- En qué punto temporal se lanza la animación[calcMode]
- Para indicar el modo de interpolar la animación
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
- Especificación de SMIL
- Guía de animaciones SVG SMIL
- <animate> en MDN
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