Research and create animator animated HTML element primitives
romelperez opened this issue · comments
The current Animator
architecture is not intuitive enough for normal animated component scenarios. Making an animated component requires to define and pass around HTML element references and other required data in multiple places in the component source code to execute a dynamic HTML animation.
A new way to handle "common" animator animations use cases is required to simplify the animations setup. It does not mean to modify the Animator
component, but to add a new way to subscribe for animation flow changes and manipulate dynamic styles and HTML animations.
By suggestion of @Dessix, there can be a primitive component to handle single HTML element animations as a child of an Animator
component. Similar to how styled-components handles styles and react-motion handles animations, there can be a primitive component to handle animation flow subscriptions on an HTML element.
For example, there can be a new Animated
component to create an HTML element with provided prop as: keyof HTMLElementTagNameMap
. The component will subscribe to the closest parent Animator
flow, and when it changes, it will call the respective provided animation callbacks with animator and the HTML element references.
<Animator animator={...}>
<Animated
as='button'
onEntering={(animator: AnimatorRef, button: HTMLButtonElement) => {
// animate the "button" element on flow entering
}}
onExiting={(animator: AnimatorRef, button: HTMLButtonElement) => {
// animate the "button" element on flow exiting
}}
onUnmount={(animator: AnimatorRef, button: HTMLButtonElement) => {
// remove animations
}}
...
>
My Button
</Animated>
</Animator>
A primitive component API like this, should support the following requirements:
- It should create an HTML element as usual with the same API and types.
- It should allow to subscribe to the closest parent
Animator
flow and call the respective provided callbacks. - It should allow to setup initial CSS styles when the
Animator
animate = true
. And ignore them otherwise. - It should allow to modularize on flow callbacks in a different location. Similar to how component styles (using Emotion) can be modularized in a different location or file.
- It should allow to compose external parameters without impacting performance. For instance, if a design
theme
object is required in the callbacks, there should be a way to compose that object with the callbacks without creating and passing new functions props or data props to the component. This is a common use case for dynamic components.
Also, right now the Animator
API supports imperative animation tools, declarative animation APIs are not properly supported. It could be useful to allow support for declarative animation definitions such as react-motion, react-spring, and framer-motion.
- Research common animation use cases and define a general robust API to handle single HTML element animations under an
Animator
flow subscription. - Create a component primitive to handle single HTML element animator animations.
Using the Arwes Animator
component with declarative HTML animation libraries like react-motion, react-spring, and framer-motion would require specific "bindings" with subscriptions strategies from the animation libraries to the Animator
component.
The libraries are based on spring physics animations so durations and easings are not used. This requires that the Animator
component should handle non-root animator nodes without fixed durations. This should be a new feature for the component.
The Animated
component was created in a new @arwes/animated
package. The component work with animejs for the HTML animations. An example of how it can work can be done as follows:
const duration = { enter: 200, exit: 200 };
const Sandbox = () => {
const [activate, setActivate] = React.useState(true);
React.useEffect(() => {
const timeout = setTimeout(() => setActivate(!activate), 2000);
return () => clearTimeout(timeout);
}, [activate]);
return (
<Animator animator={{ activate, duration }}>
<Animated
as='div'
style={{
width: 300,
height: 50,
backgroundColor: 'cyan'
}}
animated={{
initialStyles: { opacity: 0, width: 0 },
entering: { opacity: 1, width: 300 },
exiting: { opacity: 0, width: 0 }
}}
/>
</Animator>
);
};
render(<Sandbox />);
Resulting in:
animated.mp4
Now the @arwes/animator
(renamed from @arwes/animation
) is decoupled from the HTML animation implementation functionalities. So in the future, there can be other packages with binding to other animation libraries.