Usemos React, styled-components y react-flip-toolkit para crear nuestra propia versión del menú de navegación animado en la página de inicio de Stripe. Es un menú impresionante con algunos efectos de animación ingeniosos y la combinación de estas tres herramientas puede hacer que sea relativamente fácil de recrear.
Este es un tutorial de nivel intermedio que asume familiaridad con React y conceptos básicos de animación. Nuestra guía React es un buen lugar para comenzar.
Esto es lo que pretendemos hacer:
Consulte el menú Pen React Stripe de Alex (@aholachek) en CodePen.
Ver repositorio
Rompiendo la animación
Primero, dividamos la animación en diferentes partes para que podamos reproducirla más fácilmente. Es posible que desee ver el producto terminado en cámara lenta (use los conmutadores) para que pueda captar todos los detalles.
- El contenedor desplegable blanco actualiza tanto su tamaño como su posición.
- El fondo gris en la mitad inferior del contenedor desplegable cambia su altura.
- A medida que se mueve el contenedor desplegable, el contenido anterior se desvanece y se desplaza ligeramente hacia la dirección opuesta, como si el menú desplegable los dejara atrás, mientras que el nuevo contenido se desliza a la vista.
Hay algunas reglas de guía útiles a tener en cuenta cuando nos embarcamos en la reproducción de esta animación en React. Siempre que sea posible, simplifiquemos las cosas haciendo que el navegador administre el diseño. Haremos esto manteniendo los elementos en el flujo DOM regular en lugar de usar el posicionamiento absoluto y el cálculo manual. En lugar de tener un solo componente desplegable que tenemos que reubicar cada vez que cambia la posición del mouse de un usuario, representaremos un solo menú desplegable en la sección correspondiente de la barra de navegación.
Usaremos la técnica FLIP para crear la ilusión de que los tres componentes desplegables separados son en realidad un solo componente móvil.
Andamiaje de la interfaz de usuario con componentes con estilo
Para empezar, crearemos un componente de barra de navegación no animado que simplemente toma un objeto de configuración de títulos y componentes desplegables y presenta un menú de barra de navegación. El componente mostrará y ocultará el menú desplegable correspondiente al pasar el mouse por encima.
Construiremos los componentes de la interfaz de usuario utilizando componentes con estilo. No solo son una forma conveniente de crear una interfaz de usuario modular, sino que también tienen una gran API para agregar animaciones de fotogramas clave CSS configurables. Resulta que las animaciones CSS y React funcionan muy bien juntas, por lo que usaremos fotogramas clave CSS para agregar muchas de las animaciones más adelante.
Con los componentes ensamblados sin animaciones, hemos creado algo que se parece a esto:
Consulte el menú Pen React Stripe antes de la animación de Alex (@aholachek) en CodePen.
Observe que falta el fondo gris en la parte inferior del menú. Es el único elemento que tendremos que sacar del flujo DOM regular y la posición absoluta, así que lo ignoraremos por ahora.
Animando nuestro menú desplegable con la técnica FLIP
Vamos a utilizar la biblioteca react-flip-toolkit para ayudarnos a animar el tamaño y la posición del menú desplegable. Esta es una biblioteca que armé para hacer que las transiciones avanzadas y complejas sean más fáciles y configurables.
Nos proporciona dos componentes: un nivel superior <Flipper/>
componente, y un <Flipped/>
componente para envolver cualquier niño que queramos animar.
Primero, configuremos el Flipper
componente contenedor en la función de renderizado de AnimatedNavbar:
// currentIndex is the index of the hovered dropdown
<Flipper flipKey={currentIndex}>
<Navbar>
{navbarConfig.map((n, index) => {
// render navbar items here
})}
</Navbar>
</Flipper>
A continuación, en nuestro componente DropdownContainer, ajustaremos los elementos que necesitan ser animados en su propio Flipped
componentes, asegurándose de darles a cada uno un único flipdId
apuntalar:
<DropdownRoot>
<Flipped flipId="dropdown-caret">
<Caret />
</Flipped>
<Flipped flipId="dropdown">
<DropdownBackground>
{children}
</DropdownBackground>
</Flipped>
</DropdownRoot>
Estamos animando el <Caret/>
componente y el <DropdownBackground/>
componente por separado para que el overflow:hidden
estilo en el <DropdownBackground/>
componente no interfiere con la representación del <Caret/>
componente.
Ahora tenemos una animación FLIP en funcionamiento, pero todavía hay un problema: el contenido del menú desplegable aparece extrañamente estirado en el transcurso de la animación:
Consulte el menú de rayas de Pen React – Error n. ° 1: sin ajuste de escala por Alex (@aholachek) en CodePen.
Este efecto no deseado se produce porque las transformaciones de escala se aplican a los niños. Si aplica un scaleY(2)
a un div con algo de texto adentro, estará escalando el texto y distorsionándolo también.
Podemos resolver este problema envolviendo a los niños en un Flipped
componente con un inverseFlipId
que hace referencia al componente principal flipId
(en este caso "dropdown"
) para solicitar que las transformaciones de padres se cancelen para los niños. Debido a que todavía queremos que las transformaciones de traducción afecten a los niños, también pasamos la scale
prop para limitar la cancelación a solo cambios de escala.
<DropdownRoot>
<Flipped flipId="dropdown-caret">
<Caret />
</Flipped>
<Flipped flipId="dropdown">
<DropdownBackground>
<Flipped inverseFlipId="dropdown" scale>
{children}
</Flipped>
</DropdownBackground>
</Flipped>
</DropdownRoot>
Uf. Todo ese trabajo y hemos creado algo que se parece a esto:
Vea el menú Pen React Stripe – Simple FLIP de Alex (@aholachek) en CodePen.
Todo está en los detalles
Se acerca, pero todavía tenemos que prestar atención a los pequeños detalles que hacen que la animación se vea genial: la animación de rotación sutil cuando el menú desplegable aparece y desaparece, el fundido cruzado de los niños del menú desplegable anterior y actual, y ese fondo gris suave como la seda. animación de altura.
Animaciones de fotogramas clave CSS configurables con componentes con estilo
Los componentes con estilo, que hemos estado usando para crear la interfaz de usuario para esta demostración, ofrecen una forma muy conveniente de crear animaciones de fotogramas clave configurables. Usaremos esta funcionalidad tanto para la animación de entrada desplegable como para el fundido cruzado de los contenidos. Podemos pasar información básica sobre la animación deseada, ya sea que el contenido se esté animando hacia adentro o hacia afuera, y la dirección en que se ha movido el mouse del usuario, y obtener automáticamente la animación adecuada aplicada. Aquí, por ejemplo, está el código para la animación de fundido cruzado en el <FadeContents>
componente:
const getFadeContainerKeyFrame = ({ animatingOut, direction }) => {
if (!direction) return;
return keyframes`
from {
transform: translateX(${
animatingOut ? 0 : direction === "left" ? 20 : -20
}px);
}
to {
transform: translateX(${
!animatingOut ? 0 : direction === "left" ? -20 : 20
}px);
opacity: ${animatingOut ? 0 : 1};
}
`;
};
const FadeContainer = styled.div`
animation-name: ${getFadeContainerKeyFrame};
animation-duration: ${props => props.duration * 0.5}ms;
animation-fill-mode: forwards;
position: ${props => (props.animatingOut ? "absolute" : "relative")};
opacity: ${props => (props.direction && !props.animatingOut ? 0 : 1)};
animation-timing-function: linear;
top: 0;
left: 0;
`;
Cada vez que el usuario coloca el cursor sobre un nuevo elemento, proporcionaremos no solo el menú desplegable actual, sino el menú desplegable anterior como elementos secundarios del DropdownContainer
componente, junto con información sobre en qué dirección se ha movido el mouse del usuario. El DropdownContainer
el componente luego envolverá a sus hijos en un nuevo componente, FadeContents
, que utilizará el código de animación de fotogramas clave anterior para agregar la transición adecuada.
Aquí hay un enlace al código completo para FadeContents
componente.
La animación de entrada / salida del menú desplegable funcionará de manera muy similar.
El toque final: una animación de fondo fluida
Finalmente, agreguemos la animación de fondo gris. Para mantener esta animación nítida, debemos apartarnos de nuestra estrategia anterior de mantener el anidamiento DOM normal y dejar que el navegador maneje el diseño, y realizar algunos cálculos de posicionamiento manual en su lugar. También necesitaremos interactuar directamente con el DOM. En resumen, se va a poner un poco complicado.
Aquí hay una representación visual de nuestro enfoque básico:
Vea el menú Pen React Stripe – Fondo animado por Alex (@aholachek) en CodePen.
Posicionaremos absolutamente un div gris en la parte superior de la DropdownContainer
. En el componentDidMount
función de ciclo de vida de DropdownContainer
, actualizaremos el translateY
transformar del fondo gris. Si el componente del contenedor desplegable solo tiene un solo hijo (lo que significa que el usuario solo ha desplazado un solo menú desplegable hasta ahora), configuraremos los div grises translateY
a la altura de la primera sección desplegable. Si hay dos hijos, incluido un menú desplegable anterior, estableceremos la inicial translateY
a la altura de la primera sección del menú desplegable anterior, y luego animar el translateY
a la altura de la primera sección del menú desplegable actual. Aquí está la función en la que se llama componentDidMount
:
const updateAltBackground = ({
altBackground,
prevDropdown,
currentDropdown
}) => {
const prevHeight = getFirstDropdownSectionHeight(prevDropdown)
const currentHeight = getFirstDropdownSectionHeight(currentDropdown)
// we'll use this function when we want a change
// to happen immediately, without CSS transitions
const immediateSetTranslateY = (el, translateY) => {
el.style.transform = `translateY(${translateY}px)`
el.style.transition = "transform 0s"
requestAnimationFrame(() => (el.style.transitionDuration = ""))
}
if (prevHeight) {
// transition the grey ("alt") background from its previous height
// to its current height
immediateSetTranslateY(altBackground, prevHeight)
requestAnimationFrame(() => {
altBackground.style.transform = `translateY(${currentHeight}px)`
})
} else {
// immediately set the background to the appropriate height
// since we don't have a stored value
immediateSetTranslateY(altBackground, currentHeight)
}
}
Este enfoque requiere DropdownContainer
usar un ref
y busque dentro de sus hijos para tomar medidas DOM en el getFirstDropdownSectionHeight
función, que se siente descuidada. Si tiene alguna idea para implementaciones alternativas, hágamelo saber en los comentarios.
Terminando
Con suerte, este artículo ha ayudado a aclarar algunas técnicas que puede utilizar la próxima vez que cree una animación en React. Normalmente hay varias formas de lograr cualquier efecto, pero a menudo tiene sentido comenzar con la implementación más simple posible (componentes básicos con algunas transiciones CSS o animaciones de fotogramas clave) y escalar la complejidad a partir de ahí cuando sea necesario. En nuestro caso, eso significó incluir una biblioteca adicional, react-flip-toolkit, por lo que no tuvimos que preocuparnos por la transición manual de la posición del componente desplegable en la pantalla. Para recrear completamente la animación, tuvimos que escribir una buena cantidad de código. Pero al dividir esta animación en partes separadas y abordarlas una por una, logramos replicar un efecto de interfaz de usuario bastante bueno en React.