React Animators
ARWES provides a few components and functionalities to orchestrate animations with the animator system. Make sure to read the motion fundamentals to understand the animator system.
An <Animator>
component can be used to declare an animator node. It does not render UI elements and provides a React context with the animator interface to all children components.
As an example, we can create a card component with image, title, and description:
const Card = (): JSX.Element => (<article className='card'><img className='card-image' src='logo.svg' /><h1 className='card-title'>ARWES</h1><p className='card-description'>Futuristic Sci-Fi UI Web Framework.</p></article>)
Which could hypothetically render something like this:
<Animator>
Generally, an UI component can have one animator to manage its animations but if more complex animations are required, multiple animators can be used for different pieces of the component internally. For the card component, there can be a parent animator and children animators for every element:
import { Animator } from '@arwes/react'const Card = (): JSX.Element => (<Animator><article className='card'><Animator><img className='card-image' src='logo.svg' /></Animator><Animator><h1 className='card-title'>ARWES</h1></Animator><Animator><p className='card-description'>Futuristic Sci-Fi UI Web Framework.</p></Animator></article></Animator>)
<Animated>
By itself, it does not do anything but it is complemented by the <Animated>
component to run animations. It will listen to the closest parent animator node to run animations on every state change. If the animator is disabled or there is no animator available, all its animation definitions will be ignored.
import { Animator, Animated } from '@arwes/react'const Card = (): JSX.Element => (<Animator><Animated as='article' className='card'><Animator><Animated as='img' className='card-image' src='logo.svg' /></Animator><Animator><Animated as='h1' className='card-title'>ARWES</Animated></Animator><Animator><Animated as='p' className='card-description'>Futuristic Sci-Fi UI Web Framework.</Animated></Animator></Animated></Animator>)
The card can have the following transition animations:
- The card container can have a fading transition.
- The image can have a rotation and flickering transition.
- The title and the description can have a right-to-left translation and fading transition.
<Animator><Animatedas='article'className='card'animated={['fade']}><Animator><Animatedas='img'className='card-image'animated={['flicker', ['rotate', -45, 0]]}src='logo.svg'/></Animator><Animator><Animatedas='h1'className='card-title'animated={['fade', ['x', 20, 0]]}>ARWES</Animated></Animator><Animator><Animatedas='p'className='card-description'animated={['fade', ['x', 20, 0]]}>Futuristic Sci-Fi UI Web Framework.</Animated></Animator></Animated></Animator>
Which would run the following animations when the parent animator node has enter and exit transitions:
Animations
<Animated>
provides a few built-in transitions such as fade
and flicker
for simplicity.
Any custom transition animation can be defined with a shorthand array like [cssProperty, fromValue, toValue]
which will run the animation for entering and exiting animator node transitions.
But there are more ways to define animations such as with objects or functions for more complex use cases.
{/* Shorthand array definition: */}<Animated animated={[['opacity', 0, 1]]} />{/* Is the same with object definition: */}<Animated animated={{transitions: {entering: { opacity: [0, 1] },exiting: { opacity: [1, 0] }}}} />{/* Is the same with custom functions (using Motion One library): */}import { animate } from 'motion'<Animated animated={{transitions: {entering: ({ element, duration }) =>animate(element, { opacity: [0, 1] }, { duration }),exiting: ({ element, duration }) =>animate(element, { opacity: [1, 0] }, { duration })}}} />
For most cases, the entering
and exiting
state animations are enough but the exited
and entered
states can be used also.
By default, <Animated>
will be hidden with CSS visibility: hidden
when the corresponding animator is in exited
state, unless configured otherwise.
<Animated>
animations will get their durations from their parent <Animator>
node but they can be overwriten. There are also other settings which can be configured in both components. Keep in mind that all durations are defined in seconds.
import { spring } from 'motion'{/* Defining durations and timings in <Animator>. */}<Animator duration={{ enter: 0.7, exit: 0.3, delay: 0.25 }}><Animated animated={[['opacity', 0, 1, undefined, spring()]]} /></Animator>{/* Or */}{/* Defining durations and timings in <Animated>. */}<Animator><Animated animated={{transitions: {entering: { opacity: [0, 1], duration: 0.7, delay: 0.25, easing: spring() },exiting: { opacity: [1, 0], duration: 0.3, easing: spring() }}}} /></Animator>
In the card component, the make the animations smoother and sleeker:
- The entire card container can be animated for the entire duration of the card transition. This can be achieved by making the main
<Animator>
a combination of its children animators withcombine
prop. The<Animated as="article">
element will now have the entire duration dynamically calculated automatically. - Then each element inside could be animated in staggering order for a better look and feel. Since the main animator is the parent of all three children animators, it can be configured with a
manager="stagger"
prop. - And we can have the staggering interval duration slower and the image transition slower from the default settings with custom durations.
<Animator combine manager="stagger" duration={{ stagger: 0.1 }}><Animatedas='article'className='card'animated={['fade']}><Animator duration={{ enter: 0.8 }}><Animatedas='img'className='card-image'animated={['flicker', ['rotate', -45, 0]]}src='logo.svg'/></Animator>{/* ... */}</Animated></Animator>
<AnimatorGeneralProvider>
All <Animator>
components in a tree can have default settings customized using the <AnimatorGeneralProvider>
component. It can be used mostly to enable/disable animators and setup default durations.
import { AnimatorGeneralProvider } from '@arwes/react'<AnimatorGeneralProviderdisabled={false}duration={{ enter: 0.4, exit: 0.4, stagger: 0.04 }}><Animator>{/* ... */}</Animator></AnimatorGeneralProvider>
Simplification
The card component animations can be roughly simplified by just using one animator and running just one animation since the of UI elements are known and controlled.
import { Animator, Animated, type AnimatedProp } from '@arwes/react'import { timeline, stagger } from 'motion'const animated: AnimatedProp = {transitions: {entering: ({ element, $, duration }) =>timeline([[element, { opacity: [0, 1] }, { duration }],[$('img'),{ opacity: [0, 1, 0.5, 1], rotate: [-45, 0] },{ at: 0, duration: duration * 0.75 }],[$('h1, p'),{ opacity: [0, 1], x: [20, 0] },{ at: 0.1, duration: duration - 0.2, delay: stagger(0.1) }]]),// The exit transition animation is simplified by just// animating the root element.exiting: { opacity: [1, 0, 0.5, 0] }}}const Card = (): JSX.Element => (<Animator><Animatedas='article'className='card'animated={animated}><img className='card-image' src='logo.svg' /><h1 className='card-title'>ARWES</h1><p className='card-description'>Futuristic Sci-Fi UI Web Framework.</p></Animated></Animator>)
This approach restricts the flexibility because animations are hard-coded. But in isolated components it can be a performance improvement. This depends on the use case and performance constraints.
Animation Tools
Integrating any other animation tool is fairly easy to do. An example with GSAP could be:
import { gsap } from 'gsap'<Animated animated={{transitions: {entering: ({ element, duration }) =>gsap.to(element, { opacity: 1, duration }),exiting: ({ element, duration }) =>gsap.to(element, { opacity: 0, duration })}}} />
The only requirement is that the return of the transition animation function is an object which contains a .cancel()
method to cancel the animation and remove all animated properties on the element(s). Optionally, it can provide a .then(callback)
method or .finished
promise to know when the animation is finished for performance reasons.
Composition
Once a UI component has configured its animators and animated components, it can be reused anywhere along with other animators to orchestrate when and how the animations should run.
Check out the Playground for more examples.