arwes / arwes

Futuristic Sci-Fi UI Web Framework.

Home Page:https://arwes.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.