Con una actualización de Safari muy reciente, la API de animaciones web (WAAPI) ahora es compatible sin una bandera en todos los navegadores modernos (excepto IE). Aquí tienes un práctico lápiz en el que puedes comprobar qué funciones admite tu navegador. WAAPI es una buena forma de hacer animación (que debe hacerse en JavaScript) porque es nativo, lo que significa que no requiere bibliotecas adicionales para funcionar. Si es completamente nuevo en WAAPI, aquí tiene una muy buena introducción de Dan Wilson.
Uno de los enfoques más eficientes de la animación es FLIP. FLIP requiere un poco de JavaScript para hacer lo suyo.
Echemos un vistazo a la intersección de usar WAAPI, FLIP e integrar todo eso en React. Pero comenzaremos sin React primero, luego llegaremos a eso.
FLIP y WAAPI
¡Las animaciones FLIP son mucho más fáciles con WAAPI!
Actualización rápida sobre FLIP: La gran idea es que coloques el elemento donde quieres que termine primero. A continuación, aplique transformaciones para moverlo a la posición inicial. Luego, desactive esas transformaciones.
Animar transformaciones es súper eficiente, por lo que FLIP es súper eficiente. Antes de WAAPI, teníamos que manipular directamente los estilos de los elementos para configurar transformaciones y esperar a que el siguiente marco lo desarme / invierta:
// FLIP Before the WAAPI
el.style.transform = `translateY(200px)`;
requestAnimationFrame(() => {
el.style.transform = '';
});
Muchas bibliotecas se basan en este enfoque. Sin embargo, existen varios problemas con esto:
- Todo se siente como un gran truco.
- Es extremadamente difícil invertir la animación FLIP. Si bien las transformaciones CSS se invierten “gratis” una vez que se elimina una clase, este no es el caso aquí. Iniciar un nuevo FLIP mientras se está ejecutando uno anterior puede causar fallas. La inversión requiere analizar una matriz de transformación con
getComputedStyles
y usándolo para calcular las dimensiones actuales antes de configurar una nueva animación. - Las animaciones avanzadas son casi imposibles. Por ejemplo, para evitar distorsionar a los hijos de un padre escalado, necesitamos tener acceso al valor actual de la escala en cada cuadro. Esto solo se puede hacer analizando la matriz de transformación.
- Hay muchas trampas en el navegador. Por ejemplo, a veces hacer que una animación FLIP funcione sin problemas en Firefox requiere llamar
requestAnimationFrame
dos veces:
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.style.transform = '';
});
});
No tenemos ninguno de estos problemas cuando se usa WAAPI. La marcha atrás se puede hacer sin dolor con el reverse
La función de contrapeso de los niños también es posible. Y cuando hay un error, es fácil identificar al culpable exacto, ya que solo trabajamos con funciones simples, como animate
y reverse
, en lugar de peinar cosas como el requestAnimationFrame
Acercarse.
Aquí está el esquema de la versión WAAPI:
el.classList.toggle('someclass');
const keyframes = /* Calculate the size/position diff */;
el.animate(keyframes, 2000);
FLIP y reacciona
Para comprender cómo funcionan las animaciones FLIP en React, es importante saber cómo y, lo que es más importante, por qué funcionan en JavaScript simple. Recuerde la anatomía de una animación FLIP:
Todo lo que tiene un fondo violeta debe suceder antes del paso de “pintura” de renderizado. De lo contrario, veríamos un destello de nuevos estilos por un momento que no es bueno. Las cosas se complican un poco más en React ya que todas las actualizaciones de DOM están hechas por nosotros.
La magia de las animaciones FLIP es que un elemento se transforma antes de que el navegador tenga la oportunidad de pintar. Entonces, ¿cómo sabemos el momento “antes de pintar” en React?
Satisfacer la useLayoutEffect
gancho. Si incluso te preguntaste para qué es… ¡esto es! Todo lo que pasamos en esta devolución de llamada ocurre sincrónicamente después de las actualizaciones de DOM pero antes de pintar. En otras palabras, ¡este es un gran lugar para configurar un FLIP!
Hagamos algo para lo que la técnica FLIP sea muy buena: animando la posición DOM. No hay nada que CSS pueda hacer si queremos animar cómo se mueve un elemento de una posición DOM a otra. (Imagínese completar una tarea en una lista de tareas pendientes y moverla a la lista de tareas “completadas”, como cuando hace clic en los elementos del lápiz a continuación).
Veamos el ejemplo más simple. Al hacer clic en cualquiera de los dos cuadrados del siguiente lápiz, se intercambian posiciones. Sin FLIP, sucedería instantáneamente.
Están sucediendo muchas cosas allí. Observe cómo ocurre todo el trabajo dentro de las devoluciones de llamada del gancho del ciclo de vida: useEffect
y useLayoutEffect
. Lo que lo hace un poco confuso es que la línea de tiempo de nuestra animación FLIP no es obvia solo a partir del código, ya que ocurre a través de dos Reaccionar renders. Aquí está la anatomía de una animación de React FLIP para mostrar el orden diferente de las operaciones:
A pesar de que useEffect
siempre corre tras useLayoutEffect
y después de la pintura del navegador, es importante almacenar en caché la posición y el tamaño del elemento después del primer renderizado. No tendremos la oportunidad de hacerlo en el segundo render porque useLayoutEffect
se ejecuta después de todas las actualizaciones de DOM. Pero el procedimiento es esencialmente el mismo que con las animaciones FLIP de vainilla.
Advertencias
Como la mayoría de las cosas, hay algunas advertencias a tener en cuenta al trabajar con FLIP en React.
Manténgalo por debajo de 100 ms
Una animación FLIP es un cálculo. El cálculo lleva tiempo y antes de que pueda mostrar esa transformación fluida de 60 fps, debe trabajar bastante. La gente no notará un retraso si es inferior a 100 ms, así que asegúrese de que todo esté por debajo de eso. La pestaña Rendimiento de DevTools es un buen lugar para comprobarlo.
Renders innecesarios
No podemos usar useState para almacenar en caché el tamaño, las posiciones y los objetos de animación porque cada setState
provocará un renderizado innecesario y ralentizará la aplicación. Incluso puede causar errores en el peor de los casos. Intenta usar useRef
en su lugar, piense en él como un objeto que puede ser mutado sin renderizar nada.
Diseño de paliza
Evite activar repetidamente el diseño del navegador. En el contexto de las animaciones FLIP, eso significa evitar recorrer elementos y leer su posición con getBoundingClientRect
e inmediatamente animarlos con animate. Lote “lee” y “escribe” siempre que sea posible. Esto permitirá animaciones extremadamente fluidas.
Cancelación de animación
Intente hacer clic aleatoriamente en los cuadrados en la demostración anterior mientras se mueven, y luego nuevamente después de que se detengan. Verá fallas. En la vida real, los usuarios interactuarán con los elementos mientras se mueven, por lo que vale la pena asegurarse de que se cancelen, pausen y actualicen sin problemas.
Sin embargo, no todas las animaciones se pueden revertir con reverse
. A veces, queremos que se detengan y luego se muevan a una nueva posición (como cuando se baraja aleatoriamente una lista de elementos). En este caso, necesitamos:
- obtener un tamaño / posición de un elemento en movimiento
- terminar la animación actual
- calcular las nuevas diferencias de tamaño y posición
- iniciar una nueva animación
En React, esto puede ser más difícil de lo que parece. Perdí mucho tiempo luchando con eso. El objeto de animación actual debe almacenarse en caché. Una buena forma de hacerlo es crear un Map
para obtener la animación mediante una identificación. Luego, necesitamos obtener el tamaño y la posición del elemento móvil. Hay dos maneras de hacerlo:
- Utilice un componente de función: Simplemente recorra cada elemento animado directamente en el cuerpo de la función y almacene en caché las posiciones actuales.
- Utilice un componente de clase: Utilizar el
getSnapshotBeforeUpdate
método del ciclo de vida.
De hecho, los documentos oficiales de React recomiendan usar getSnapshotBeforeUpdate
“Porque puede haber retrasos entre los ciclos de vida de la fase de” renderizado “(como render
) y ciclos de vida de la fase de “compromiso” (como getSnapshotBeforeUpdate
y componentDidUpdate
). ” Sin embargo, todavía no existe una contraparte de gancho de este método. Descubrí que usar el cuerpo del componente de función es lo suficientemente bueno.
No luches contra el navegador
Lo he dicho antes, pero evite luchar contra el navegador e intente hacer que las cosas sucedan como lo haría el navegador. Si necesitamos animar un cambio de tamaño simple, entonces considere si CSS sería suficiente (p. Ej. transform: scale()
). Descubrí que las animaciones FLIP se usan mejor donde los navegadores realmente no pueden ayudar:
- Animando el cambio de posición del DOM (como hicimos arriba)
- Compartir animaciones de diseño
El segundo es una versión más complicada del primero. Hay dos elementos DOM que actúan y se ven como uno cambiando su posición (mientras que otro está desmontado / oculto). Este truco permite algunas animaciones geniales. Por ejemplo, esta animación está hecha con una biblioteca que construí llamada react-easy-flip
que utiliza este enfoque:
Bibliotecas
Hay bastantes bibliotecas que facilitan las animaciones FLIP en React y abstraen el texto estándar. Los que actualmente se mantienen activamente incluyen: react-flip-toolkit
y mío, react-easy-flip
.
Si no te importa algo más pesado pero capaz de animaciones más generales, echa un vistazo framer-motion
. ¡También hace fantásticas animaciones de diseño compartido! Hay un video que investiga esa biblioteca.
Recursos y referencias
- Animando lo inanimable de Josh W. Comeau
- Cree animaciones de expansión y colapso de alto rendimiento de Paul Lewis y Stephen McGruer
- La magia dentro del movimiento mágico de Matt Perry
- Usar variables CSS animadas de JavaScript, tuiteado por @keyframers
- Vista interior del navegador web moderno (parte 3) por Mariko Kosaka
- Construyendo una animación de interfaz de usuario compleja en React, simplemente por Alex Holachek
- Animación de diseños con la técnica FLIP por David Khourshid
- Animaciones suaves con React Hooks, nuevamente por Kirill Vasiltsov
- Transición de elementos compartidos con React Hooks de Jayant Bhawal