您可以通过创建分配给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;