@arwes/animation
Assemble and disassemble user interfaces using animation controls in React.
Based on the UI/UX guidelines, the animation tools can be used to manage and control animation flows in applications. Then the specific components animations or components events can be defined more easily, such as visual effects or sound playbacks when a component transition from one state to another.
The animation tools require the React component tree to allow the dynamic communication between components.
It goes hand in hand with the sounds tools, though it is not a dependency.
Installation
All the tools are bundled and can be installed with the following NPM package:
npm install @arwes/animation
The animation management tooling requires React v17 as a peer-dependency.
npm install react@17 react-dom@17 prop-types
In TypeScript, the following type packages are needed:
npm install @types/react@17 @types/react-dom@17
Animators
Any React component can have animation controls and be added to the
Arwes system by using the <Animator/>
component.
The <Animator/>
component will work as an animated node in the system. It will
be interconnected by sharing data and communicating with its parent and
children counterparts.
Normally, the withAnimator
higher-order component (HOC)
will handle the animator component setup instead of the component itself.
So class/function components with specific initial settings can be created.
A HTML button component can be animated like:
import React, { FC, useRef, MutableRefObject } from 'react';import { AnimatorRef, AnimatorClassSettings, withAnimator } from '@arwes/animation';interface ButtonProps {// An animated component will receive the `animator` prop with// the animator API.animator: AnimatorRef}const ButtonComponent: FC<ButtonProps> = props => {const buttonRef = useRef<HTMLButtonElement>(null);// Here you can set any kind of data you want to pass to the// animator "animate events". In this case, a HTML button ref.props.animator.setupAnimateRefs(buttonRef);return (<button ref={buttonRef}>{props.children}</button>);};// "enter" is the duration to transition from exited to entering to entered.// "exit" is the duration to transition from entered to exiting to exited.const duration = { enter: 200, exit: 200 };const onAnimateEntering = (animator: AnimatorRef,buttonRef: MutableRefObject<HTMLButtonElement>): void => {// On flow entering:// You can animate the references passed as the second parameter.};const onAnimateExiting = (animator: AnimatorRef,buttonRef: MutableRefObject<HTMLButtonElement>): void => {// On flow exiting:// You can animate the references passed as the second parameter.};const animatorClassSettings: AnimatorClassSettings = {duration,onAnimateEntering,onAnimateExiting};const Button = withAnimator(animatorClassSettings)(ButtonComponent);export { ButtonProps, Button };
The new wrapped component <Button/>
by the HOC will have animations
enabled and it will handle when to call the animate events on flow transitions.
But this component will not execute any HTML/CSS animations on its elements, so we need to check how to add them in the animate events.
Animate Events
An animator will only handle the flow transitions and flow control management. The actual HTML elements animations should be handled inside the animate events.
By setting up the references to the HTML elements inside the component render
with props.animator.setupAnimateRefs()
, the events will receive them for
manipulations.
The HTML elements animations can be made with any HTML animation library. Recommended libraries are animejs and GreenSock.
If the example <Button/>
component should have animations:
- To enter the flow with opacity from
0
to1
and timeduration.enter
. - To exit the flow with opacity from
1
to0
and timeduration.exit
.
They could be written like this using animejs:
import anime from 'animejs';// ...const onAnimateEntering = (animator: AnimatorRef,buttonRef: MutableRefObject<HTMLButtonElement>): void => {anime({targets: buttonRef.current,duration: animator.duration.enter,easing: 'linear',opacity: [0, 1]});};const onAnimateExiting = (animator: AnimatorRef,buttonRef: MutableRefObject<HTMLButtonElement>): void => {anime({targets: buttonRef.current,duration: animator.duration.exit,easing: 'linear',opacity: [1, 0]});};// ...
When the flow enters, it would go from hidden to visible. When the flow exits, it will reverse it.
There is a event for all the flow states transitions and the component mounting and unmounting lifecycles.
onAnimateMount
- On component mount.onAnimateEntering
- On flow entering.onAnimateEntered
- On flow entered.onAnimateExiting
- On flow exiting.onAnimateExited
- On flow exited.onAnimateUnmount
- On component unmount.
Now, according to how the component is used, it will behave in a certain way.
Parent/Children Nodes
By default, if the component is used alone or if it does not have parent counterparts, then it is considered a root component, unless configured otherwise.
If the component is a root node, on component mount, by default its flow will
transition from exited
to entering
to entered
and stay there.
If the component is a child node, it will always listen to its closest parent node flow state to know when it should transition. See more in Nesting Animators.
Normally, the root nodes are managed using its instance settings, and the children nodes are managed by their parent nodes.
Instance Settings
An animated component when it is used, it can receive an animator
settings
object to configure declaratively its animator.
The root nodes can receive the property animator.activate
to determine
when it should enter or exit the animation flow. This, in turn, will manage
its non-root children nodes accordingly.
In the example of the <Button/>
component, it could receive instance
settings like:
import { AnimatorInstanceSettings, ... } from '@arwes/animation';// ...const instanceSettings: AnimatorInstanceSettings = {// true to make the component transition in.// false to make the component transition out.activate: true};// ...<Button animator={instanceSettings}>My Animated Button</Button>// ...
A complete example using the <Button/>
component as a root node can look
like this:
It will update the component flow activation every 2 seconds.
Since it is an animated component, by default, its flow will be exited
. Then
if it is activated, it will transition from exited
to entering
to entered
.
When it transitions to entering
, the event onAnimateEntering
will be called.
When it is deactivated, it will transition from entered
to exiting
to exited
.
When it transitions to exiting
, the event onAnimateExiting
will be called.
The component should set the initial styles of the animator animations changes.
In this case, the component is set to opacity: 0
when it is exited
in the
animation. Since its initial value is exited
and the events are not executed,
the component should set the initial CSS styles to opacity: 0
too for
consistency.
When an animated components exits, it is not unmounted.
When nesting animators, the children nodes will listen to their parent nodes so there is not management needed for them, it would be automatically handled by the animation tools.
General Settings
Usually, the application animated components would have certain similar settings
for consistency. The available general settings can be provided using the
<AnimatorGeneralProvider/>
provider.
For example, to provide common animator durations to all animated children nodes, something like this can be done:
import React, { FC } from 'react';import { AnimatorGeneralProvider, AnimatorGeneralProviderSettings } from '@arwes/animation';const animatorGeneralSettings: AnimatorGeneralProviderSettings = {duration: { enter: 200, exit: 100 }};// ...<AnimatorGeneralProvider animator={animatorGeneralSettings}>{/* ... */}</AnimatorGeneralProvider>// ...
Currently, only the setting duration
can be setup this way.
Suspend On Visibility Change
Some HTML animation libraries like animejs pause the animations when the browser window looses focus. In animejs this behavior can be disabled using:
import anime from 'animejs';anime.suspendWhenDocumentHidden = false;
You can see more at anime - suspend on visibility change.
You can see and play with more examples in the playground.