文档

文档

React

升级指南

升级指南

我们力求减少破坏性的 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 回退

此版本移除了 whileInViewIntersectionObserver 回退行为。

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 的事件一起使用,但它被类型化为也接受 onMouseDownonTouchStart 事件。这些现在将为 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 包装器。

测量布局更改

当具有 layoutlayoutId 属性的组件重新渲染时,会检测到布局更改。但是,仅当 一个组件更改时就测量 所有组件不是高性能的。

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 组件,以帮助减少包大小。

以前,可以通过 MotionConfigfeatures 属性同步或异步加载 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 支持几种执行布局动画的方法,即 positionTransitionlayoutTransition 属性。

// 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 中,布局动画可能会扭曲正在更改大小的组件上的 borderRadiusboxShadow 属性。如果任何一个属性被动画化,则现在已修复。

<motion.div layout initial={{ borderRadius: 20 }} />

更改大小的布局动画也可能扭曲子组件。现在可以通过为它们提供 layout 属性来纠正这一点。

只有直接子元素才需要针对缩放进行纠正。

<motion.div layout>
  <motion.div layout />
</motion.div>

破坏性更改

在升级之前,您应该注意一些没有立即修复方法的更改。

拖动

拖动已重构为使用与 Motion 2 的布局动画相同的布局投影渲染方法,以确保这两个功能彼此完全兼容。

这导致了一些破坏性更改

  • 拖动监听器(如 onDrag)现在报告相对于视口的 point,与 Motion 中的其他指针手势保持一致。

  • dragOriginXdragOriginY 已被移除。添加这些是为了允许一种 hacky 的方法使 positionTransitiondrag 兼容,但 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 回退

此版本移除了 whileInViewIntersectionObserver 回退行为。

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 的事件一起使用,但它被类型化为也接受 onMouseDownonTouchStart 事件。这些现在将为 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 包装器。

测量布局更改

当具有 layoutlayoutId 属性的组件重新渲染时,会检测到布局更改。但是,仅当 一个组件更改时就测量 所有组件不是高性能的。

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 组件,以帮助减少包大小。

以前,可以通过 MotionConfigfeatures 属性同步或异步加载 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 支持几种执行布局动画的方法,即 positionTransitionlayoutTransition 属性。

// 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 中,布局动画可能会扭曲正在更改大小的组件上的 borderRadiusboxShadow 属性。如果任何一个属性被动画化,则现在已修复。

<motion.div layout initial={{ borderRadius: 20 }} />

更改大小的布局动画也可能扭曲子组件。现在可以通过为它们提供 layout 属性来纠正这一点。

只有直接子元素才需要针对缩放进行纠正。

<motion.div layout>
  <motion.div layout />
</motion.div>

破坏性更改

在升级之前,您应该注意一些没有立即修复方法的更改。

拖动

拖动已重构为使用与 Motion 2 的布局动画相同的布局投影渲染方法,以确保这两个功能彼此完全兼容。

这导致了一些破坏性更改

  • 拖动监听器(如 onDrag)现在报告相对于视口的 point,与 Motion 中的其他指针手势保持一致。

  • dragOriginXdragOriginY 已被移除。添加这些是为了允许一种 hacky 的方法使 positionTransitiondrag 兼容,但 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 回退

此版本移除了 whileInViewIntersectionObserver 回退行为。

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 的事件一起使用,但它被类型化为也接受 onMouseDownonTouchStart 事件。这些现在将为 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 包装器。

测量布局更改

当具有 layoutlayoutId 属性的组件重新渲染时,会检测到布局更改。但是,仅当 一个组件更改时就测量 所有组件不是高性能的。

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 组件,以帮助减少包大小。

以前,可以通过 MotionConfigfeatures 属性同步或异步加载 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 支持几种执行布局动画的方法,即 positionTransitionlayoutTransition 属性。

// 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 中,布局动画可能会扭曲正在更改大小的组件上的 borderRadiusboxShadow 属性。如果任何一个属性被动画化,则现在已修复。

<motion.div layout initial={{ borderRadius: 20 }} />

更改大小的布局动画也可能扭曲子组件。现在可以通过为它们提供 layout 属性来纠正这一点。

只有直接子元素才需要针对缩放进行纠正。

<motion.div layout>
  <motion.div layout />
</motion.div>

破坏性更改

在升级之前,您应该注意一些没有立即修复方法的更改。

拖动

拖动已重构为使用与 Motion 2 的布局动画相同的布局投影渲染方法,以确保这两个功能彼此完全兼容。

这导致了一些破坏性更改

  • 拖动监听器(如 onDrag)现在报告相对于视口的 point,与 Motion 中的其他指针手势保持一致。

  • dragOriginXdragOriginY 已被移除。添加这些是为了允许一种 hacky 的方法使 positionTransitiondrag 兼容,但 layout 默认与 drag 兼容。

useAnimatedState

useAnimatedState API 是一个实验性的、未记录的 API,用于 Framer X。现在已移除。

保持联系

订阅以获取最新消息和更新。

保持联系

订阅以获取最新消息和更新。