基础
import React from 'react';
import { motion } from 'framer-motion';
function App() {
const [isEnabled, setIsEnabled] = React.useState(true);
return (
<>
<motion.div
initial={false}
className="yellow ball"
transition={{
type: 'spring',
stiffness: 200,
damping: 25,
}}
animate={{
y: isEnabled ? 60 : 0,
}}
/>
<button onClick={() => setIsEnabled(!isEnabled)}>
Toggle
</button>
</>
);
}
export default App;animate 指定动画 —— 对它的所有修改都会有对应的动画效果
motion 支持对 所有 属性应用动画(数值、颜色、图片、shadow、可见性等)
注意:如果需要对元素的宽高的 auto 进行动画,不能使用 display: none - 需要改成 visibility: hidden
transition 指定了动画的方式
默认值与动画变更的值相关(For instance, physical properties like x or scale are animated with spring physics, whereas values like opacity or color are animated with duration-based easing curves.)
https://motion.dev/docs/react-transitions#transition-settings
默认 animate 会在元素出现时播放,设置initial 为 false 可以避免初始动画
或者initial 也可以指定另外一组属性值,元素出现时会播放 initial → animate 的动画
AnimatePresence
AnimatePresence 用于使得 exit 动画得以展示
mode
"sync": Children animate in/out as soon as they're added/removed.(默认)
"wait": The entering child will wait until the exiting child has animated out. Note: Currently only renders a single child at a time.
"popLayout": Exiting children will be "popped" out of the page layout. This allows surrounding elements to move to their new layout immediately.
对 height=auto 的元素高度变化应用动画
import { motion } from "framer-motion";
import { useState, useRef, useEffect } from "react";
import useMeasure from "react-use-measure";
export default function Example() {
const [showExtraContent, setShowExtraContent] = useState(false);
const [height, setHeight] = useState("auto");
const [elementRef, bounds] = useMeasure();
return (
<div className="wrapper">
<button className="button" onClick={() => setShowExtraContent((b) => !b)}>
Toggle height
</button>
<motion.div className="element" initial={false} animate={{ height: bounds.height || 'auto' }}>
<div className="inner" ref={elementRef}>
<h1>Fake Family Drawer</h1>
<p>
This is a fake family drawer. Animating height is tricky, but
satisfying when it works.
</p>
{showExtraContent ? (
<p>
This extra content will change the height of the drawer. Some even
more content to make the drawer taller and taller and taller...
</p>
) : null}
</div>
</motion.div>
</div>
);
}
其中的 useMeasure 是利用 ResizeObserver 实现的
const elementRef = useRef(null);
useEffect(() => {
const observer = new ResizeObserver(([entry]) => {
// const rect = entry.target.getBoundingClientRect();
//
// setBounds({
// width: rect.width,
// height: rect.height,
// });
const borderBoxSize = entry.borderBoxSize[0]
setBounds({
width: borderBoxSize.inlineSize,
height: borderBoxSize.blockSize,
});
});
if (elementRef.current) {
observer.observe(elementRef.current);
}
return () => observer.disconnect();
}, []);Variants
variants 有三个作用
统一动画参数的位置(统一定义好 variants,然后 initial animate 和 exit 只需指定 variant key 即可)
继承 —— 在父元素定义了 variants,子元素会自动继承
响应 state 的变化:variant 可以传递一个函数,用于响应 state 的变化(该 state 通过 custom 传入)
对于退出动画而言,因其已不在 react tree,所以元素上的 custom 不生效 —— 这时,可以通过在父元素的 AnimatePresence 上传递 custom
Layout Animation
指定 layout={true} (或可简写为 layout)即可为元素开启 Layout Animation;Layout Animation 使得元素获得在布局变化时得到动画的能力
当指定 layout={true} 时,等同于开启了 position + size,其中 position 意味着会对位置变动产生动画,size 意味着会对大小变动产生动画
layout animation 对于 height: auto 这种情况不生效;需要采用上述的特别方法
Layout Animation 采用的是 transform ,因此大小变动时会导致内部文字的大小变动;因此通常将文字相关元素特殊设置为 layout=”position” 来避免其大小改变
Shared Layout Animation
通过指定 layoutId 可以将不同的 dom 元素视为一个并应用 layout animation
当指定了 layoutId 时,layout={true} 可省略
应当尽可能保证 layoutId 唯一,因此通常需与 React.useId() 一同使用
当 layoutId 指定了 falsy value 之时,等同于未指定
该条目被触发通常为利用 item index 作为 layoutId - 而第一个 index 为 0 - 一个很合理的解决方案就是和 useId 一同使用
不知何故,当存在 layoutId 的元素同时存在 key 而 key 并非全局唯一时,可能会出现一些奇奇怪怪的问题;建议在可能的情况下将 key 与 layoutId 保持一致