Motion动画的几个例子
用useInView触发动画
用useAnimation控制动画
其中 whileTap="tapsss"也表明key的值可以自定义,whileTap也可以接函数
import { motion, useAnimation } from "framer-motion";
import { useEffect, useRef } from "react";
import { useInView } from "framer-motion"; // 一个辅助判断元素是否在视口中的钩子// 示例:当元素滚动进入视口时开始动画
export default () => {const controls = useAnimation();const ref = useRef(null);const isInView = useInView(ref); // 判断元素是否在视口内useEffect(() => {// 当元素进入视口时,启动动画if (isInView) {controls.start("visible");}}, [isInView]);const elementVariants = {hidden: { opacity: 0, y: 50 },visible: {opacity: 1,y: 0,transition: { delay: 1, duration: 0.6, ease: "easeOut" }},tapsss: {scale: 0.5}};return (<motion.divref={ref}variants={elementVariants}initial="hidden"whileTap="tapsss"animate={controls} // 使用 controls 控制动画style={{ width: 200, height: 200, backgroundColor: "green" }}>当我进入视野时,动画开始</motion.div>);
};
触发动画的方式之点击按钮
import { motion } from "framer-motion";
import { useState } from "react";// 示例:点击按钮开始动画
export default () => {const [isAnimating, setIsAnimating] = useState(false);// 定义动画变体(Variants)const boxVariants = {initial: { opacity: 0, scale: 0.5 },animate: { opacity: 1, scale: 1, transition: { duration: 0.5 } }};return (<div>{/* 通过点击事件控制动画 */}<motion.buttononClick={() => setIsAnimating(true)}>开始动画</motion.button><motion.divvariants={boxVariants}initial="initial" // 初始状态animate={isAnimating ? "animate" : "initial"} // 根据状态决定动画style={{ width: 100, height: 100, backgroundColor: "#38f" }}/></div>);
};
完整播放点击动画之一(点击别的元素触发动画)
用来做移动端按钮的点击效果不错
import { motion, useAnimation } from "framer-motion";
import { useState } from "react";
export default () => {const controls = useAnimation();const [isPlaying, setIsPlaying] = useState(false);// 手动触发一系列动画const startSequence = async () => {setIsPlaying(true);await controls.start({x: 100,backgroundColor: "#f00",transition: { duration: 0.5 }});await controls.start({y: 100,backgroundColor: "#0f0",transition: { duration: 0.5 }});await controls.start({x: 0,y: 0,backgroundColor: "#00f",transition: {duration: 0.5,ease: "backOut" // 添加一点弹性效果}});setIsPlaying(false);};return (<div><button onClick={startSequence} disabled={isPlaying}>{isPlaying ? "动画播放中..." : "开始序列动画"}</button><motion.divanimate={controls} // 手动控制的动画状态style={{width: 100,height: 100,backgroundColor: "blue",marginTop: 20}}/></div>);
};
完整播放点击动画之二(点击自身触发动画)
核心都是利用 const controls = useAnimation();控制动画
都是执行一个函数,里面用 controls.start开启动画
controls.start的实参不仅可以是对象,也可以是字符串(比如本文的例子"用useInView触发动画")
import { motion, useAnimation } from "framer-motion";
//点击按钮触发动画
//模拟whileTap,使点击按钮后,可以完整播放动画,而不是只播放一半
//重点是点击后,完整播放完动画
export default () => {const controls = useAnimation();const handleTap = async () => {// 在这里执行任意逻辑await controls.start({scale: 0.8,rotate: 30,transition: { duration: 0.2 }});await controls.start({scale: 1,rotate: 0,transition: { duration: 0.2, ease: 'backOut' }});};return (<motion.buttonanimate={controls}onTapStart={handleTap}style={{ padding: 20, background: "#666", color: "white" }}>点我(函数控制)</motion.button>);
};
父子动画
细节:非直接子元素也是可以有动画效果
细节2: 子元素继承父级的动画状态
子元素只有一个属性 <motion.div variants={itemVariants}>
:
虽然没有显式写
animate
或initial
,但它会隐式继承父级当前的状态名(即"visible"
或"hidden"
)。父级的状态名(如
"visible"
)会直接传递给子元素,子元素通过匹配itemVariants
中的同名状态(visible
或hidden
)来执行动画。
import { motion } from "framer-motion";
import { useState } from "react";// 示例:点击按钮开始动画
export default () => {const [isAnimating, setIsAnimating] = useState(false);const containerVariants = {hidden: { opacity: 0 },visible: {opacity: 1,transition: {// 父元素动画开始后,延迟1.2秒再开始子元素动画delayChildren: 1.2,// 每个子元素依次出现,间隔0.5秒staggerChildren: 0.5}}};const itemVariants = {hidden: { opacity: 0, x: -50 },visible: {opacity: 1, x: 0, transition: {duration: 0.5,ease: "backOut"}}};return (<div><button onClick={() => { setIsAnimating(!isAnimating) }}>按钮</button><motion.divinitial="hidden"animate={isAnimating ? "visible" : "hidden"}variants={containerVariants}style={{width: 300,height: 300,color: '#fff',backgroundColor: "blue",marginTop: 20}}><div><section><motion.div variants={itemVariants}>11111111111</motion.div><motion.div variants={itemVariants}>22222222222</motion.div><motion.div variants={itemVariants}>33333333333</motion.div><motion.div variants={itemVariants}>44444444444</motion.div></section></div></motion.div></div>);
};
whileInView一些API
import { motion } from "framer-motion";const buttonVariants = {yiGeMinZi: {y: [100, 0, 50], // 自定义关键帧backgroundColor: ['#f00', '#38f', '#ac1'],transition: {delay: 1,duration: 2,times: [0, 0.7, 1], // 70%时间用于0.8→1.2ease: "easeInOut"}}
};export default function HoverButton() {return (<motion.buttonvariants={buttonVariants}whileInView="yiGeMinZi"viewport={{ amount: 0.3, once: true }} // 30% 进入视口时触发,只触发一次initial={{ scale: 0.8 }}style={{ padding: "10px 20px" }}>悬停我</motion.button>);
}
利用whileInView的错开动画
viewport={{ once: true, margin: "10px" }}中的margin支持正负的百分比和px
custom给了更多的可能性
import { motion } from "framer-motion";const items = ["🍎", "🍌", "🍊", "🍇"];const itemVariants = {hidden: { opacity: 0, x: -50 },visible: (i) => ({opacity: 1,x: 0,transition: { delay: i * 0.1 }})
};export default function FruitList() {return (<div>{items.map((item, i) => (<motion.divkey={i}variants={itemVariants}initial="hidden"whileInView="visible"custom={i} // 传递索引控制延迟viewport={{ once: true, margin: "10px" }}style={{ margin: "20px" }}>{item}</motion.div>))}</div>);
}
元素离开屏幕检测方法
import { useRef, useEffect } from "react";
import { motion, useInView } from "framer-motion";const items = ["🍎", "🍌", "🍊", "🍇"];const itemVariants = {hidden: { opacity: 0, x: -50 },visible: (i) => ({opacity: 1,x: 0,transition: { delay: i * 0.1 }})
};export default function FruitList() {const ref = useRef(null);const isInView = useInView(ref); // 检测元素是否在视口内useEffect(() => {if (isInView) {console.log("元素111");// 在这里执行动画或逻辑} else {console.log("元素已离开视口");}}, [isInView]);return (<div ref={ref}>{items.map((item, i) => (<motion.divkey={i}variants={itemVariants}initial="hidden"whileInView="visible"custom={i} // 传递索引控制延迟viewport={{ once: true, margin: "10px" }}style={{ margin: "20px" }}>{item}</motion.div>))}</div>);
}