说在前面
framer-motion 学起来其实有点费时间,不是说它复杂,而是说它的内容多,每个内容单独拆开学习都不复杂,但是每个内容都需要理解之后才能更好的应用,我们需要一步一步来。 本文较长,每个标题算是一个小节,每个小节大约花费 10 分钟左右的时间。建议一个小节阅读完之后吸收消化再阅读下一个小节,请不要着急,less is more, slow is fast。
简单动画 <animate>
动画的本质是状态的变化,简单的动画通常只有两种状态,一种是初始状态,一种是最终状态。大部分情况下,我们对动画的精细度要求没有那么高,所以通常的动画都只定义两个状态,initial 和 animate。比如:
import { motion } from "framer-motion"
export const MyComponent = () => (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
/>
)
表示 div 从最开始的 “完全透明且缩放 0.5” 的状态,过渡到 “不透明且无缩放” 的状态。 而且,实际上 framer-motion 官网给出的第一个例子甚至没有 initial 状态,猜测 framer-motion 应该会自动补充初始状态。所以如果你不需要多么精细地控制动画的话,直接给出 animate 属性就好。
但是有时候我们对于状态的变化是有更精细化的需求的。通常 framer-motion 会自动帮我们选择一种合适的状态变化过程(我们称为 transition),比如位置的变化通常是 spring,而颜色的变化则是 tween。如果你要再精细一点,你可以自己控制 transition,这个时候需要添加 transition 属性,比如:
import { motion } from "framer-motion"
export const MyComponent = () => (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
/>
)
transition 本身又有非常复杂的设置,值得单独写一章,暂时按下不表,感兴趣可以查看文档 www.framer.com/motion/tran…
常见的前端动画只需要这样设置就可以了。不过我们还需要更复杂和精细一点的控制,这个时候就需要 keyframes 了。其实就是 animate 的升级版。
更加精细的动画 <keyframes>
我们都知道,动画是很多帧状态之间的渐变生成的,所以我们不仅可以像上述一样仅仅定义两个状态,我们可以定义一连串的状态,让一个东西按照顺序逐渐发生变化,比如:
import { motion } from "framer-motion"
export const MyComponent = () => (
<motion.div
animate={{
scale: [1, 2, 2, 1, 1],
rotate: [0, 0, 270, 270, 0],
borderRadius: ["20%", "20%", "50%", "50%", "20%"],
}}
/>
)
总共有 5 个状态,按照从头到尾的顺序分别是:
{scale: 1, rotate: 0, borderRadius: '20%'}
{scale: 2, rotate: 0, borderRadius: '20%'}
{scale: 2, rotate: 270, borderRadius: '50%'}
{scale: 1, rotate: 270, borderRadius: '50%'}
{scale: 1, rotate: 0, borderRadius: '20%'}
那么这个 div 会按照状态从 1 -> 2 -> 3 -> 4 -> 5 的顺序逐渐的变化,你可以依次想象一下每个状态,然后就能感受到这个动画的效果,再把代码运行一下看看实际的效果,对比一下。
这里,你也可以再精细化的添加 transition 属性,更加明确的定义状态之间的变化应该如何进行。
to be continued...
更加复杂的动画 <variants>
以上只是针对一个 html 元素(比如 div)进行动画的设置,有时候我们希望一个动画是一个系统,就是一个 DOM 树的整体进行动画展示,不仅是它自己,还包括它的子元素。这个时候就需要 variants。variants 简单来说也是定义两个状态,只不过这两个状态针对不同的元素有不同的效果。
我们先看一个针对单一元素的例子:
import { motion } from "framer-motion"
const variants = {
open: { opacity: 1, x: 0 },
closed: { opacity: 0, x: "-100%" },
}
export const MyComponent = () => {
const [isOpen, setIsOpen] = useState(false)
return (
<motion.nav
animate={isOpen ? "open" : "closed"}
variants={variants}
>
<Toggle onClick={() => setIsOpen(isOpen => !isOpen)} />
<Items />
</motion.nav>
)
}
这个表示我针对 motion.nav 元素进行动画展示,nav 有两个状态,一个是 open,一个是 closed。当 open 的时候使用 variants.open 属性,当 closed 的时候使用 variants.closed 属性。这个其实没什么,用上面的两种方式也可以设置,不过这个 variants 最好的一点就是它不止针对当前元素,还针对元素的子元素,也就是 Items 中的任何元素也可以设置 open 和 closed 的 variants,这样就方便了,也统一了。 而且你还可以在 transition 中通过 staggerChildren 的设置让元素都有一个延迟实现效果的方式。 其实 variants 的本质依然是定义两个状态,只不过这两个状态是可以作用在整个 tree 上面,大家一起配合完成的一个更加复杂的动画。
自定义组件
不知道是不是支持第三方组件? www.framer.com/motion/comp…
to be continued...