Explorando @property y sus poderes de animación

Eh, ¿qué es @property
? ¡Es una nueva característica de CSS! Te da superpoderes. No es broma, hay cosas que @property
se pueden hacer y que desbloquean cosas en CSS que nunca antes habíamos podido hacer.
Si bien todo @property
es interesante, quizás lo más interesante es que proporciona una forma de especificar un tipo para propiedades CSS personalizadas. Un tipo proporciona más información contextual al navegador, y eso da como resultado algo interesante: ¡ podemos darle al navegador la información que necesita para realizar la transición y animar esas propiedades!
Pero antes de que nos entusiasmemos demasiado con esto, vale la pena señalar que el soporte no existe del todo. Tal como está actualmente en el momento de escribir este artículo, @property
es compatible con Chrome y, por extensión, con Edge. Necesitamos estar atentos a la compatibilidad del navegador para cuando podamos usarlo en otros lugares, como Firefox y Safari.
En primer lugar, obtenemos verificación de tipo.
@property --spinAngle { /* An initial value for our custom property */ initial-value: 0deg; /* Whether it inherits from parent set values or not */ inherits: false; /* The type. Yes, the type. You thought TypeScript was cool */ syntax: 'angle';}@keyframes spin { to { --spinAngle: 360deg; }}
¡Así es! Escriba la comprobación en CSS. Es como crear nuestra propia mini especificación CSS. Sí ese es un ejemplo simple. Consulta todos los distintos tipos que tenemos a nuestra disposición:
length
number
percentage
length-percentage
color
image
url
integer
angle
time
resolution
transform-list
transform-function
custom-ident
(una cadena de identificación personalizada)
Antes de todo esto, es posible que hayamos dependido del uso de “trucos” para potenciar animaciones con propiedades personalizadas.
Las variables CSS son increíbles, ¿verdad? Pero a menudo se pasa por alto el poder del alcance. Por ejemplo, tome esta demostración, 3 animaciones diferentes pero solo 1 animación definida Eso significa animaciones dinámicas https://t.co/VN02NlC4G8 vía @CodePen #CSS #animation #webdev #webdesign #coding pic.twitter.com/ig8baxr7F3
– Jhey (@jh3yy) 5 de noviembre de 2019
¿Qué cosas interesantes podemos hacer entonces? Echemos un vistazo para despertar nuestra imaginación.
animemos el color
¿Cómo podrías animar un elemento ya sea a través de una serie de colores o entre ellos? Soy un gran defensor del espacio de color HSL, que divide las cosas en números bastante comprensibles: tono, saturación y luminosidad, respectivamente.
Animar un tono parece algo divertido que podemos hacer. ¿Qué es colorido? ¡Un arcoíris! Hay una variedad de maneras en que podríamos hacer un arcoíris. Aquí hay uno:
En este ejemplo, las propiedades personalizadas de CSS se configuran en las diferentes bandas del arcoíris y se utilizan :nth-child()
para limitarlas a bandas individuales. Cada banda también tiene un --index
juego para ayudar con el tamaño.
Para animar esas bandas, podríamos usar eso --index
para establecer algunos retrasos de animación negativos, pero luego usar la misma clave de animación de fotograma para alternar entre los tonos.
.rainbow__band { border-color: hsl(var(--hue, 10), 80%, 50%); animation: rainbow 2s calc(var(--index, 0) * -0.2s) infinite linear;}@keyframes rainbow { 0%, 100% { --hue: 10; } 14% { --hue: 35; } 28% { --hue: 55; } 42% { --hue: 110; } 56% { --hue: 200; } 70% { --hue: 230; } 84% { --hue: 280; }}
Eso podría funcionar bien si desea un efecto “escalonado”. Pero esos pasos de fotogramas clave no son particularmente precisos. Usó pasos de 14%
como un salto brusco.
Podríamos animar el border-color
y eso haría el trabajo. Pero todavía tendremos un problema de cálculo de pasos de fotogramas clave. Y necesitamos escribir mucho CSS para lograrlo:
@keyframes rainbow { 0%, 100% { border-color: hsl(10, 80%, 50%); } 14% { border-color: hsl(35, 80%, 50%); } 28% { border-color: hsl(55, 80%, 50%); } 42% { border-color: hsl(110, 80%, 50%); } 56% { border-color: hsl(200, 80%, 50%); } 70% { border-color: hsl(230, 80%, 50%); } 84% { border-color: hsl(280, 80%, 50%); }}
Ingresar @property
. Comenzamos definiendo una propiedad personalizada para el tono. Esto le dice al navegador que nuestra propiedad personalizada, --hue
será un número (no una cadena que parece un número):
@property --hue { initial-value: 0; inherits: false; syntax: 'number';}
Los valores de tono en HSL pueden ir de 0
a 360
. Comenzamos con un valor inicial de 0
. El valor no se va a heredar. Y nuestro valor, en este caso, es un número. La animación es tan sencilla como:
@keyframes rainbow { to { --hue: 360; }}
Sí, ese es el billete:
Para que los puntos de partida sean precisos, podríamos jugar con retrasos para cada banda. Esto nos da una gran flexibilidad. Por ejemplo, podemos aumentarlo animation-duration
y obtenemos un ciclo lento. Juega con la velocidad en esta demostración.
Puede que no sea el ejemplo más “alocado”, pero creo que animar el color tiene algunas oportunidades divertidas cuando utilizamos espacios de color que hacen un uso lógico de los números. Animar a través de la rueda de colores antes requería cierta complejidad. Por ejemplo, genere fotografías clave con un preprocesador, como Stylus:
@keyframes party for $frame in (0..100) {$frame * 1%} background 'hsl(%s, 65%, 40%)' % ($frame * 3.6)
Hacemos esto simplemente porque el navegador no lo entiende. Considere que pasar de 0 a 360 en la rueda de colores es una transición instantánea porque ambos valores hsl muestran el mismo color.
@keyframes party { from { background: hsl(0, 80%, 50%); } to { background: hsl(360, 80%, 50%); }}
Los fotogramas claves son los mismos, por lo que el navegador asume que la animación permanece en el mismo background
valor cuando lo que realmente queremos es que el navegador recorra todo el espectro de tonos, comenzando en un valor y terminando en ese mismo valor después de realizar. los movimientos. .
Piense en todas las otras oportunidades que tenemos aquí. Podemos:
- animar la saturación
- utilizar diferentes flexibilizaciones
- animar la ligereza
- Intentar
rgb()
- Pruebe grados
hsl()
y declara nuestro tipo de propiedad personalizada comoangle
¡Lo bueno es que podemos compartir ese valor animado entre elementos con alcance! Considere este botón. El borde y la sombra se animan a través de la rueda de colores al pasar el ratón.
Animar el color me hace pensar… ¡guau!
Numeración directa
Debido a que podemos definir tipos de números, como integer
y number
, eso significa que también podemos animar números en lugar de usarlos como parte de otra cosa. Carter Li de hecho escribió un artículo sobre esto aquí en CSS-Tricks. El truco consiste en utilizar una integer
combinación con contadores CSS. Esto es similar a cómo podemos trabajar el contador en juegos de “CSS puro” como este.
El uso de counter
pseudoelementos y proporciona una forma de convertir un número en una cadena. Entonces podemos usar esa cadena para content
un pseudoelemento. Aquí están las partes importantes:
@property --milliseconds { inherits: false; initial-value: 0; syntax: 'integer';}.counter { counter-reset: ms var(--milliseconds); animation: count 1s steps(100) infinite;}.counter:after { content: counter(ms);}@keyframes count { to { --milliseconds: 100; }}
Lo que nos da algo como esto. Muy genial.
Vaya un poco más allá y obtendrá un cronómetro funcional hecho únicamente con CSS y HTML. ¡Haz clic en los botones! Lo bueno aquí es que esto realmente funciona como un temporizador. No sufrirá deriva. En cierto modo, puede ser más preciso que las soluciones de JavaScript que utilizamos a menudo, como setInterval
. Mire este fantástico vídeo del desarrollador de Google Chrome sobre los contadores de JavaScript.
¿Para qué otras cosas podrías usar números animados? ¿Quizás una cuenta regresiva?
Degradados animados
Ya conoces los unos, lineal, radial y cónico. ¿Alguna vez has estado en un lugar donde querías realizar una transición o animar las paradas de color? Bueno, @property
¡puedo hacer eso!
Considere un gradiente donde estamos creando algunas olas en una playa. Una vez que hayamos superpuesto algunas imágenes, podríamos hacer algo como esto.
body { background-image: linear-gradient(transparent 0 calc(35% + (var(--wave) * 0.5)), var(--wave-four) calc(75% + var(--wave)) 100%), linear-gradient(transparent 0 calc(35% + (var(--wave) * 0.5)), var(--wave-three) calc(50% + var(--wave)) calc(75% + var(--wave))), linear-gradient(transparent 0 calc(20% + (var(--wave) * 0.5)), var(--wave-two) calc(35% + var(--wave)) calc(50% + var(--wave))), linear-gradient(transparent 0 calc(15% + (var(--wave) * 0.5)), var(--wave-one) calc(25% + var(--wave)) calc(35% + var(--wave))), var(--sand);}
Están sucediendo bastantes cosas allí. Pero, para desglosarlo, estamos creando cada color con calc()
. Y en ese cálculo, sumamos el valor de --wave
. El buen truco aquí es que cuando animamos ese --wave
valor, todas las capas de ondas se mueven.
Este es todo el código que necesitábamos para que eso sucediera:
body { animation: waves 5s infinite ease-in-out;}@keyframes waves { 50% { --wave: 25%; }}
Sin el uso de @property
, nuestras olas oscilarían entre la marea alta y la baja. Pero con él conseguimos un agradable efecto frío como este.
Es emocionante pensar en otras interesantes oportunidades que tenemos al manipular imágenes. Como rotación. ¿O qué tal animar el ángulo de un conic-gradient
... pero, dentro de un border-image
. Bramus Van Damme hace un trabajo brillante al cubrir este concepto.
Analicémoslo creando un indicador de carga. Vamos a animar un ángulo y un tono al mismo tiempo. Podemos comenzar con dos propiedades personalizadas:
@property --angle { initial-value: 0deg; inherits: false; syntax: 'number';}@property --hue { initial-value: 0; inherits: false; syntax: 'angle';}
La animación actualizará el ángulo y el tono con una ligera pausa en cada iteración.
@keyframes load { 0%, 10% { --angle: 0deg; --hue: 0; } 100% { --angle: 360deg; --hue: 100; }}
Ahora apliquémoslo como parte border-image
de un elemento.
.loader { --charge: hsl(var(--hue), 80%, 50%); border-image: conic-gradient(var(--charge) var(--angle), transparent calc(var(--angle) * 0.5deg)) 30; animation: load 2s infinite ease-in-out;}
Muy genial.
Desafortunadamente, border-image
no juega bien con border-radius
. Pero podríamos usar un pseudoelemento detrás de esto. Combínelo con los trucos de animación numérica de antes y tendremos una animación de carga/carga completa. (Sí, cambia cuando llega al 100%).
Las transformaciones también son geniales
Un problema con la animación de transformaciones es la transición entre ciertas partes. Muchas veces termina rompiéndose o no luciendo como debería. Consideremos el ejemplo clásico del lanzamiento de una pelota. Queremos que vaya del punto A al punto B imitando el efecto de la gravedad.
Un intento inicial podría verse así
@keyframes throw { 0% { transform: translate(-500%, 0); } 50% { transform: translate(0, -250%); } 100% { transform: translate(500%, 0); }}
Pero pronto veremos que no se parece en nada a lo que queremos.
Antes, es posible que hayamos recurrido a elementos envolventes y los hayamos animado de forma aislada. Pero, con @property
, podemos animar los valores individuales de la transformación. Y todo en una línea de tiempo. Cambiemos la forma en que esto funciona definiendo propiedades personalizadas y luego estableciendo una transformación en la pelota.
@property --x { inherits: false; initial-value: 0%; syntax: 'percentage';}@property --y { inherits: false; initial-value: 0%; syntax: 'percentage';}@property --rotate { inherits: false; initial-value: 0deg; syntax: 'angle';}.ball { animation: throw 1s infinite alternate ease-in-out; transform: translateX(var(--x)) translateY(var(--y)) rotate(var(--rotate));}
Ahora, para nuestra animación, podemos componer la transformación que queremos con respecto a los fotogramas clave:
@keyframes throw { 0% { --x: -500%; --rotate: 0deg; } 50% { --y: -250%; } 100% { --x: 500%; --rotate: 360deg; }}
¿El resultado? El camino curvo que esperábamos. Y podemos hacer que se vea diferente dependiendo de las diferentes funciones de sincronización que usemos. Podríamos dividir la animación en tres formas y usar diferentes funciones de sincronización. Eso nos daría resultados diferentes según la forma en que se mueve la pelota.
Consideremos otro ejemplo en el que tenemos un automóvil que queremos conducir alrededor de un cuadrado con esquinas redondeadas.
Podemos utilizar un enfoque similar al que hicimos con la pelota:
@property --x { inherits: false; initial-value: -22.5; syntax: 'number';}@property --y { inherits: false; initial-value: 0; syntax: 'number';}@property --r { inherits: false; initial-value: 0deg; syntax: 'angle';}
El auto transform
está usando calculado con vmin
para mantener la capacidad de respuesta:
.car { transform: translate(calc(var(--x) * 1vmin), calc(var(--y) * 1vmin)) rotate(var(--r));}
Ahora puede escribir un recorrido extremadamente preciso cuadro por cuadro para el automóvil. Podríamos empezar con el valor de --x
.
@keyframes journey { 0%, 100% { --x: -22.5; } 25% { --x: 0; } 50% { --x: 22.5; } 75% { --x: 0; }}
El coche hace el recorrido correcto en el eje x.
Luego nos basamos en eso sumando el recorrido del eje y:
@keyframes journey { 0%, 100% { --x: -22.5; --y: 0; } 25% { --x: 0; --y: -22.5; } 50% { --x: 22.5; --y: 0; } 75% { --x: 0; --y: 22.5; }}
Bueno, eso no está del todo bien.
Introduzcamos algunos pasos adicionales en nuestro @keyframes
para suavizar las cosas:
@keyframes journey { 0%, 100% { --x: -22.5; --y: 0; } 12.5% { --x: -22.5; --y: -22.5; } 25% { --x: 0; --y: -22.5; } 37.5% { --y: -22.5; --x: 22.5; } 50% { --x: 22.5; --y: 0; } 62.5% { --x: 22.5; --y: 22.5; } 75% { --x: 0; --y: 22.5; } 87.5% { --x: -22.5; --y: 22.5; }}
Ah, mucho mejor ahora:
Todo lo que queda es la rotación del coche. Vamos con una ventana del 5% en las esquinas. No es preciso pero definitivamente muestra el potencial de lo que es posible:
@keyframes journey { 0% { --x: -22.5; --y: 0; --r: 0deg; } 10% { --r: 0deg; } 12.5% { --x: -22.5; --y: -22.5; } 15% { --r: 90deg; } 25% { --x: 0; --y: -22.5; } 35% { --r: 90deg; } 37.5% { --y: -22.5; --x: 22.5; } 40% { --r: 180deg; } 50% { --x: 22.5; --y: 0; } 60% { --r: 180deg; } 62.5% { --x: 22.5; --y: 22.5; } 65% { --r: 270deg; } 75% { --x: 0; --y: 22.5; } 85% { --r: 270deg; } 87.5% { --x: -22.5; --y: 22.5; } 90% { --r: 360deg; } 100% { --x: -22.5; --y: 0; --r: 360deg; }}
Y ahí lo tenemos, ¡un coche circulando por una plaza curva! Sin envoltorios, sin necesidad de matemáticas complejas. Y lo compusimos todo con propiedades personalizadas.
Potenciando una escena completa con variables
Hemos visto algunas @property
posibilidades bastante interesantes hasta ahora, pero juntar todo lo que hemos visto aquí puede llevar las cosas a otro nivel. Por ejemplo, podemos potenciar escenas enteras con solo unas pocas propiedades personalizadas.
Considere el siguiente concepto para una página 404. Dos propiedades registradas alimentan las diferentes partes móviles. Tenemos un degradado en movimiento recortado con -webkit-background-clip
. La sombra se mueve leyendo los valores de las propiedades. Y balanceamos otro elemento para el efecto de luz.
¡Eso es todo!
Es emocionante pensar en qué tipos de cosas podemos hacer con la capacidad de definir tipos @property
. Al brindarle al navegador contexto adicional sobre una propiedad personalizada, podemos volvernos locos de maneras que antes no podíamos con cadenas básicas.
¿Qué ideas tienes para los otros tipos? El tiempo y la resolución generarían transiciones interesantes, aunque admito que no pude hacer que funcionaran de la manera que esperaba. url
También podría ser ordenado, como quizás hacer una transición entre una variedad de fuentes como lo hace normalmente un carrusel de imágenes. ¡Solo estamos haciendo una lluvia de ideas aquí!
Espero que este vistazo rápido @property
te inspire a verlo y crear tus propias demostraciones increíbles. Espero ver lo que haces. De hecho, ¡compártelos conmigo aquí en los comentarios!
Deja un comentario