我正在尝试创建一个可重用的组件,它将在整个应用程序中使用一致的过渡。在此过程中,我创建了一个使用框架运动来制作动画的 div。我希望能够通过 prop 告诉组件它是一个 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
您可以通过创建分配给
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>
如果您记录
motion
的类型和 motion.div
的类型,您可以看到运动不是一个对象,而是一个可调用的函数。
console.log(typeof motion) // => function
哪里
console.log(typeof motion.div) // => object
这是包装器组件的另一个示例,具有类似的概念,它基于子组件创建运动元素。当元素处于视图中时,它会淡入,对 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;
使用元素类型的字符串属性来实现此目的的一种方法是将其作为索引传递到运动组件中:
const AnimatedEl = ({ el }: { el: "h1" | "div" }) => {
const Motion = motion[el];
return <Motion variants={{}} />;
};
element
可以适当输入,例如h1
、div
或使用其他内置类型。