我正在尝试创建一个可重用的组件,将在整个应用程序中使用一致的过渡。在这样做时,我创建了一个使用帧运动来动画的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
你可以通过创建一个新组件来创建动态帧运动元素,该组件被分配给motion
函数,并传入元素类型。然后使用新组件代替motion[el]
。
so insideAnimatedDiv
add:
const DynamicMotionComponent = motion(el);
然后在返回语句中这样使用:
<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等…它修改了子react组件的className道具,并应用了Framer Motion variables道具,我是这样实现的:
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;