Efecto de fragmentación de imágenes con máscaras CSS y propiedades personalizadas.

Geoff compartió esta idea de un tablero de ajedrez donde los mosaicos desaparecen uno por uno para revelar una imagen. En él, un elemento tiene una imagen de fondo, luego un diseño de cuadrícula CSS contiene los “mosaicos” que van desde un color de fondo relleno hasta transparente, revelando la imagen. Un ligero toque de SCSS hace tambalear la animación.
Tengo una idea similar, pero con un enfoque diferente. En lugar de revelar la imagen, comenzamos con ella completamente revelada y luego dejamos que desaparezca un mosaico a la vez, como si estuviera flotando en pequeños fragmentos.
Aquí hay una demostración funcional del resultado. Sin manejo de JavaScript, sin trucos de SVG. Sólo una img
y algo de magia SCSS.
¿Guay, verdad? Claro, pero aquí está el problema. Tendrás que ver esto en Chrome, Edge u Opera porque esos son los únicos navegadores compatibles @property
en este momento y ese es un componente clave de esta idea. No dejaremos que eso nos detenga porque esta es una gran oportunidad para mojarnos las manos con características interesantes de CSS, como máscaras y animaciones de degradados lineales con la ayuda de @property
.
enmascarar cosas
A veces el enmascaramiento es difícil de conceptualizar ya menudo se confunde con el recorte. La conclusión: las máscaras son imágenes. Cuando una imagen se aplica como máscara a un elemento, cualquier parte transparente de la imagen nos permite ver a través del elemento. Cualquier parte opaca hará que el elemento sea completamente visible.
Las máscaras funcionan de la misma manera que la opacidad, pero en diferentes partes del mismo elemento. Eso es diferente del recorte, que es un camino donde todo lo que está fuera del camino simplemente está oculto. Las ventajas del enmascaramiento es que podemos tener tantas capas de máscara como queramos en el mismo elemento, similar a cómo podemos encadenar múltiples imágenes en background-image
.
Y como las máscaras son imágenes, podemos usar CSS degradados para crearlas. Tomemos un ejemplo sencillo para comprender mejor el truco.
img { mask: linear-gradient(rgba(0,0,0,0.8) 0 0) left, /* 1 */ linear-gradient(rgba(0,0,0,0.5) 0 0) right; /* 2 */ mask-size: 50% 100%; mask-repeat: no-repeat;}
Aquí, estamos definiendo dos capas de máscara en una imagen. Ambos son de un color sólido pero los valores de transparencia alfa son diferentes. La sintaxis anterior puede parecer extraña, pero es una forma simplificada de escribir linear-gradient(rgba(0,0,0,0.8), rgba(0,0,0,0.8))
.
Vale la pena señalar que el color que utilizamos es irrelevante ya que el valor predeterminado mask-mode
es alpha
. El valor alfa es lo único relevante. Nuestro gradiente puede ser linear-gradient(rgba(X,Y,Z,0.8) 0 0)
donde X
y Y
son Z
valores aleatorios.
Cada capa de máscara es igual 50% 100%
(o la mitad del ancho y el alto total de la imagen). Una máscara cubre la izquierda y la otra cubre la derecha. Al final, tenemos dos máscaras que no se superponen y cubren toda el área de la imagen y, como comentamos anteriormente, cada una tiene un valor de transparencia alfa definido de manera diferente.
Animando gradientes lineales
Lo que queremos hacer es aplicar una animación a los valores alfa del gradiente lineal de nuestra máscara para crear una animación de transparencia. Más adelante, los convertiremos en animaciones asincrónicas que crearán el efecto de fragmentación.
Animar degradados es algo que no hemos podido hacer en CSS. Es decir, hasta que obtuvimos soporte limitado para @property
. Jhey Tompkins profundizó en los increíbles poderes de animación de @property
, demostrando cómo se puede utilizar para realizar transiciones de gradientes. Nuevamente, querrás ver esto en Chrome u otro navegador compatible con Blink:
En resumen, @property
nos permite crear propiedades CSS personalizadas donde podemos definir la sintaxis especificando un tipo. Creemos dos propiedades, --c-0
y --c-1
, que tomen un número con un valor inicial de 1
.
@property --c-0 { syntax: "number"; initial-value: 1; inherits: false;}@property --c-1 { syntax: "number"; initial-value: 1; inherits: false;}
Esas propiedades representarán los valores alfa en nuestra máscara CSS. Y dado que ambos son completamente opacos por defecto (es decir 1
), la imagen completa se muestra a través de la máscara. Así es como podemos reescribir la máscara usando las propiedades personalizadas:
/* Omitting the @property blocks above for brevity */img { mask: linear-gradient(rgba(0,0,0,var(--c-0)) 0 0) left, /* 1 */ linear-gradient(rgba(0,0,0,var(--c-1)) 0 0) right; /* 2 */ mask-size: 50% 100%; mask-repeat: no-repeat; transition: --c-0 0.5s, --c-1 0.3s 0.4s;}img:hover { --c-0:0; --c-1:0;}
Todo lo que estamos haciendo aquí es aplicar una duración y un retraso de transición diferentes para cada variable personalizada. Continúe y coloque el cursor sobre la imagen. El primer degradado de la máscara se desvanecerá hasta un valor alfa de 0
para que la imagen sea totalmente transparente, seguido del segundo degradado.
¡Más enmascaramiento!
Hasta ahora, sólo hemos estado trabajando con dos degradados lineales en nuestra máscara y dos propiedades personalizadas. Para crear un efecto de mosaico o fragmentación, necesitaremos muchos más mosaicos, ¡y eso significa muchos más degradados y muchas propiedades personalizadas!
SCSS hace que esto sea una tarea bastante trivial, así que a eso recurriremos para escribir estilos de ahora en adelante. Como vimos en el primer ejemplo, tenemos una especie de matriz de mosaicos. Podemos pensar en ellas como filas y columnas, así que definimos dos variables SCSS $x
y $y
las representamos.
Propiedades personalizadas
Necesitaremos @property
definiciones para cada uno. Sin embargo, nadie quiere escribir todo eso a mano, así que permitamos que SCSS haga el trabajo pesado por nosotros ejecutando nuestras propiedades a través de un bucle:
@for $i from 0 through ($x - 1) { @for $j from 0 through ($y - 1) { @property --c-#{$i}-#{$j} { syntax: "number"; initial-value: 1; inherits: false; } }}
Luego hacemos que todos pasen al 0
cursor:
img:hover { @for $i from 0 through ($x - 1) { @for $j from 0 through ($y - 1) { --c-#{$i}-#{$j}: 0; } }}
degradados
Vamos a escribir un @mixin
que nos los genere:
@mixin image() { $all_t: (); // Transition $all_m: (); // Mask @for $i from 0 through ($x - 1) { @for $j from 0 through ($y - 1) { $all_t: append($all_t, --c-#{$i}-#{$j} transition($i,$j), comma); $all_m: append($all_m, linear-gradient(rgba(0,0,0,var(--c-#{$i}-#{$j})) 0 0) calc(#{$i}*100%/(#{$x} - 1)) calc(#{$j}*100%/(#{$y} - 1)), comma); } } transition: $all_t; mask: $all_m;}
Todas nuestras capas de máscara tienen el mismo tamaño, por lo que solo necesitamos una propiedad para esto, dependiendo de las variables y $x
y :$y
calc()
mask-size: calc(100%/#{$x}) calc(100%/#{$y})
Es posible que también hayas notado esta línea:
$all_t: append($all_t, --c-#{$i}-#{$j} transition($i,$j), comma);
Dentro de la misma mezcla, también estamos generando la transition
propiedad que contiene todas las propiedades personalizadas previamente definidas.
Finalmente, generamos una duración/retraso diferente para cada propiedad, gracias a la random()
función en SCSS.
@function transition($i,$j) { @return $s*random()+s $s*random()+s;}
Ahora todo lo que tenemos que hacer es ajustar las variables $x
y $y
para controlar la granularidad de nuestra fragmentación.
Jugando con las animaciones
También podemos cambiar la configuración aleatoria para considerar diferentes tipos de animaciones.
En el código anterior, definí la transition()
función como se muestra a continuación:
// Uncomment one to use it@function transition($i,$j) { // @return (($s*($i+$j))/($x+$y))+s (($s*($i+$j))/($x+$y))+s; /* diagonal */ // @return (($s*$i)/$x)+s (($s*$j)/$y)+s; /* left to right */ // @return (($s*$j)/$y)+s (($s*$i)/$x)+s; /* top to bottom */ // @return ($s*random())+s (($s*$j)/$y)+s; /* top to bottom random */ @return ($s*random())+s (($s*$i)/$y)+s; /* left to right random */ // @return ($s*random())+s (($s*($i+$j))/($x+$y))+s; /* diagonal random */ // @return ($s*random())+s ($s*random())+s; /* full random*/}
Ajustando la fórmula, podemos obtener diferentes tipos de animación. Simplemente descomente el que desea utilizar. Esta lista no es exhaustiva; podemos tener cualquier combinación considerando más foros. (Te dejaré imaginar lo que es posible si agregamos funciones matemáticas avanzadas, como sin()
, sqrt()
etc.)
Jugando con los degradados
Todavía podemos jugar con nuestro código ajustando el degradado para que, en lugar de animar el valor alfa, animemos las paradas de color. Nuestro gradiente se verá así:
linear-gradient(white var(--c-#{$i}-#{$j}),transparent 0)
Luego animamos la variable de 100%
a 0%
. Y bueno, no tenemos que ceñirnos a gradientes lineales. ¿Por qué no radiales?
Al igual que la transición, podemos definir cualquier tipo de gradiente que queramos: ¡las combinaciones son infinitas!
Jugando con la superposición
Introduzcamos otra variable para controlar la superposición entre nuestras máscaras de degradado. Esta variable se establecerá mask-size
así:
calc(#{$o}*100%/#{$x}) calc(#{$o}*100%/#{$y})
No hay superposición si es igual a 1
. Si es más grande, entonces obtenemos una superposición. Esto nos permite hacer aún más tipos de animaciones:
¡Eso es todo!
Todo lo que tenemos que hacer es encontrar la combinación perfecta entre variables y fórmulas para crear efectos de fragmentación de imágenes sorprendentes y locos.
Deja un comentario