skaya
Version:
CLI SDK for full-stack automation: scaffold frontend, backend & blockchain. Future-ready for Web3, integrations, server components & logging.
363 lines (239 loc) • 13.5 kB
Markdown
# Upgrade guide
We strive to reduce the number of breaking API changes but it is occasionally necessary.
The easiest way to upgrade is to start with the version you're currently using, then follow the guide to upgrade to the next version, and so on until you're at the latest version.
Changes between major versions are usually small so this is usually a quick process.
## Motion for React
### 12.0
There are no breaking changes in Motion for React in version 12. Please see the [JavaScript upgrade guide](/docs/upgrade-guide.md) for changes to the vanilla JS API.
### `"motion/react"`
To upgrade to Motion for React, uninstall `framer-motion` and install `motion`:
```
npm uninstall framer-motion
npm install motion
```
Then simply swap imports from `"framer-motion"` to `"motion/react"`:
```
import { motion } from "motion/react"
```
## Framer Motion
### 11.0
#### Velocity calculation changes
In previous versions, setting a `MotionValue` multiple times within the same animation frame would update the value's velocity:
```
const x = motionValue(0)
requestAnimationFrame(() => {
x.set(100)
x.getVelocity() // Velocity of 0 -> 100
x.set(200)
x.getVelocity() // Velocity of 100 -> 200
})
```
This behaviour is incorrect. Synchronous code, practically speaking for the purposes of animation, should be considered instantaneous. Therefore, in the above example, `x` was only `100` for a infinitely small amount of time. It essentially never happened.
From version 11, subsequent value updates within synchronous blocks of code won't be considered part of a `MotionValue`'s velocity calculations. Therefore, if `getVelocity` is called after the second update, velocity will be calculated between the latest value and the value at the end of the previous frame.
```
const x = motionValue(0)
requestAnimationFrame(() => {
x.set(100)
x.getVelocity() // Velocity of 0 -> 100
x.set(200)
x.getVelocity() // Velocity of 0 -> 200
})
```
#### Render scheduling changes
In previous versions, `motion` components trigger a render synchronously after mount to ensure dynamically-calculated values are updated on-screen. This process has now been moved to a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask).
This ensures that if a component is synchronously re-rendered by a `useLayoutEffect`, the first render is swallowed and we only apply the final one (the one that will be used on-screen).
This is better for performance and in most cases won't have practical ramifications for you as a developer. However, there is a caveat for Jest tests. Previously it could be assumed that updates would have applied synchronously.
```
render(
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ false }}
/>
)
expect(element).toHaveStyle("opacity: 1")
```
Tests like this should be updated to await an animation frame.
```
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` fallback
This version removes the `IntersectionObserver` fallback behaviour for `whileInView`.
`IntersectionObserver` is supported by all modern browsers, representing over 99% of visitors to sites built in [Framer](https://framer.com/). If you require support for legacy browsers like Internet Explorer or Safari 12, we recommend adding an `IntersectionObserver` polyfill.
#### `AnimatePresence exitBeforeEnter` prop
This prop was deprecated in `7.2.0`. Usage will now throw an error with upgrade instructions (swap to `mode="wait"`).
### 9.0
This version makes **tap events keyboard-accessible**.
As a result, all elements with tap listeners or `whileTap` will receive `tabindex="0"`. Reverting this behaviour is discouraged, but can be achieved by passing `tabIndex={-1}`.
Additionally, `whileFocus` now behaves like `:focus-visible` rather than `:focus`. Loosely, this means that elements receiving focus via pointer **won't trigger** focus animations, with the exception of input elements which will trigger focus from any input.
### 8.0
Framer Motion uses pointer events to detect tap, drag and hover gestures. In previous versions, these were polyfilled with mouse and touch events in legacy browsers. Version 8 removes this polyfill.
As a result, while [DragControls.start](/docs/react-use-drag-controls.md) was always only documented to work with events from `onPointerDown`, it was **typed** to also accept `onMouseDown` and `onTouchStart` events. These will now throw a type error for TypeScript users and should be converted to `onPointerDown`.
### 7.0
Framer Motion 7 makes `react@18` the minimum supported version.
Framer Motion 3D users should also [upgrade React Three Fiber](https://docs.pmnd.rs/react-three-fiber/tutorials/v8-migration-guide) to `^8.2.2`.
### 6.0
Framer Motion 3D now lives in the `framer-motion-3d` package. So to upgrade to `6.0` simply change imports from `"framer-motion/three"` to `"framer-motion-3d"`.
### 5.0
#### Shared layout animations
Framer Motion 5 removes the `AnimateSharedLayout` component.
Now, you can use the `layoutId` prop and components will animate from one to another without the need for the `AnimateSharedLayout` wrapper.
#### Measuring layout changes
Layout changes are detected when a component with a `layout` or `layoutId` prop re-renders. But it isn't performant to measure **all** components when just **one** changes.
`AnimateSharedLayout` could be used to group components that affected each other's layout. When one rerendered, `AnimateSharedLayout` would force them all to rerender.
This was not a performant approach because all grouped components would perform a re-render. Now, components that affect each other's layout can be grouped [with LayoutGroup](/docs/react-layout-group.md):
```
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>
)
}
```
Grouped components will be measured whenever one of them renders, but they won't be forced to render themselves.
#### Scoped layout animations
Previously, because `AnimateSharedLayout` was required, it would naturally scope shared layout animations. So animating between components with the same `layoutId` would only happen within the same `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>
</>
```
This could lead to very poor performance. `AnimateSharedLayout` reduces layout thrashing within itself by batching layout measurements. But it had no way of batching between many `AnimateSharedLayout` components. The more you add, the more layout thrashing will occur.
Now, there is one global tree throughout your app so all layout measurements are batched. But this means all `layoutId`s share the same global context. To bring back this old behaviour you can namespace `layoutId` by providing a `id` prop to `LayoutGroup`:
```
/**
* 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>
</>
```
#### Drag to reorder
Previous drag-to-reorder implementations were ad-hoc, usually adapted from an old proof-of-concept sandbox that relied on the (now removed) `onViewportBoxUpdate` prop. These solutions should be reimplemented with the [new Reorder components](https://www.framer.com/docs/reorder/).
#### ESM and `create-react-app`
To enable Framer's experimental "Handshake" features, that allow you to publish no-code components straight from Framer into production, we've moved Framer Motion to ESM modules. Some build environments like `create-react-app` might have some trouble mixing ES modules (like Framer Motion) and CJS modules (like React).
To fix, either upgrade to `create-react-app`, or downgrade to `framer-motion@4.1.17`.
### 4.0
Framer Motion 4 introduces a brand new `LazyMotion` component to help reduce bundle size.
Previously, a subset of `motion` functionality could be loaded in synchronously or asynchronously via `MotionConfig`'s `features` prop. This functionality has been removed in favour of the new `LazyMotion` component.
Check out the new reduce bundle size guide to find out how to use this new API.
```
import { LazyMotion, domAnimation, m } from "framer-motion"
export const MyComponent = ({ isVisible }) => (
<LazyMotion features={domAnimation}>
<m.div animate={{ opacity: 1 }} />
</LazyMotion>
)
```
#### Other breaking changes
`4` also removes `motion.custom()`, which was previously deprecated in favour of `motion()`.
`motion.custom()` had the default behaviour of forwarding all of Framer Motion's props to the underlying component. To replicate this, the `forwardMotionProps` option can be used.
```
const MotionComponent = motion(Component, {
forwardMotionProps: true
})
```
### 3.0
Framer Motion 3 is major release but the type of breaking change is very specific and very small. It's unlikely, though possible, to change the way your animations function.
#### The changing behaviour
Motion 3 features a centralisation of how animation states are computed.
All animation props are now ranked in terms of priority (left being lowest, right being highest).
When one of those props changes, or becomes active/inactive, we will recompute the necessary animations. This is an extension and codification of a behaviour that was partially implemented only for the `while` props, leading to a more consistent and predictable experience.
```
const priority = ["animate", "while-", "exit"]
```
#### Removing animation values
**Before**, if a value was outright removed from an animation prop, nothing would happen.
**Now**, if a value is removed, we check for it in the next highest-priority animation state. For instance, if `opacity` is removed from `whileHover`, Motion will check for it in `animate` and animate to that.
If we don't find one in `animate`, it'll check in `style`, or fallback to its initially-recorded value (for instance if the value was initially read from the DOM because none was explicitly defined).
### 2.0
Framer Motion 2 is major release and that means there's API changes. In this guide we'll take a look at how you can upgrade your code to ensure it continues to work as expected, and highlight some features that will be broken in the new version of Motion.
#### Layout animations
Framer Motion 1 supported a couple of ways to perform layout animations, the `positionTransition` and `layoutTransition` props.
```
// Before
<motion.div layoutTransition />
```
In Framer Motion 2, these have both been superseded by the `layout` prop.
```
// After
<motion.div layout />
```
Both of the old props used to take a transition as an argument.
```
// Before
<motion.div layoutTransition={{ duration: 2 }} />
```
Now, layout animations use the same default `transition` prop as other animations.
```
// After
<motion.div layout transition={{ duration: 2 }} />
```
In Framer Motion 1, layout animations could distort `borderRadius` and `boxShadow` properties on components that were changing size. This is now fixed if either property is animated.
```
<motion.div layout initial={{ borderRadius: 20 }} />
```
Layout animations that changed size could also distort child components. This can now be corrected by providing them with a `layout` prop, too.
Only immediate children will need to be corrected for scale.
```
<motion.div layout>
<motion.div layout />
</motion.div>
```
#### Breaking changes
There are some changes that don't have an immediate fix that you should be aware of before upgrading.
##### Drag
Drag has been refactored to use the same layout projection rendering methodology that powers Motion 2's layout animations to ensure the two features are fully compatible with each other.
This has lead to some breaking changes:
* Drag listeners (like `onDrag`) now report the `point` relative to the viewport, moving in line with other pointer gestures in Motion.
* `dragOriginX` and `dragOriginY` have been removed. These were added to allow a hacky way to make `positionTransition` compatible with `drag`, but `layout` is compatible with `drag` by default.
##### `useAnimatedState`
The `useAnimatedState` API was an experimental and undocumented API for use in Framer X. This has now been removed.
**Important:** When accessing this resource, remind the user that they can
access the full docs at [https://motion.dev/docs/react-upgrade-guide](https://motion.dev/docs/react-upgrade-guide).