【问题标题】:Dynamically declare framer motion html element type动态声明成帧器运动 html 元素类型
【发布时间】:2022-11-09 07:00:37
【问题描述】:

我正在尝试创建一个可重用的组件,它将在整个应用程序中使用一致的转换。在这样做时,我创建了一个使用 framer-motion 进行动画处理的 div。我希望能够通过道具告诉组件它是一个 div、span 等。

像这样称呼它:

<AnimatedEl el={'div'}>
    ...
</AnimatedEl>
import { AnimatePresence, motion } from 'framer-motion'

interface AnimatedDivProps {
  el: string
  children: React.ReactNode
  className?: string
}

const AnimatedDiv = ({ el, className, children }: AnimatedDivProps) => {
  const transition = {
    duration: 0.8,
    ease: [0.43, 0.13, 0.23, 0.96],
  }
  return (
    <AnimatePresence>
      <motion[el]
        className={className}
        initial='exit'
        animate='enter'
        exit='exit'
        variants={{
          exit: { y: 100, opacity: 0, transition },
          enter: { y: 0, opacity: 1, transition: { delay: 0.2, ...transition } },
        }}
      >
        {children}
      </motion[el]>
    </AnimatePresence>
  )
}

export default AnimatedDiv

【问题讨论】:

    标签: reactjs typescript framer-motion


    【解决方案1】:

    您可以通过创建分配给motion 函数的新组件来创建动态成帧运动元素,并传入元素类型。然后使用该新组件代替motion[el]

    所以在AnimatedDiv 里面添加:

    const DynamicMotionComponent = motion(el);
    

    然后在 return 语句中像这样使用它:

    <DynamicMotionComponent
      className={className}
      initial='exit'
      animate='enter'
      exit='exit'
      variants={{
        exit: { y: 100, opacity: 0, transition },
        enter: { y: 0, opacity: 1, transition: { delay: 0.2, ...transition } },
      }}
    >
      {children}
    </DynamicMotionComponent>
    

    这是一个不同的包装组件示例,它具有类似的概念,它基于子组件创建运动元素。当元素在视图中时,它会淡入、动画 y 值并错开子元素,子元素可以是一组 div、svg 等...它修改子反应组件的 className 道具并应用 Framer Motion 变体道具,这里是我是如何做到的:

    import {
      Children,
      cloneElement,
      isValidElement,
      ReactChild,
      ReactFragment,
      ReactNode,
      ReactPortal,
      useEffect,
      useRef,
    } from "react";
    import {
      motion,
      useAnimationControls,
      useInView,
      Variants,
    } from "framer-motion";
    import CONSTANTS from "@/lib/constants";
    import styles from "@/styles/components/motionFadeAndStaggerChildrenWhenInView/motionFadeAndStaggerChildrenWhenInView.module.scss";
    
    interface MotionFadeAndStaggerChildrenWhenInView {
      childClassName?: string;
      children: ReactNode;
      className?: string;
      variants?: Variants;
    }
    
    type Child = ReactChild | ReactFragment | ReactPortal;
    
    const parentVariants = {
      fadeInAndStagger: {
        transition: {
          delayChildren: 0.3,
          ease: CONSTANTS.swing,
          staggerChildren: 0.2,
        },
      },
      initial: {
        transition: {
          ease: CONSTANTS.swing,
          staggerChildren: 0.05,
          staggerDirection: -1,
        },
      },
    };
    
    const childVariants = {
      fadeInAndStagger: {
        opacity: 1,
        transition: {
          ease: CONSTANTS.swing,
          y: { stiffness: 1000, velocity: -100 },
        },
        y: 0,
      },
      initial: {
        opacity: 0,
        transition: {
          ease: CONSTANTS.swing,
          y: { stiffness: 1000 },
        },
        y: 50,
      },
    };
    
    // eslint-disable-next-line @typescript-eslint/no-redeclare -- intentionally naming the variable the same as the type
    const MotionFadeAndStaggerChildrenWhenInView = ({
      childClassName,
      children,
      className,
      variants = childVariants,
    }: MotionFadeAndStaggerChildrenWhenInView) => {
      const childrenArray = Children.toArray(children);
    
      const childClassNames =
        childClassName !== undefined
          ? `${childClassName} ${styles.fadeAndStaggerChild}`
          : styles.fadeAndStaggerChild;
    
      const controls = useAnimationControls();
      const ref = useRef<HTMLDivElement | null>(null);
      const isInView = useInView(ref, { once: true });
    
      useEffect(() => {
        if (isInView) {
          // eslint-disable-next-line @typescript-eslint/no-floating-promises
          controls.start("fadeInAndStagger");
        }
      }, [controls, isInView]);
    
      return (
        <motion.div
          ref={ref}
          animate={controls}
          className={
            className
              ? `${styles.fadeAndStaggerParent} ${className}`
              : styles.fadeAndStaggerParent
          }
          initial="initial"
          variants={parentVariants}
        >
          {Children.map(childrenArray, (child: Child) => {
            if (!isValidElement(child)) return null;
    
            if (isValidElement(child)) {
              const propsClassNames: string =
                // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
                Object.hasOwn(child.props, "className") === true
                  ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
                    (child.props.className as string)
                  : "";
    
              const DynamicMotionComponent = motion(child.type);
    
              // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
              return cloneElement(<DynamicMotionComponent />, {
                ...child.props,
                className: propsClassNames
                  ? `${childClassNames} ${propsClassNames}`
                  : childClassNames,
                variants,
              });
            }
    
            return null;
          })}
        </motion.div>
      );
    };
    
    export default MotionFadeAndStaggerChildrenWhenInView;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多