Dar sentido a reaccionar-primavera
- reaccionar-primavera redux
- muelles
- Aquí está esa demostración
- Retazos
- Algunas cosas a considerar con estos sandboxes
- Ahora construyamos algo real.
- Animando nuestro modal
- Configuración básica
- Animando el tamaño modal
- Animando los resultados
- Animando libros seleccionados (fuera)
- Animar libros seleccionados (en)
La animación es una de las cosas más difíciles de hacer bien con React. En esta publicación, intentaré brindar la introducción a reaccionar-spring que desearía haber tenido cuando comencé y luego profundizaré en algunos casos de uso interesantes. Si bien react-spring no es la única biblioteca de animación para React, es una de las más populares (y mejores).
Usaré la versión 9 más reciente que, al momento de escribir este artículo, se encuentra en estado de candidato a lanzamiento. Si no está completamente publicado cuando lea esto, asegúrese de instalarlo con react-spring@next
. Por lo que he visto y por lo que me dijo el responsable de mantenimiento, el código es increíblemente estable. El único problema que he visto es un pequeño error cuando se usa con el modo concurrente, que se puede rastrear en el repositorio de GitHub.
reaccionar-primavera redux
Antes de llegar a algunos casos de uso de aplicaciones interesantes, hagamos una introducción vertiginosa. Cubriremos resortes, animación de altura y luego transiciones. Tendré una demostración funcional al final de esta sección, así que no se preocupe si las cosas se vuelven un poco confusas en el camino.
muelles
Consideremos el canónico “Hola mundo” de la animación: la aparición y desaparición gradual del contenido. Detengámonos por un momento y consideremos cómo activaríamos y desactivaríamos la opacidad sin ningún tipo de animación. Se vería algo como esto:
export default function App() { const [showing, setShowing] = useState(false); return ( div div style={{ opacity: showing ? 1 : 0 }} This content will fade in and fade out /div button onClick={() = setShowing(val = !val)}Toggle/button hr / /div );}
Fácil, pero aburrido. ¿Cómo animamos la opacidad cambiante? ¿No sería bueno si pudiéramos establecer mediante declaración la opacidad que queremos según el estado, como lo hicimos anteriormente, excepto que esos valores se animaran sin problemas? Eso es lo que hace reaccionar-primavera. Piense en reaccionar-spring como nuestro intermediario que lava nuestros valores de estilo cambiantes, para que pueda producir la transición suave entre los valores de animación que queremos. Como esto:
const [showA, setShowA] = useState(false);
const fadeStyles = useSpring({ config: { ...config.stiff }, from: { opacity: 0 }, to: { opacity: showA ? 1 : 0 }});
Especificamos nuestros valores de estilo iniciales con from
y especificamos el valor actual en la to
sección, según nuestro estado actual. El valor de retorno, fadeStyles
contiene los valores de estilo reales que aplicamos a nuestro contenido. Sólo hay una última cosa que necesitamos...
Podrías pensar que podrías hacer esto:
div style={fadeStyles}
…y listo. Pero en lugar de usar un div normal, necesitamos usar un div de reacción-spring que se crea a partir de la exportación animada. Puede parecer confuso, pero lo único que realmente significa es esto:
animated.div style={fadeStyles}
Y eso es.
Animando altura
Dependiendo de lo que estemos animando, es posible que deseemos que el contenido se deslice hacia arriba y hacia abajo, desde una altura cero hasta su tamaño completo, para que el contenido circundante se ajuste y fluya suavemente en su lugar. Es posible que desees que podamos copiar lo anterior, con la altura yendo de cero a automática, pero, por desgracia, no puedes animar a la altura automática. Eso no funciona con CSS básico ni con reaccionar-spring. En cambio, necesitamos saber la altura real de nuestro contenido y especificarla en la to
sección de nuestro resorte.
Necesitamos tener la altura de cualquier contenido arbitrario sobre la marcha para poder pasar ese valor a reaccionar-primavera. Resulta que la plataforma web tiene algo diseñado específicamente para eso: un archivo ResizeObserver
. ¡Y el soporte es realmente bastante bueno! Como usamos React, por supuesto incluiremos ese uso en un gancho. Así es como se ve el mío:
export function useHeight({ on = true /* no value means on */ } = {} as any) { const ref = useRefany(); const [height, set] = useState(0); const heightRef = useRef(height); const [ro] = useState( () = new ResizeObserver(packet = { if (ref.current heightRef.current !== ref.current.offsetHeight) { heightRef.current = ref.current.offsetHeight; set(ref.current.offsetHeight); } }) ); useLayoutEffect(() = { if (on ref.current) { set(ref.current.offsetHeight); ro.observe(ref.current, {}); } return () = ro.disconnect(); }, [on, ref.current]); return [ref, height as any];}
Opcionalmente podemos proporcionar un on
valor que active y desactive la medición (lo que será útil más adelante). Cuando on
es true
, le decimos a nuestro ResizeObserver
que observe el contenido. Devolvemos una referencia que debe aplicarse a cualquier contenido que queramos medir, así como a la altura actual.
Veámoslo en acción.
const [heightRef, height] = useHeight();const slideInStyles = useSpring({ config: { ...config.stiff }, from: { opacity: 0, height: 0 }, to: { opacity: showB ? 1 : 0, height: showB ? height : 0 }});
useHeight
nos da una referencia y un valor de altura del contenido que estamos midiendo, que pasamos a nuestro resorte. Luego aplicamos la referencia y aplicamos los estilos de altura.
animated.div style={{ ...slideInStyles, overflow: "hidden" }} div ref={heightRef} This content will fade in and fade out with sliding /div/animated.div
Ah, y no olvides agregarlo overflow: hidden
al recipiente. Eso nos permite contener adecuadamente nuestros valores de altura de ajuste.
Animar transiciones
Por último, veamos cómo agregar y eliminar elementos animados desde y hacia el DOM. Ya sabemos cómo animar el cambio de valores de un elemento que existe y permanece en el DOM, pero para animar la adición o eliminación de elementos, necesitamos un nuevo gancho: useTransition.
Si ha usado reaccionar-spring antes, este es uno de los pocos lugares donde la versión 9 tiene grandes cambios en su API. Vamos a ver.
Para animar una lista de elementos, como este:
const [list, setList] = useState([]);
…declararemos nuestra función de transición así:
const listTransitions = useTransition(list, { config: config.gentle, from: { opacity: 0, transform: "translate3d(-25%, 0px, 0px)" }, enter: { opacity: 1, transform: "translate3d(0%, 0px, 0px)" }, leave: { opacity: 0, height: 0, transform: "translate3d(25%, 0px, 0px)" }, keys: list.map((item, index) = index)});
Como mencioné anteriormente, el valor de retorno listTransitions
es una función. reaccionar-spring está rastreando la matriz de la lista, controlando los elementos que se agregan y eliminan. Llamamos a la listTransitions
función, proporcionamos una devolución de llamada que acepta un solo styles
objeto y un solo elemento, y reaccionar-spring la llamará para cada elemento en la lista con los estilos correctos, en función de si se agregó recientemente, se eliminó recientemente o simplemente se encuentra en el lista.
Tenga en cuenta la sección de claves: esto nos permite decirle a reaccionar-spring cómo identificar objetos en la lista. En este caso, decidí decirle a reaccionar-spring que el índice de un elemento en la matriz define de forma única ese elemento. Normalmente, esto sería una idea terrible, pero por ahora nos permite ver la función en acción. En la demostración siguiente, el botón "Agregar elemento" agrega un elemento al final de la lista cuando se hace clic, y el botón "Eliminar último elemento" elimina el elemento agregado más recientemente de la lista. Entonces, si escribe en el cuadro de entrada y luego presiona rápidamente el botón Agregar y luego el botón Eliminar, verá que el mismo elemento comienza a ingresar suavemente y luego, inmediatamente, desde cualquier etapa de la animación en la que se encuentre, comienza a salir. Por el contrario, si agrega un elemento y luego presiona rápidamente el botón Eliminar y el botón Agregar, el mismo elemento comenzará a deslizarse, luego se detendrá abruptamente en su lugar y se deslizará de regreso a donde estaba.
Aquí está esa demostración
¡Vaya, fueron un montón de palabras! Aquí hay una demostración funcional que muestra todo lo que acabamos de cubrir en acción.
Ver demostración
Retazos
¿Notaste cómo, cuando deslizas el contenido hacia abajo en la demostración, rebota en su lugar, como... un resorte? De ahí proviene el nombre: reaccionar-primavera interpola nuestros valores cambiantes utilizando la física de resortes. No simplemente corta el valor que cambia en N deltas iguales que aplica sobre N retrasos iguales. En cambio, utiliza un algoritmo más sofisticado que produce ese efecto primaveral, que parecerá más natural.
El algoritmo de resorte es totalmente configurable y viene con una serie de ajustes preestablecidos que puede sacar del estante; la demostración anterior utiliza los ajustes preestablecidos stiff
y gentle
. Consulte los documentos para obtener más información.
Observe también cómo estoy animando valores dentro de translate3d
valores. Como puede ver, la sintaxis no es la más concisa, por lo que reaccionar-spring proporciona algunos atajos. Hay documentación sobre esto, pero durante el resto de esta publicación continuaré usando la sintaxis completa sin atajos para mantener las cosas lo más claras posible.
Cerraré esta sección llamando la atención sobre el hecho de que, cuando deslizas el contenido hacia arriba en la demostración de arriba, probablemente verás que el contenido debajo se pone un poco nervioso al final. Este es el resultado del mismo efecto de rebote. Se ve nítido cuando el contenido rebota hacia abajo y a su posición, pero menos cuando deslizamos el contenido hacia arriba. Estén atentos para ver cómo podemos apagarlo. (Spoiler, es la clamp
propiedad).
Algunas cosas a considerar con estos sandboxes
Code Sandbox utiliza recarga en caliente. A medida que cambia el código, los cambios generalmente se reflejan inmediatamente. Esto es genial, pero puede causar estragos en las animaciones. Si comienza a modificar y luego ve un comportamiento extraño y aparentemente incorrecto, intente actualizar la zona de pruebas.
Los otros sandboxes en esta publicación utilizarán un modal. Por razones que no he podido entender, cuando el modal está abierto, no podrás modificar ningún código: el modal se niega a ceder el foco. Por lo tanto, asegúrese de cerrar el modal antes de intentar realizar cambios.
Ahora construyamos algo real.
Esos son los componentes básicos de reaccionar-spring. Usémoslos para construir algo más interesante. Se podría pensar, teniendo en cuenta todo lo anterior, que reaccionar-spring es bastante sencillo de usar. Desafortunadamente, en la práctica, puede resultar complicado descubrir algunas cosas sutiles que hay que hacer bien. El resto de esta publicación profundizará en muchos de estos detalles.
Las publicaciones de blog anteriores que he escrito han estado relacionadas de alguna manera con el proyecto paralelo de mi lista de libros. Este no será diferente: no es una obsesión, es solo que ese proyecto tiene un punto final GraphQL disponible públicamente y una gran cantidad de código existente que se puede aprovechar, lo que lo convierte en un objetivo obvio.
Creemos una interfaz de usuario que le permita abrir un modal y buscar libros. Cuando lleguen los resultados, puede agregarlos a una lista actualizada de libros seleccionados que se muestran debajo del modal. Cuando haya terminado, puede cerrar el modal y hacer clic en un botón para buscar libros similares a la selección.
Comenzaremos con una interfaz de usuario funcional y luego animaremos las piezas paso a paso, incluyendo demostraciones interactivas a lo largo del camino.
Si está realmente ansioso por ver cómo será el resultado final, o si ya está familiarizado con reaccionar-spring y quiere ver si estoy cubriendo algo que aún no sabe, aquí está (no lo hará). No gano ningún premio de diseño, lo sé muy bien). El resto de esta publicación cubrirá el viaje para llegar a ese estado final, paso a paso.
Animando nuestro modal
Comencemos con nuestro modal. Antes de comenzar a agregar cualquier tipo de datos, hagamos que nuestro modal se anime bien. Así es como se ve un modal básico no animado. Estoy usando la interfaz de usuario Reach de Ryan Florence (específicamente el componente modal), pero la idea será la misma sin importar lo que uses para construir tu modal. Nos gustaría que nuestro fondo desaparezca y también realizar la transición de nuestro contenido modal.
Dado que un modal se representa condicionalmente en función de algún tipo de propiedad "abierta", usaremos el useTransition
gancho. Ya estaba envolviendo el modal Reach UI con mi propio componente modal y renderizando nada o el modal real basado en la isOpen
propiedad. Solo necesitamos pasar por el gancho de transición para que se anime.
Así es como se ve el gancho de transición:
const modalTransition = useTransition(!!isOpen, { config: isOpen ? { ...config.stiff } : { duration: 150 }, from: { opacity: 0, transform: `translate3d(0px, -10px, 0px)` }, enter: { opacity: 1, transform: `translate3d(0px, 0px, 0px)` }, leave: { opacity: 0, transform: `translate3d(0px, 10px, 0px)` }});
No hay demasiadas sorpresas aquí. Queremos atenuar las cosas y proporcionar una ligera transición vertical según si el modal está activo o no. La pieza extraña es esta:
config: isOpen ? { ...config.stiff } : { duration: 150 },
Solo quiero usar la física de primavera si el modal se está abriendo. La razón de esto, al menos en mi experiencia, es que cuando cierras el modal, el fondo tarda demasiado en desaparecer por completo, lo que deja la interfaz de usuario subyacente no interactiva durante demasiado tiempo. Entonces, cuando el modal se abra, rebotará muy bien en su lugar con la física del resorte, y cuando se cierre, desaparecerá rápidamente en 150 ms.
Y, por supuesto, representaremos nuestro contenido a través de la función de transición que devuelve nuestro gancho. Observe que estoy quitando el estilo de opacidad del objeto de estilos para aplicarlo al fondo y luego aplicando todos los estilos de animación al contenido modal real.
return modalTransition( (styles, isOpen) = isOpen ( AnimatedDialogOverlay allowPinchZoom={true} initialFocusRef={focusRef} onDismiss={onHide} isOpen={isOpen} style={{ opacity: styles.opacity }} AnimatedDialogContent style={{ border: "4px solid hsla(0, 0%, 0%, 0.5)", borderRadius: 10, maxWidth: "400px", ...styles }} div div StandardModalHeader caption={headerCaption} onHide={onHide} / {children} /div /div /AnimatedDialogContent /AnimatedDialogOverlay ));
Ver demostración
Configuración básica
Comencemos con el caso de uso que describí anteriormente. Si sigues las demostraciones, aquí tienes una demostración completa de todo funcionando, pero sin animación. Abra el modal y busque cualquier cosa (siéntase libre de presionar Enter
en el cuadro de texto vacío). Debería acceder a mi punto final GraphQL y obtener resultados de búsqueda de mi biblioteca personal.
El resto de esta publicación se centrará en agregar animaciones a la interfaz de usuario, lo que nos dará la oportunidad de ver un antes y un después y (con suerte) observar cuánto mejor pueden hacer una interfaz de usuario algunas animaciones sutiles y bien ubicadas.
Animando el tamaño modal
Comencemos con el modal en sí. Ábralo y busque, digamos, "jefferson". Observe cómo el modal se vuelve abruptamente más grande para acomodar el nuevo contenido. ¿Podemos animar el modal a tamaños más grandes (y más pequeños)? Por supuesto. Saquemos nuestro confiable useHeight
anzuelo y veamos qué podemos hacer.
Desafortunadamente, no podemos simplemente colocar la referencia de altura en un envoltorio de nuestro contenido y luego pegar la altura en un resorte. Si hiciéramos esto, veríamos el modal deslizarse a su tamaño inicial. No queremos eso; queremos que nuestro modal completamente formado aparezca en el tamaño correcto y cambiar el tamaño desde allí.
Lo que queremos hacer es esperar a que nuestro contenido modal se represente en el DOM, luego establecer nuestra referencia de altura y activar nuestro useHeigh
gancho t para comenzar a medir. Ah, y queremos que nuestra altura inicial se establezca inmediatamente y no se anime en su lugar. Parece mucho, pero no es tan malo como parece.
Empecemos con esto:
const [heightOn, setHeightOn] = useState(false);const [sizingRef, contentHeight] = useHeight({ on: heightOn });const uiReady = useRef(false);
Tenemos algún estado sobre si estamos midiendo la altura de nuestro modal. Esto se establecerá en verdadero cuando el modal esté en el DOM. Luego llamamos a nuestro useHeight
gancho con la on
propiedad para saber si estamos activos. Por último, algunos estados deben confirmar si nuestra interfaz de usuario está lista y podemos comenzar a animar.
Lo primero es lo primero: ¿cómo sabemos cuándo nuestro modal se representa realmente en el DOM? Resulta que podemos usar una referencia que nos informe. Estamos acostumbrados a hacerlo div ref={someRef}
en React, pero en realidad puedes pasar una función, que React llamará con el nodo DOM después de que se represente. Definamos esa función ahora.
const activateRef = ref = { sizingRef.current = ref; if (!heightOn) { setHeightOn(true); }};
Eso establece nuestra referencia de altura y activa nuestro useHeight
gancho. ¡Ya casi hemos terminado!
Ahora bien, ¿cómo conseguimos que esa animación inicial no sea inmediata? El useSpring
gancho tiene dos propiedades nuevas que veremos ahora. Tiene una immediate
propiedad que le indica que haga que los cambios de estado sean inmediatos en lugar de animarlos. También tiene una onRest
devolución de llamada que se activa cuando finaliza un cambio de estado.
Aprovechemos ambos. Así es como se ve el gancho final:
const heightStyles = useSpring({ immediate: !uiReady.current, config: { ...config.stiff }, from: { height: 0 }, to: { height: contentHeight }, onRest: () = (uiReady.current = true)});
Una vez que se completa cualquier cambio de altura, configuramos la uiReady
referencia en true
. Mientras sea así false
, le decimos a reaccionar-primavera que realice cambios inmediatos. Entonces, cuando nuestro modal se monta por primera vez, contentHeight
es cero ( useHeight
devolverá cero si no hay nada que medir) y el resorte simplemente se está enfriando, sin hacer nada. Cuando el modal cambia a abierto y se representa el contenido real, activateRef
se llama a nuestra referencia, useHeight
se activará, obtendremos un valor de altura real para nuestro contenido, nuestro resorte lo establecerá "inmediatamente" y, finalmente, la onRest
devolución de llamada se activará y se animarán los cambios futuros. ¡Uf!
Debo señalar que si, en algún caso de uso alternativo, tuviéramos inmediatamente una altura correcta en el primer renderizado, podríamos simplificar el gancho anterior a solo esto:
const heightStyles = useSpring({ to: { height: contentHeight }, config: config.stiff,})
…que en realidad se puede simplificar aún más a esto:
const heightStyles = useSpring({ height: contentHeight, config: config.stiff,})
Nuestro gancho se representaría inicialmente con la altura correcta y cualquier cambio en ese valor se animaría. Pero dado que nuestro modal se representa antes de mostrarse, no podemos aprovechar esta simplificación.
Los lectores entusiastas podrían preguntarse qué sucede cuando se cierra el modal. Bueno, el contenido se cancelará y el gancho de altura simplemente se mantendrá con la última altura informada, aunque seguirá "observando" un nodo DOM que ya no está en el DOM. Si eso te preocupa, siéntete libre de limpiar las cosas mejor que yo aquí, tal vez con algo como esto:
useLayoutEffect(() = { if (!isOpen) { setHeightOn(false); }}, [isOpen]);
Eso cancelará el ResizeObserver para ese nodo DOM y solucionará la pérdida de memoria.
Ver demostración
Animando los resultados
A continuación, veamos cómo animar el cambio de resultados dentro del modal. Si realiza algunas búsquedas, debería ver que los resultados aparecen y desaparecen inmediatamente.
Eche un vistazo al SearchBooksContent
componente en el searchBooks.js
archivo. En este momento, tenemos const booksObj = data?.allBooks;
lo que extrae el resultado apropiado de la respuesta GraphQL y luego los representa.
{booksObj.Books.map(book = ( SearchResult key={book._id} book={book} selected={selectedBooksMap[book._id]} selectBook={selectBook} dispatch={props.dispatch} /))}
A medida que lleguen nuevos resultados de nuestro punto final GraphQL, este objeto cambiará, entonces, ¿por qué no aprovechar ese hecho, pasarlo al useTransition
gancho de antes y definir algunas animaciones de transición?
const resultsTransition = useTransition(booksObj, { config: { ...config.default }, from: { opacity: 0, position: "static", transform: "translate3d(0%, 0px, 0px)" }, enter: { opacity: 1, position: "static", transform: "translate3d(0%, 0px, 0px)" }, leave: { opacity: 0, position: "absolute", transform: "translate3d(90%, 0px, 0px)" }});
Tenga en cuenta el cambio de position: static
a position: absolute
. Un conjunto de resultados saliente con posicionamiento absoluto no tiene ningún efecto sobre la altura de su padre, que es lo que queremos. Nuestro padre ajustará el tamaño al nuevo contenido y, por supuesto, nuestro modal se animará muy bien al nuevo tamaño según el trabajo que hicimos anteriormente.
Como antes, usaremos nuestra función de transición para representar nuestro contenido:
div className="overlay-holder" {resultsTransition((styles, booksObj) = booksObj?.Books?.length ? ( animated.div style={styles} {booksObj.Books.map(book = ( SearchResult key={book._id} book={book} selected={selectedBooksMap[book._id]} selectBook={selectBook} dispatch={props.dispatch} / ))} /animated.div ) : null )}
Ahora los nuevos conjuntos de resultados aparecerán gradualmente, mientras que los conjuntos de resultados salientes se desvanecerán (y se deslizarán ligeramente) para darle al usuario una señal adicional de que las cosas han cambiado.
Por supuesto, también queremos animar cualquier mensaje, como cuando no hay resultados o cuando el usuario ha seleccionado todo en el conjunto de resultados. El código para esto es bastante repetitivo con todo lo demás aquí, y como esta publicación ya se está volviendo larga, dejaré el código en la demostración.
Animando libros seleccionados (fuera)
En este momento, seleccionar un libro desaparece instantáneamente y abruptamente de la lista. Apliquemos nuestro desvanecimiento habitual mientras lo deslizamos hacia la derecha. Y como el elemento se desliza hacia la derecha (mediante transformación), probablemente queramos que su altura se anime a cero para que la lista pueda ajustarse suavemente al elemento saliente, en lugar de que se deslice hacia afuera, dejando atrás un cuadro vacío, que luego desaparece inmediatamente.
A estas alturas probablemente pienses que esto es fácil. Esperas algo como esto:
const SearchResult = props = { let { book, selectBook, selected } = props;
const initiallySelected = useRef(selected); const [sizingRef, currentHeight] = useHeight();
const heightStyles = useSpring({ config: { ...config.stiff, clamp: true }, from: { opacity: initiallySelected.current ? 0 : 1, height: initiallySelected.current ? 0 : currentHeight, transform: "translate3d(0%, 0px, 0px)" }, to: { opacity: selected ? 0 : 1, height: selected ? 0 : currentHeight, transform: `translate3d(${selected ? "25%" : "0%"},0px,0px)` } });
Esto utiliza nuestro useHeight
gancho confiable para medir nuestro contenido, usando el selected
valor para animar el elemento que sale. Estamos rastreando el selected
accesorio y animando a, o comenzando con, una altura de 0 si ya está seleccionado, en lugar de simplemente eliminar el elemento y usar una transición. Esto permite que diferentes conjuntos de resultados que tienen el mismo libro rechacen correctamente mostrarlo, si está seleccionado.
Este código funciona. Pruébelo en esta demostración.
Pero hay un problema. Si selecciona la mayoría de los libros en un conjunto de resultados, habrá una especie de cadena de animación saltarina a medida que continúe seleccionando. El libro comienza a animarse fuera de la lista y luego la altura del modal comienza a quedar atrás.
En mi opinión, esto parece una tontería, así que veamos qué podemos hacer al respecto.
Ya hemos visto cómo podemos usar la immediate
propiedad para desactivar todas las animaciones de primavera. También hemos visto la onRest
devolución de llamada cuando finaliza una animación, y estoy seguro de que no te sorprenderá saber que hay una onStart
devolución de llamada que hace lo que esperas. Usemos esas piezas para permitir que el contenido dentro de nuestro modal "desactive" la animación de altura del modal cuando el contenido mismo esté animando alturas.
Primero, agregaremos algún estado a nuestro modal que active y desactive la animación.
const animatModalSizing = useRef(true);const modalSizingPacket = useMemo(() = { return { disable() { animatModalSizing.current = false; }, enable() { animatModalSizing.current = true; } };}, []);
Ahora, vinculémoslo a nuestra transición anterior.
const heightStyles = useSpring({ immediate: !uiReady.current || !animatModalSizing.current, config: { ...config.stiff }, from: { height: 0 }, to: { height: contentHeight }, onRest: () = (uiReady.current = true)});
Excelente. Ahora, ¿cómo podemos modalSizingPacket
reducir eso a nuestro contenido, de modo que cualquier cosa que estemos renderizando pueda realmente desactivar la animación del modal, cuando sea necesario? ¡Contexto por supuesto! Creemos un pedazo de contexto.
export const ModalSizingContext = createContext(null);
Luego, envolveremos todo el contenido de nuestro modal con él:
ModalSizingContext.Provider value={modalSizingPacket}
Ahora nuestro componente SearchResult puede capturarlo:
const { enable: enableModalSizing, disable: disableModalSizing } = useContext( ModalSizingContext);
…y atarlo directamente a su resorte:
const heightStyles = useSpring({ config: { ...config.stiff, clamp: true }, from: { opacity: initiallySelected.current ? 0 : 1, height: initiallySelected.current ? 0 : currentHeight, transform: "translate3d(0%, 0px, 0px)" }, to: { opacity: selected ? 0 : 1, height: selected ? 0 : currentHeight, transform: `translate3d(${selected ? "25%" : "0%"},0px,0px)` }, onStart() { if (uiReady.current) { disableModalSizing(); } }, onRest() { uiReady.current = true; setTimeout(() = { enableModalSizing(); }); }});
Tenga en cuenta el setTimeout
al final. He considerado necesario asegurarme de que la animación del modal esté realmente apagada hasta que todo esté arreglado.
Sé que fue mucho código. Si me moví demasiado rápido, asegúrese de ver la demostración para ver todo esto en acción.
Animar libros seleccionados (en)
Concluyamos esta publicación de blog animando los libros seleccionados que aparecen en la pantalla principal, debajo del modal. Hagamos que los libros recién seleccionados aparezcan gradualmente mientras se deslizan desde la izquierda cuando se seleccionan, luego se deslicen hacia la derecha mientras su altura se reduce a cero cuando se eliminan.
Usaremos una transición, pero ya parece haber un problema porque debemos tener en cuenta que cada uno de los libros seleccionados debe tener su propia altura individual. Anteriormente, cuando buscábamos useTransition
, teníamos un único objeto from
y to
que se aplicaba para entrar y salir de elementos.
Aquí, usaremos una forma alternativa, lo que nos permitirá proporcionar una función para el to
objeto. Se invoca con el elemento de animación real (un objeto de libro en este caso) y devolvemos el to
objeto que contiene los valores de animación. Además, realizaremos un seguimiento de un objeto de búsqueda simple que asigna la identificación de cada libro a su altura y luego lo vincularemos a nuestra transición.
Primero, creemos nuestro mapa de valores de altura:
const [displaySizes, setDisplaySizes] = useState({});const setDisplaySize = useCallback( (_id, height) = { setDisplaySizes(displaySizes = ({ ...displaySizes, [_id]: height })); }, [setDisplaySizes]);
Pasaremos la setDisplaySizes
función de actualización al SelectedBook
componente y la usaremos para useHeight
informar la altura real de cada libro.
const SelectedBook = props = { let { book, removeBook, styles, setDisplaySize } = props; const [ref, height] = useHeight(); useLayoutEffect(() = { height setDisplaySize(book._id, height); }, [height]);
Observe cómo verificamos que el valor de altura se haya actualizado con un valor real antes de llamarlo. Esto es para no establecer prematuramente el valor en cero antes de establecer la altura correcta, lo que causaría que nuestro contenido se animara hacia abajo, en lugar de deslizarse completamente. En cambio, inicialmente no se establecerá ninguna altura, por lo que nuestro contenido será predeterminado height: auto
. Cuando nuestro anzuelo se dispare, se establecerá la altura real. Cuando se elimina un elemento, la altura se animará hasta cero, a medida que se desvanece y se desliza.
Aquí está el gancho de transición:
Deja un comentario