升级指南
我们力求减少破坏性的 API 更改,但偶尔这是必要的。
升级的最简单方法是从您当前使用的版本开始,然后按照指南升级到下一个版本,依此类推,直到您达到最新版本。
主要版本之间的更改通常很小,因此这通常是一个快速的过程。
Motion for React
12.0
Motion for React 版本 12 中没有破坏性更改。请参阅JavaScript 升级指南以了解 vanilla JS API 的更改。
"motion/react"
要升级到 Motion for React,请卸载 framer-motion
并安装 motion
npm uninstall framer-motion npm install motion
然后只需将导入从 "framer-motion"
替换为 "motion/react"
import { motion } from "motion/react"
Framer Motion
11.0
速度计算更改
在以前的版本中,在同一动画帧内多次设置 MotionValue
会更新该值的速度
const x = motionValue(0) requestAnimationFrame(() => { x.set(100) x.getVelocity() // Velocity of 0 -> 100 x.set(200) x.getVelocity() // Velocity of 100 -> 200 })
这种行为是不正确的。实际上,对于动画的目的而言,同步代码应被视为瞬时的。因此,在上面的示例中,x
仅在无限短的时间内为 100
。它基本上从未发生过。
从版本 11 开始,同步代码块内的后续值更新将不被视为 MotionValue
速度计算的一部分。因此,如果在第二次更新后调用 getVelocity
,则速度将在最新值和上一帧末尾的值之间计算。
const x = motionValue(0) requestAnimationFrame(() => { x.set(100) x.getVelocity() // Velocity of 0 -> 100 x.set(200) x.getVelocity() // Velocity of 0 -> 200 })
渲染调度更改
在以前的版本中,motion
组件在挂载后同步触发渲染,以确保动态计算的值在屏幕上更新。此过程现在已移至微任务.
这确保了如果组件被 useLayoutEffect
同步重新渲染,则第一次渲染将被吞噬,我们只应用最终的渲染(将在屏幕上使用的渲染)。
这对于性能更好,并且在大多数情况下不会对您作为开发人员产生实际影响。但是,Jest 测试有一个注意事项。以前可以假设更新将同步应用。
render( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ false }} /> ) expect(element).toHaveStyle("opacity: 1")
应更新此类测试以等待动画帧。
render( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ false }} /> ) await nextFrame() expect(element).toHaveStyle("opacity: 1") // utils.js import { frame } from "framer-motion" export async function nextFrame() { return new Promise<void>((resolve) => { frame.postRender(() => resolve()) }) }
10.0
IntersectionObserver
回退
此版本移除了 whileInView
的 IntersectionObserver
回退行为。
IntersectionObserver
受所有现代浏览器支持,代表了访问在以下网站上构建的网站的访问者的 99% 以上Framer。如果您需要支持旧版浏览器(如 Internet Explorer 或 Safari 12),我们建议添加 IntersectionObserver
polyfill。
AnimatePresence exitBeforeEnter
属性
此属性已在 7.2.0
中弃用。现在使用将抛出带有升级说明的错误(切换到 mode="wait"
)。
9.0
此版本使 tap 事件可键盘访问。
因此,所有带有 tap 监听器或 whileTap
的元素都将接收 tabindex="0"
。不鼓励恢复此行为,但可以通过传递 tabIndex={-1}
来实现。
此外,whileFocus
现在的行为类似于 :focus-visible
而不是 :focus
。 粗略地说,这意味着通过指针接收焦点的元素不会触发焦点动画,但输入元素除外,输入元素将从任何输入触发焦点。
8.0
Framer Motion 使用指针事件来检测 tap、drag 和 hover 手势。在以前的版本中,这些在旧版浏览器中使用鼠标和触摸事件进行了 polyfill。版本 8 移除了此 polyfill。
因此,虽然DragControls.start始终仅记录为与来自 onPointerDown
的事件一起使用,但它被类型化为也接受 onMouseDown
和 onTouchStart
事件。这些现在将为 TypeScript 用户抛出类型错误,应转换为 onPointerDown
。
7.0
Framer Motion 7 使 react@18
成为最低支持版本。
Framer Motion 3D 用户也应该升级 React Three Fiber到 ^8.2.2
。
6.0
Framer Motion 3D 现在位于 framer-motion-3d
包中。因此,要升级到 6.0
,只需将导入从 "framer-motion/three"
更改为 "framer-motion-3d"
。
5.0
共享布局动画
Framer Motion 5 移除了 AnimateSharedLayout
组件。
现在,您可以使用 layoutId
属性,组件将从一个动画到另一个,而无需 AnimateSharedLayout
包装器。
测量布局更改
当具有 layout
或 layoutId
属性的组件重新渲染时,会检测到布局更改。但是,仅当 一个组件更改时就测量 所有组件不是高性能的。
AnimateSharedLayout
可用于对影响彼此布局的组件进行分组。当一个组件重新渲染时,AnimateSharedLayout
将强制它们全部重新渲染。
这不是一种高性能的方法,因为所有分组的组件都将执行重新渲染。现在,可以分组影响彼此布局的组件使用 LayoutGroup:
import { LayoutGroup, motion } from "framer-motion" export function App() { return ( <LayoutGroup> <Submenu /> <Submenu /> </LayoutGroup> ) } function Submenu({ children }) { const [isOpen, setIsOpen] = useState(false) return ( <motion.ul layout style={{ height: isOpen ? "auto" : 40 }} > {children} </motion.ul> ) }
每当其中一个组件渲染时,都会测量分组的组件,但不会强制它们自身渲染。
作用域布局动画
以前,由于需要 AnimateSharedLayout
,因此它自然会限定共享布局动画的作用域。因此,在具有相同 layoutId
的组件之间进行动画处理只会发生在同一 AnimateSharedLayout
内
/** * These items share the same layoutId but won't animate * between each other because they're children of different * AnimateSharedLayout components. */ <> <AnimateSharedLayout> {isVisible ? <motion.div layoutId="modal" /> : null} </AnimateSharedLayout> <AnimateSharedLayout> {isVisible ? <motion.div layoutId="modal" /> : null} </AnimateSharedLayout> </>
这可能会导致非常差的性能。AnimateSharedLayout
通过批量处理布局测量来减少自身内部的布局抖动。但是它无法批量处理许多 AnimateSharedLayout
组件之间的布局抖动。您添加的越多,布局抖动就越多。
现在,您的应用程序中有一个全局树,因此所有布局测量都已批量处理。但这意味着所有 layoutId
都共享相同的全局上下文。要恢复此旧行为,您可以通过向 LayoutGroup
提供 id
属性来命名空间 layoutId
/** * These layoutIds are now namespaced with * the id provided to LayoutGroup. */ <> <LayoutGroup id="a"> {isVisible ? <motion.div layoutId="modal" /> : null} </LayoutGroup> <LayoutGroup id="b"> {isVisible ? <motion.div layoutId="modal" /> : null} </LayoutGroup> </>
拖动以重新排序
以前的拖动重新排序实现是临时的,通常改编自旧的概念验证沙箱,该沙箱依赖于(现在已删除的)onViewportBoxUpdate
属性。这些解决方案应使用以下内容重新实现新的 Reorder 组件.
ESM 和 create-react-app
为了启用 Framer 的实验性“Handshake”功能,该功能允许您直接从 Framer 将无代码组件发布到生产环境,我们将 Framer Motion 迁移到了 ESM 模块。某些构建环境(如 create-react-app
)在混合 ES 模块(如 Framer Motion)和 CJS 模块(如 React)时可能会遇到一些问题。
要修复此问题,请升级到 create-react-app@next
,或降级到 framer-motion@4.1.17
。
4.0
Framer Motion 4 引入了全新的 LazyMotion
组件,以帮助减少包大小。
以前,可以通过 MotionConfig
的 features
属性同步或异步加载 motion
功能的子集。此功能已移除,取而代之的是新的 LazyMotion
组件。
查看新的 减少包大小 指南以了解如何使用此新 API。
import { LazyMotion, domAnimation, m } from "framer-motion" export const MyComponent = ({ isVisible }) => ( <LazyMotion features={domAnimation}> <m.div animate={{ opacity: 1 }} /> </LazyMotion> )
其他破坏性更改
4
还移除了 motion.custom()
,该函数之前已弃用,取而代之的是 motion()
。
motion.custom()
的默认行为是将 Framer Motion 的所有属性转发到基础组件。要复制此行为,可以使用 forwardMotionProps
选项。
const MotionComponent = motion(Component, { forwardMotionProps: true })
3.0
Framer Motion 3 是主要版本,但破坏性更改的类型非常具体且非常小。虽然有可能,但不太可能改变动画的运作方式。
正在改变的行为
Motion 3 的特点是动画状态计算方式的集中化。
现在,所有动画属性都按优先级排序(左侧最低,右侧最高)。
当其中一个属性更改或变为活动/非活动状态时,我们将重新计算必要的动画。这是部分仅为 while
属性实现的扩展和编纂,从而带来更一致和可预测的体验。
const priority = ["animate", "while-", "exit"]
移除动画值
之前,如果从动画属性中完全删除一个值,则不会发生任何事情。
现在,如果删除了一个值,我们会在下一个最高优先级的动画状态中检查它。例如,如果从 whileHover
中删除了 opacity
,Motion 将在 animate
中检查它并动画到该值。
如果我们在 animate
中找不到一个,它将在 style
中检查,或者回退到其最初记录的值(例如,如果该值最初是从 DOM 中读取的,因为没有明确定义)。
2.0
Framer Motion 2 是主要版本,这意味着存在 API 更改。在本指南中,我们将了解如何升级您的代码以确保其继续按预期工作,并重点介绍 Motion 新版本中将要破坏的一些功能。
布局动画
Framer Motion 1 支持几种执行布局动画的方法,即 positionTransition
和 layoutTransition
属性。
// Before <motion.div layoutTransition />
在 Framer Motion 2 中,这些都已被 layout
属性取代。
// After <motion.div layout />
旧的属性都使用过渡作为参数。
// Before <motion.div layoutTransition={{ duration: 2 }} />
现在,布局动画使用与其他动画相同的默认 transition
属性。
// After <motion.div layout transition={{ duration: 2 }} />
在 Framer Motion 1 中,布局动画可能会扭曲正在更改大小的组件上的 borderRadius
和 boxShadow
属性。如果任何一个属性被动画化,则现在已修复。
<motion.div layout initial={{ borderRadius: 20 }} />
更改大小的布局动画也可能扭曲子组件。现在可以通过为它们提供 layout
属性来纠正这一点。
只有直接子元素才需要针对缩放进行纠正。
<motion.div layout> <motion.div layout /> </motion.div>
破坏性更改
在升级之前,您应该注意一些没有立即修复方法的更改。
拖动
拖动已重构为使用与 Motion 2 的布局动画相同的布局投影渲染方法,以确保这两个功能彼此完全兼容。
这导致了一些破坏性更改
拖动监听器(如
onDrag
)现在报告相对于视口的point
,与 Motion 中的其他指针手势保持一致。dragOriginX
和dragOriginY
已被移除。添加这些是为了允许一种 hacky 的方法使positionTransition
与drag
兼容,但layout
默认与drag
兼容。
useAnimatedState
useAnimatedState
API 是一个实验性的、未记录的 API,用于 Framer X。现在已移除。
我们力求减少破坏性的 API 更改,但偶尔这是必要的。
升级的最简单方法是从您当前使用的版本开始,然后按照指南升级到下一个版本,依此类推,直到您达到最新版本。
主要版本之间的更改通常很小,因此这通常是一个快速的过程。
Motion for React
12.0
Motion for React 版本 12 中没有破坏性更改。请参阅JavaScript 升级指南以了解 vanilla JS API 的更改。
"motion/react"
要升级到 Motion for React,请卸载 framer-motion
并安装 motion
npm uninstall framer-motion npm install motion
然后只需将导入从 "framer-motion"
替换为 "motion/react"
import { motion } from "motion/react"
Framer Motion
11.0
速度计算更改
在以前的版本中,在同一动画帧内多次设置 MotionValue
会更新该值的速度
const x = motionValue(0) requestAnimationFrame(() => { x.set(100) x.getVelocity() // Velocity of 0 -> 100 x.set(200) x.getVelocity() // Velocity of 100 -> 200 })
这种行为是不正确的。实际上,对于动画的目的而言,同步代码应被视为瞬时的。因此,在上面的示例中,x
仅在无限短的时间内为 100
。它基本上从未发生过。
从版本 11 开始,同步代码块内的后续值更新将不被视为 MotionValue
速度计算的一部分。因此,如果在第二次更新后调用 getVelocity
,则速度将在最新值和上一帧末尾的值之间计算。
const x = motionValue(0) requestAnimationFrame(() => { x.set(100) x.getVelocity() // Velocity of 0 -> 100 x.set(200) x.getVelocity() // Velocity of 0 -> 200 })
渲染调度更改
在以前的版本中,motion
组件在挂载后同步触发渲染,以确保动态计算的值在屏幕上更新。此过程现在已移至微任务.
这确保了如果组件被 useLayoutEffect
同步重新渲染,则第一次渲染将被吞噬,我们只应用最终的渲染(将在屏幕上使用的渲染)。
这对于性能更好,并且在大多数情况下不会对您作为开发人员产生实际影响。但是,Jest 测试有一个注意事项。以前可以假设更新将同步应用。
render( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ false }} /> ) expect(element).toHaveStyle("opacity: 1")
应更新此类测试以等待动画帧。
render( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ false }} /> ) await nextFrame() expect(element).toHaveStyle("opacity: 1") // utils.js import { frame } from "framer-motion" export async function nextFrame() { return new Promise<void>((resolve) => { frame.postRender(() => resolve()) }) }
10.0
IntersectionObserver
回退
此版本移除了 whileInView
的 IntersectionObserver
回退行为。
IntersectionObserver
受所有现代浏览器支持,代表了访问在以下网站上构建的网站的访问者的 99% 以上Framer。如果您需要支持旧版浏览器(如 Internet Explorer 或 Safari 12),我们建议添加 IntersectionObserver
polyfill。
AnimatePresence exitBeforeEnter
属性
此属性已在 7.2.0
中弃用。现在使用将抛出带有升级说明的错误(切换到 mode="wait"
)。
9.0
此版本使 tap 事件可键盘访问。
因此,所有带有 tap 监听器或 whileTap
的元素都将接收 tabindex="0"
。不鼓励恢复此行为,但可以通过传递 tabIndex={-1}
来实现。
此外,whileFocus
现在的行为类似于 :focus-visible
而不是 :focus
。 粗略地说,这意味着通过指针接收焦点的元素不会触发焦点动画,但输入元素除外,输入元素将从任何输入触发焦点。
8.0
Framer Motion 使用指针事件来检测 tap、drag 和 hover 手势。在以前的版本中,这些在旧版浏览器中使用鼠标和触摸事件进行了 polyfill。版本 8 移除了此 polyfill。
因此,虽然DragControls.start始终仅记录为与来自 onPointerDown
的事件一起使用,但它被类型化为也接受 onMouseDown
和 onTouchStart
事件。这些现在将为 TypeScript 用户抛出类型错误,应转换为 onPointerDown
。
7.0
Framer Motion 7 使 react@18
成为最低支持版本。
Framer Motion 3D 用户也应该升级 React Three Fiber到 ^8.2.2
。
6.0
Framer Motion 3D 现在位于 framer-motion-3d
包中。因此,要升级到 6.0
,只需将导入从 "framer-motion/three"
更改为 "framer-motion-3d"
。
5.0
共享布局动画
Framer Motion 5 移除了 AnimateSharedLayout
组件。
现在,您可以使用 layoutId
属性,组件将从一个动画到另一个,而无需 AnimateSharedLayout
包装器。
测量布局更改
当具有 layout
或 layoutId
属性的组件重新渲染时,会检测到布局更改。但是,仅当 一个组件更改时就测量 所有组件不是高性能的。
AnimateSharedLayout
可用于对影响彼此布局的组件进行分组。当一个组件重新渲染时,AnimateSharedLayout
将强制它们全部重新渲染。
这不是一种高性能的方法,因为所有分组的组件都将执行重新渲染。现在,可以分组影响彼此布局的组件使用 LayoutGroup:
import { LayoutGroup, motion } from "framer-motion" export function App() { return ( <LayoutGroup> <Submenu /> <Submenu /> </LayoutGroup> ) } function Submenu({ children }) { const [isOpen, setIsOpen] = useState(false) return ( <motion.ul layout style={{ height: isOpen ? "auto" : 40 }} > {children} </motion.ul> ) }
每当其中一个组件渲染时,都会测量分组的组件,但不会强制它们自身渲染。
作用域布局动画
以前,由于需要 AnimateSharedLayout
,因此它自然会限定共享布局动画的作用域。因此,在具有相同 layoutId
的组件之间进行动画处理只会发生在同一 AnimateSharedLayout
内
/** * These items share the same layoutId but won't animate * between each other because they're children of different * AnimateSharedLayout components. */ <> <AnimateSharedLayout> {isVisible ? <motion.div layoutId="modal" /> : null} </AnimateSharedLayout> <AnimateSharedLayout> {isVisible ? <motion.div layoutId="modal" /> : null} </AnimateSharedLayout> </>
这可能会导致非常差的性能。AnimateSharedLayout
通过批量处理布局测量来减少自身内部的布局抖动。但是它无法批量处理许多 AnimateSharedLayout
组件之间的布局抖动。您添加的越多,布局抖动就越多。
现在,您的应用程序中有一个全局树,因此所有布局测量都已批量处理。但这意味着所有 layoutId
都共享相同的全局上下文。要恢复此旧行为,您可以通过向 LayoutGroup
提供 id
属性来命名空间 layoutId
/** * These layoutIds are now namespaced with * the id provided to LayoutGroup. */ <> <LayoutGroup id="a"> {isVisible ? <motion.div layoutId="modal" /> : null} </LayoutGroup> <LayoutGroup id="b"> {isVisible ? <motion.div layoutId="modal" /> : null} </LayoutGroup> </>
拖动以重新排序
以前的拖动重新排序实现是临时的,通常改编自旧的概念验证沙箱,该沙箱依赖于(现在已删除的)onViewportBoxUpdate
属性。这些解决方案应使用以下内容重新实现新的 Reorder 组件.
ESM 和 create-react-app
为了启用 Framer 的实验性“Handshake”功能,该功能允许您直接从 Framer 将无代码组件发布到生产环境,我们将 Framer Motion 迁移到了 ESM 模块。某些构建环境(如 create-react-app
)在混合 ES 模块(如 Framer Motion)和 CJS 模块(如 React)时可能会遇到一些问题。
要修复此问题,请升级到 create-react-app@next
,或降级到 framer-motion@4.1.17
。
4.0
Framer Motion 4 引入了全新的 LazyMotion
组件,以帮助减少包大小。
以前,可以通过 MotionConfig
的 features
属性同步或异步加载 motion
功能的子集。此功能已移除,取而代之的是新的 LazyMotion
组件。
查看新的 减少包大小 指南以了解如何使用此新 API。
import { LazyMotion, domAnimation, m } from "framer-motion" export const MyComponent = ({ isVisible }) => ( <LazyMotion features={domAnimation}> <m.div animate={{ opacity: 1 }} /> </LazyMotion> )
其他破坏性更改
4
还移除了 motion.custom()
,该函数之前已弃用,取而代之的是 motion()
。
motion.custom()
的默认行为是将 Framer Motion 的所有属性转发到基础组件。要复制此行为,可以使用 forwardMotionProps
选项。
const MotionComponent = motion(Component, { forwardMotionProps: true })
3.0
Framer Motion 3 是主要版本,但破坏性更改的类型非常具体且非常小。虽然有可能,但不太可能改变动画的运作方式。
正在改变的行为
Motion 3 的特点是动画状态计算方式的集中化。
现在,所有动画属性都按优先级排序(左侧最低,右侧最高)。
当其中一个属性更改或变为活动/非活动状态时,我们将重新计算必要的动画。这是部分仅为 while
属性实现的扩展和编纂,从而带来更一致和可预测的体验。
const priority = ["animate", "while-", "exit"]
移除动画值
之前,如果从动画属性中完全删除一个值,则不会发生任何事情。
现在,如果删除了一个值,我们会在下一个最高优先级的动画状态中检查它。例如,如果从 whileHover
中删除了 opacity
,Motion 将在 animate
中检查它并动画到该值。
如果我们在 animate
中找不到一个,它将在 style
中检查,或者回退到其最初记录的值(例如,如果该值最初是从 DOM 中读取的,因为没有明确定义)。
2.0
Framer Motion 2 是主要版本,这意味着存在 API 更改。在本指南中,我们将了解如何升级您的代码以确保其继续按预期工作,并重点介绍 Motion 新版本中将要破坏的一些功能。
布局动画
Framer Motion 1 支持几种执行布局动画的方法,即 positionTransition
和 layoutTransition
属性。
// Before <motion.div layoutTransition />
在 Framer Motion 2 中,这些都已被 layout
属性取代。
// After <motion.div layout />
旧的属性都使用过渡作为参数。
// Before <motion.div layoutTransition={{ duration: 2 }} />
现在,布局动画使用与其他动画相同的默认 transition
属性。
// After <motion.div layout transition={{ duration: 2 }} />
在 Framer Motion 1 中,布局动画可能会扭曲正在更改大小的组件上的 borderRadius
和 boxShadow
属性。如果任何一个属性被动画化,则现在已修复。
<motion.div layout initial={{ borderRadius: 20 }} />
更改大小的布局动画也可能扭曲子组件。现在可以通过为它们提供 layout
属性来纠正这一点。
只有直接子元素才需要针对缩放进行纠正。
<motion.div layout> <motion.div layout /> </motion.div>
破坏性更改
在升级之前,您应该注意一些没有立即修复方法的更改。
拖动
拖动已重构为使用与 Motion 2 的布局动画相同的布局投影渲染方法,以确保这两个功能彼此完全兼容。
这导致了一些破坏性更改
拖动监听器(如
onDrag
)现在报告相对于视口的point
,与 Motion 中的其他指针手势保持一致。dragOriginX
和dragOriginY
已被移除。添加这些是为了允许一种 hacky 的方法使positionTransition
与drag
兼容,但layout
默认与drag
兼容。
useAnimatedState
useAnimatedState
API 是一个实验性的、未记录的 API,用于 Framer X。现在已移除。
我们力求减少破坏性的 API 更改,但偶尔这是必要的。
升级的最简单方法是从您当前使用的版本开始,然后按照指南升级到下一个版本,依此类推,直到您达到最新版本。
主要版本之间的更改通常很小,因此这通常是一个快速的过程。
Motion for React
12.0
Motion for React 版本 12 中没有破坏性更改。请参阅JavaScript 升级指南以了解 vanilla JS API 的更改。
"motion/react"
要升级到 Motion for React,请卸载 framer-motion
并安装 motion
npm uninstall framer-motion npm install motion
然后只需将导入从 "framer-motion"
替换为 "motion/react"
import { motion } from "motion/react"
Framer Motion
11.0
速度计算更改
在以前的版本中,在同一动画帧内多次设置 MotionValue
会更新该值的速度
const x = motionValue(0) requestAnimationFrame(() => { x.set(100) x.getVelocity() // Velocity of 0 -> 100 x.set(200) x.getVelocity() // Velocity of 100 -> 200 })
这种行为是不正确的。实际上,对于动画的目的而言,同步代码应被视为瞬时的。因此,在上面的示例中,x
仅在无限短的时间内为 100
。它基本上从未发生过。
从版本 11 开始,同步代码块内的后续值更新将不被视为 MotionValue
速度计算的一部分。因此,如果在第二次更新后调用 getVelocity
,则速度将在最新值和上一帧末尾的值之间计算。
const x = motionValue(0) requestAnimationFrame(() => { x.set(100) x.getVelocity() // Velocity of 0 -> 100 x.set(200) x.getVelocity() // Velocity of 0 -> 200 })
渲染调度更改
在以前的版本中,motion
组件在挂载后同步触发渲染,以确保动态计算的值在屏幕上更新。此过程现在已移至微任务.
这确保了如果组件被 useLayoutEffect
同步重新渲染,则第一次渲染将被吞噬,我们只应用最终的渲染(将在屏幕上使用的渲染)。
这对于性能更好,并且在大多数情况下不会对您作为开发人员产生实际影响。但是,Jest 测试有一个注意事项。以前可以假设更新将同步应用。
render( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ false }} /> ) expect(element).toHaveStyle("opacity: 1")
应更新此类测试以等待动画帧。
render( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ false }} /> ) await nextFrame() expect(element).toHaveStyle("opacity: 1") // utils.js import { frame } from "framer-motion" export async function nextFrame() { return new Promise<void>((resolve) => { frame.postRender(() => resolve()) }) }
10.0
IntersectionObserver
回退
此版本移除了 whileInView
的 IntersectionObserver
回退行为。
IntersectionObserver
受所有现代浏览器支持,代表了访问在以下网站上构建的网站的访问者的 99% 以上Framer。如果您需要支持旧版浏览器(如 Internet Explorer 或 Safari 12),我们建议添加 IntersectionObserver
polyfill。
AnimatePresence exitBeforeEnter
属性
此属性已在 7.2.0
中弃用。现在使用将抛出带有升级说明的错误(切换到 mode="wait"
)。
9.0
此版本使 tap 事件可键盘访问。
因此,所有带有 tap 监听器或 whileTap
的元素都将接收 tabindex="0"
。不鼓励恢复此行为,但可以通过传递 tabIndex={-1}
来实现。
此外,whileFocus
现在的行为类似于 :focus-visible
而不是 :focus
。 粗略地说,这意味着通过指针接收焦点的元素不会触发焦点动画,但输入元素除外,输入元素将从任何输入触发焦点。
8.0
Framer Motion 使用指针事件来检测 tap、drag 和 hover 手势。在以前的版本中,这些在旧版浏览器中使用鼠标和触摸事件进行了 polyfill。版本 8 移除了此 polyfill。
因此,虽然DragControls.start始终仅记录为与来自 onPointerDown
的事件一起使用,但它被类型化为也接受 onMouseDown
和 onTouchStart
事件。这些现在将为 TypeScript 用户抛出类型错误,应转换为 onPointerDown
。
7.0
Framer Motion 7 使 react@18
成为最低支持版本。
Framer Motion 3D 用户也应该升级 React Three Fiber到 ^8.2.2
。
6.0
Framer Motion 3D 现在位于 framer-motion-3d
包中。因此,要升级到 6.0
,只需将导入从 "framer-motion/three"
更改为 "framer-motion-3d"
。
5.0
共享布局动画
Framer Motion 5 移除了 AnimateSharedLayout
组件。
现在,您可以使用 layoutId
属性,组件将从一个动画到另一个,而无需 AnimateSharedLayout
包装器。
测量布局更改
当具有 layout
或 layoutId
属性的组件重新渲染时,会检测到布局更改。但是,仅当 一个组件更改时就测量 所有组件不是高性能的。
AnimateSharedLayout
可用于对影响彼此布局的组件进行分组。当一个组件重新渲染时,AnimateSharedLayout
将强制它们全部重新渲染。
这不是一种高性能的方法,因为所有分组的组件都将执行重新渲染。现在,可以分组影响彼此布局的组件使用 LayoutGroup:
import { LayoutGroup, motion } from "framer-motion" export function App() { return ( <LayoutGroup> <Submenu /> <Submenu /> </LayoutGroup> ) } function Submenu({ children }) { const [isOpen, setIsOpen] = useState(false) return ( <motion.ul layout style={{ height: isOpen ? "auto" : 40 }} > {children} </motion.ul> ) }
每当其中一个组件渲染时,都会测量分组的组件,但不会强制它们自身渲染。
作用域布局动画
以前,由于需要 AnimateSharedLayout
,因此它自然会限定共享布局动画的作用域。因此,在具有相同 layoutId
的组件之间进行动画处理只会发生在同一 AnimateSharedLayout
内
/** * These items share the same layoutId but won't animate * between each other because they're children of different * AnimateSharedLayout components. */ <> <AnimateSharedLayout> {isVisible ? <motion.div layoutId="modal" /> : null} </AnimateSharedLayout> <AnimateSharedLayout> {isVisible ? <motion.div layoutId="modal" /> : null} </AnimateSharedLayout> </>
这可能会导致非常差的性能。AnimateSharedLayout
通过批量处理布局测量来减少自身内部的布局抖动。但是它无法批量处理许多 AnimateSharedLayout
组件之间的布局抖动。您添加的越多,布局抖动就越多。
现在,您的应用程序中有一个全局树,因此所有布局测量都已批量处理。但这意味着所有 layoutId
都共享相同的全局上下文。要恢复此旧行为,您可以通过向 LayoutGroup
提供 id
属性来命名空间 layoutId
/** * These layoutIds are now namespaced with * the id provided to LayoutGroup. */ <> <LayoutGroup id="a"> {isVisible ? <motion.div layoutId="modal" /> : null} </LayoutGroup> <LayoutGroup id="b"> {isVisible ? <motion.div layoutId="modal" /> : null} </LayoutGroup> </>
拖动以重新排序
以前的拖动重新排序实现是临时的,通常改编自旧的概念验证沙箱,该沙箱依赖于(现在已删除的)onViewportBoxUpdate
属性。这些解决方案应使用以下内容重新实现新的 Reorder 组件.
ESM 和 create-react-app
为了启用 Framer 的实验性“Handshake”功能,该功能允许您直接从 Framer 将无代码组件发布到生产环境,我们将 Framer Motion 迁移到了 ESM 模块。某些构建环境(如 create-react-app
)在混合 ES 模块(如 Framer Motion)和 CJS 模块(如 React)时可能会遇到一些问题。
要修复此问题,请升级到 create-react-app@next
,或降级到 framer-motion@4.1.17
。
4.0
Framer Motion 4 引入了全新的 LazyMotion
组件,以帮助减少包大小。
以前,可以通过 MotionConfig
的 features
属性同步或异步加载 motion
功能的子集。此功能已移除,取而代之的是新的 LazyMotion
组件。
查看新的 减少包大小 指南以了解如何使用此新 API。
import { LazyMotion, domAnimation, m } from "framer-motion" export const MyComponent = ({ isVisible }) => ( <LazyMotion features={domAnimation}> <m.div animate={{ opacity: 1 }} /> </LazyMotion> )
其他破坏性更改
4
还移除了 motion.custom()
,该函数之前已弃用,取而代之的是 motion()
。
motion.custom()
的默认行为是将 Framer Motion 的所有属性转发到基础组件。要复制此行为,可以使用 forwardMotionProps
选项。
const MotionComponent = motion(Component, { forwardMotionProps: true })
3.0
Framer Motion 3 是主要版本,但破坏性更改的类型非常具体且非常小。虽然有可能,但不太可能改变动画的运作方式。
正在改变的行为
Motion 3 的特点是动画状态计算方式的集中化。
现在,所有动画属性都按优先级排序(左侧最低,右侧最高)。
当其中一个属性更改或变为活动/非活动状态时,我们将重新计算必要的动画。这是部分仅为 while
属性实现的扩展和编纂,从而带来更一致和可预测的体验。
const priority = ["animate", "while-", "exit"]
移除动画值
之前,如果从动画属性中完全删除一个值,则不会发生任何事情。
现在,如果删除了一个值,我们会在下一个最高优先级的动画状态中检查它。例如,如果从 whileHover
中删除了 opacity
,Motion 将在 animate
中检查它并动画到该值。
如果我们在 animate
中找不到一个,它将在 style
中检查,或者回退到其最初记录的值(例如,如果该值最初是从 DOM 中读取的,因为没有明确定义)。
2.0
Framer Motion 2 是主要版本,这意味着存在 API 更改。在本指南中,我们将了解如何升级您的代码以确保其继续按预期工作,并重点介绍 Motion 新版本中将要破坏的一些功能。
布局动画
Framer Motion 1 支持几种执行布局动画的方法,即 positionTransition
和 layoutTransition
属性。
// Before <motion.div layoutTransition />
在 Framer Motion 2 中,这些都已被 layout
属性取代。
// After <motion.div layout />
旧的属性都使用过渡作为参数。
// Before <motion.div layoutTransition={{ duration: 2 }} />
现在,布局动画使用与其他动画相同的默认 transition
属性。
// After <motion.div layout transition={{ duration: 2 }} />
在 Framer Motion 1 中,布局动画可能会扭曲正在更改大小的组件上的 borderRadius
和 boxShadow
属性。如果任何一个属性被动画化,则现在已修复。
<motion.div layout initial={{ borderRadius: 20 }} />
更改大小的布局动画也可能扭曲子组件。现在可以通过为它们提供 layout
属性来纠正这一点。
只有直接子元素才需要针对缩放进行纠正。
<motion.div layout> <motion.div layout /> </motion.div>
破坏性更改
在升级之前,您应该注意一些没有立即修复方法的更改。
拖动
拖动已重构为使用与 Motion 2 的布局动画相同的布局投影渲染方法,以确保这两个功能彼此完全兼容。
这导致了一些破坏性更改
拖动监听器(如
onDrag
)现在报告相对于视口的point
,与 Motion 中的其他指针手势保持一致。dragOriginX
和dragOriginY
已被移除。添加这些是为了允许一种 hacky 的方法使positionTransition
与drag
兼容,但layout
默认与drag
兼容。
useAnimatedState
useAnimatedState
API 是一个实验性的、未记录的 API,用于 Framer X。现在已移除。