BetaThis is a live doc! Anyone with edit access can make updates in real time without having to publish.
By Bryan

基础

1import React from 'react'; 2import { motion } from 'framer-motion'; 3 4function App() { 5 const [isEnabled, setIsEnabled] = React.useState(true); 6 7 return ( 8 <> 9 <motion.div 10 initial={false} 11 className="yellow ball" 12 transition={{ 13 type: 'spring', 14 stiffness: 200, 15 damping: 25, 16 }} 17 animate={{ 18 y: isEnabled ? 60 : 0, 19 }} 20 /> 21 <button onClick={() => setIsEnabled(!isEnabled)}> 22 Toggle 23 </button> 24 </> 25 ); 26} 27 28export default App;
  • animate 指定动画 —— 对它的所有修改都会有对应的动画效果

    • motion 支持对 所有 属性应用动画(数值、颜色、图片、shadow、可见性等)

      • 注意:如果需要对元素的宽高的 auto 进行动画,不能使用 display: none - 需要改成 visibility: hidden

  • transition 指定了动画的方式

  • 默认 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 的元素高度变化应用动画

1import { motion } from "framer-motion"; 2import { useState, useRef, useEffect } from "react"; 3import useMeasure from "react-use-measure"; 4 5export default function Example() { 6 const [showExtraContent, setShowExtraContent] = useState(false); 7 8 const [height, setHeight] = useState("auto"); 9 const [elementRef, bounds] = useMeasure(); 10 11 return ( 12 <div className="wrapper"> 13 <button className="button" onClick={() => setShowExtraContent((b) => !b)}> 14 Toggle height 15 </button> 16 <motion.div className="element" initial={false} animate={{ height: bounds.height || 'auto' }}> 17 <div className="inner" ref={elementRef}> 18 <h1>Fake Family Drawer</h1> 19 <p> 20 This is a fake family drawer. Animating height is tricky, but 21 satisfying when it works. 22 </p> 23 {showExtraContent ? ( 24 <p> 25 This extra content will change the height of the drawer. Some even 26 more content to make the drawer taller and taller and taller... 27 </p> 28 ) : null} 29 </div> 30 </motion.div> 31 </div> 32 ); 33} 34

其中的 useMeasure 是利用 ResizeObserver 实现的

1const elementRef = useRef(null); 2 3useEffect(() => { 4 const observer = new ResizeObserver(([entry]) => { 5 // const rect = entry.target.getBoundingClientRect(); 6 // 7 // setBounds({ 8 // width: rect.width, 9 // height: rect.height, 10 // }); 11 12 const borderBoxSize = entry.borderBoxSize[0] 13 14 setBounds({ 15 width: borderBoxSize.inlineSize, 16 height: borderBoxSize.blockSize, 17 }); 18 }); 19 20 if (elementRef.current) { 21 observer.observe(elementRef.current); 22 } 23 24 return () => observer.disconnect(); 25 }, []);

Variants

variants 有三个作用

  1. 统一动画参数的位置(统一定义好 variants,然后 initial animateexit 只需指定 variant key 即可)

  2. 继承 —— 在父元素定义了 variants,子元素会自动继承

  3. 响应 state 的变化:variant 可以传递一个函数,用于响应 state 的变化(该 state 通过 custom 传入)

    1. 对于退出动画而言,因其已不在 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 并非全局唯一时,可能会出现一些奇奇怪怪的问题;建议在可能的情况下将 keylayoutId 保持一致