A handwritten React animation component

May 6, 2023 1430hotness 0likes 0comments

During the development of a project, designers will inevitably create animated effects to enhance the user experience. If the current effect does not require interaction and is only for display, we can use GIF or APNG to achieve the effect.

apng.png

but if the current animation requires other interactions in addition to presentation, or even a component requires animation effects, it would be unreasonable to use a picture format. So I wrote an extremely simple css animation library rc-css-animate . Here we directly use animate.css as the dependent library for css animation. Animate.css not only provides many interactive animation style classes, but also provides animation running speed, latency, and the number of repetitions and other style classes.

as you can see, the default animate.css build animation needs to carry the prefix "animate__".

<h1 class="animate__animated animate__bounce">An animated element</h1>

of course, the library encapsulates css animation and still supports other animation libraries as well as their own handwritten css animation, but this library is not recommended if developers need to control all kinds of complex animation.

use

can be used in the following ways:

import React, { useRef } from "react";
import ReactCssAnimate from "rc-css-animate";

// introduce animate.css as an animation dependency
import "animate.css";

function App() {
  const animateRef = useRef(null);

  return (
    <div className="App">
      <ReactCssAnimate
        // Define the components that currently display the animation
        // Use by default div
        tag="div"
        // Of the current component className
        className=""
        // Of the current component style
        style={{}}
        // Of the current component ref
        ref={animateRef}
        // Animation prefix
        clsPrefix="animate__"
        // Of the current animation className
        animateCls="animated backInDown infinite"
        // Is the animation displayed at the beginning of the animation?
        initialVisible={false}
        // Gets whether the display status is processed at the end of the animation
        getVisibleWhenAnimateEnd={(cls) =>{
// if there is an Out in the current animateCls
// if false is returned, it will not be displayed at the end of the animation.
If (cls.includes ("Out")) {
Return false
}
Return true
}}
// callback at the end of the animation
OnAnimationEnd= {() => {
          console.log("done");
        }}
      >
        <div>
Test animation
</div>
      </ReactCssAnimate>
    </div>
  );
}

ReactCssAnimate uses React hooks, but also provides compatible class components. Global prefix settings are also provided.

import React from "react";
import {
  // use previous versions of the compatibility of class components
  CompatibleRAnimate as ReactCssAnimate,
  setPrefixCls,
} from "rc-css-animate";

// introduce animate.css as an animation dependency
import "animate.css";

// set the global prefix, which will be overwritten by the current component
setPrefixCls("animate__");

/ * * build animation block components * /
function BlockWrapper(props) {
  // need to obtain and input className, children, style
  const { className, children, style } = props;
  return (
    <div
      className={className}
      style={{
        background: "red",
        padding: 100,
        ...style,
      }}
    >
      {children}
    </div>
  );
}

function App() {
  return (
    <div className="App">
      <ReactCssAnimate
        tag={BlockWrapper}
        // Of the current animation className
        animateCls="animated backInDown infinite"
      >
        <div>
Test animation
</div>
      </ReactCssAnimate>
    </div>
  );
}

Source code parsing

The source code is relatively simple and is built on createElment and forwardRef. Where forwardRef forwards the currently set ref to the internal component. Students who are not familiar with forwardRef can check out the documents on the official website about Refs retweet .

import React, {
  createElement,
  forwardRef,
  useCallback,
  useEffect,
  useState,
} from "react";
import { getPrefixCls } from "./prefix-cls";
import { AnimateProps } from "./types";

// Global animation prefix
let prefixCls: string = "";

const getPrefixCls = (): string => prefixCls;

// set the global animation prefix
export const setPrefixCls = (cls: string) => {
  if (typeof cls !== "string") {
    return;
  }
  prefixCls = cls;
};

const Animate = (props: AnimateProps, ref: any) => {
  const {
    tag = "div",
    clsPrefix = "",
    animateCls,
    style,
    initialVisible,
    onAnimationEnd,
    getVisibleWhenAnimateEnd,
    children,
  } = props;

  // obtain the explicit concealment of the component through initialVisible. If not, the default is true.
  const [visible, setVisible] = useState<boolean>(initialVisible ?? true);

  // currently you don't need to show it. Just return null.
  if (!visible) {
    return null;
  }

  // No animation class, return subcomponents directly
  if (!animateCls || typeof animateCls !== "string") {
    return <>{children}</>;
  }

  useEffect(() => {
    // the settings that do not get the end of the request are hidden and returned directly without processing.
    if (!getVisibleWhenAnimateEnd) {
      return;
    }
    const visibleWhenAnimateEnd = getVisibleWhenAnimateEnd(animateCls);

    // if the animation needs to be displayed at the end of the animation and is not currently displayed, show it directly.
    if (visibleWhenAnimateEnd && !visible) {
      setVisible(true);
    }
  }, [animateCls, visible, getVisibleWhenAnimateEnd]);

  const handleAnimationEnd = useCallback(() => {
    if (!getVisibleWhenAnimateEnd) {
      onAnimationEnd?.();
      return;
    }

    // it is currently displayed and needs to be hidden after the animation ends. Set visible to false directly.
    if (visible && !getVisibleWhenAnimateEnd(animateCls)) {
      setVisible(false);
    }
    onAnimationEnd?.();
  }, [getVisibleWhenAnimateEnd]);

  let { className = "" } = props;

  if (typeof className !== "string") {
    className = "";
  }

  let animateClassName = animateCls;

  // get the final animation prefix
  const finalClsPrefix = clsPrefix || getPrefixCls();

  // none or the animation prefix is not a string and will not be processed
  if (!finalClsPrefix || typeof finalClsPrefix !== "string") {
    animateClassName = animateCls.split(" ").map((item) =>
      `${finalClsPrefix}${item}`
    ).join(" ");
  }

  // create and return React element
  return createElement(
    tag,
    {
      ref,
      onAnimationEnd: handleAnimationEnd,
      // merge the passed className and animateClassName
      className: className.concat(` ${animateClassName}`),
      style,
    },
    children,
  );
};

// use forwardRef to forward ref
// the first parameter is props, and the second parameter is ref
export default forwardRef(Animate);

the above code is all in rc-css-animate . You are also welcome to put forward issue and pr.

InterServer Web Hosting and VPS

Aaron

Hello, my name is Aaron and I am a freelance front-end developer

Comments