Overview

Moti has a number of powerful features that make your animations slick and simple.

import { MotiView, Text } from 'moti'

Mount animations#

You can set the initial state with from. Any styles passed to animate will transition for you.

<MotiView from={{ opacity: 0 }} animate={{ opacity: 1 }} />

Animate based on React state#

<MotiView animate={{ opacity: isLoading ? 1 : 0 }} />

This is useful for dynamic height changes, for instance.

const [height, setHeight] = useMeasure()
<MotiView
animate={{
height,
}}
/>

Customize your animation#

Moti animations are highly configurable, thanks to the transition prop. If you've used framer-motion, this will look familiar.

<MotiView
from={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
type: 'timing',
duration: 350,
}}
/>

You can also configure different transitions per-style:

<MotiView
from={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
// default settings for all style values
type: 'timing',
duration: 350,
// set a custom transition for scale
scale: {
type: 'spring',
delay: 100,
},
}}
/>

If you set type: 'spring', you can pass any options that Reanimated's withSpring accepts. Same goes for type: 'timing' & Reanimated's withTiming.

Mount/unmount animations 😎#

Framer Motion introduced the incredible AnimatePresence component to animate a component before it unmounts.

With Moti, you can now achieve the same thing in React Native.

Import AnimatePresence#

import { AnimatePresence } from 'moti'

Add an exit prop#

Wrap your animation with AnimatePresence, and add an exit prop.

const [visible, setVisible] = useState(false)
<AnimatePresence>
{visible && (
<MotiView
from={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
}}
/>
)}
</AnimatePresence>

Even though it's experimental, I think this feature is so cool.

Exit before enter#

You can leverage the exitBeforeEnter prop to only allow one item to be visible at a time.

Make sure that its direct children have a unique key prop for this to work.

const Skeleton = () => (
<MotiView
animate={{ opacity: 1 }}
exit={{
opacity: 0,
}}
/>
)
const WithAnimatedPresence = () => (
<AnimatePresence exitBeforeEnter>
{loading && <Skeleton key="skeleton" />}
{!loading && (
<MotiView
key="content"
animate={{ opacity: 1 }}
exit={{
opacity: 0,
}}
/>
)}
</AnimatePresence>
)

In the example above, the content won't load in until after the skeleton has faded out.

The exit prop can be inside of a nested component. However, it's important that the direct children of AnimatePresence have a unique key.

Delay animations#

You can use the delay prop

<MotiView
// delay in milliseconds
delay={200}
from={{ translateY: -5 }}
animate={{ translateY: 0 }}
/>

Or, pass your delay in transition:

<MotiView
from={{ translateY: -5 }}
animate={{ translateY: 0 }}
transition={{
delay: 100,
}}
/>

You can also set a different delay per-style:

<MotiView
from={{ translateY: -5, opacity: 0 }}
animate={{ translateY: 0, opacity: 1 }}
transition={{
translateY: {
delay: 100,
},
opacity: {
delay: 250,
},
}}
/>

Sequence animations#

To create a sequence animation, similar to CSS keyframes, just pass an array to any style:

<MotiView
animate={{
scale: [0.1, 1.1, 1],
}}
/>

This will animate to 0.1, then 1.1, then 1.

If you want to customize each step of the animation, you can also pass an object with a value field.

<MotiView
animate={{
scale: [
// you can mix primitive values with objects, too
{ value: 0.1, delay: 100 },
1.1,
{ value: 1, type: 'timing', delay: 200 },
],
}}
/>

Any transition settings can be passed to a sequence object.

Repeat & loop animations#

Repeat an animation 4 times.

<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
repeat: 4,
}}
/>

By default, repetitions reverse, meaning they automatically animate back to where they just were.

You can disable this behavior with repeatReverse: false.

<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
repeat: 4,
// when false, animation goes 0 -> 1, then starts over back at 0
repeatReverse: false,
}}
/>

Setting repeatReverse to true is like setting animationDirection: alternate in CSS.

Infinitely loop from 0 to 1:

<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
loop: true,
}}
/>

Sequences animations can't be used with repeat animations.

Repetition styles can't be changed on the fly. Reanimated's withRepeat has some limitations, so just keep that in mind.

Listen to animation changes#

The onDidAnimate function prop gets called whenever an animation completes.

<MotiView
from={{ opacity: 0 }}
animate={{ opacity: 1 }}
onDidAnimate={(
styleProp,
didAnimationFinish,
maybeValue,
{ attemptedValue }
) => {
console.log('[moti]', styleProp, didAnimationFinish) // [moti], opacity, true
if (styleProp === 'opacity' && didAnimationFinish) {
console.log('did animate opacity to: ' + attemptedValue)
}
}}
/>

Variants#

You can define static variants when your component mounts:

const animationState = useAnimationState({
from: {
opacity: 0,
},
to: {
opacity: 1,
},
})
// make sure to pass this to the `state` prop
return <MotiView state={animationState} />

Or set custom variants and update them on the fly:

const animationState = useAnimationState({
closed: {
height: 0,
},
open: {
height: 300,
},
})
const onPress = () => {
if (animationState.current === 'closed') {
animationState.transitionTo('open')
} else {
animationState.transitionTo('closed')
}
}
return <MotiView state={animationState} />

You can use this to create reusable animations, too:

const useFadeIn = () => {
return useAnimationState({
from: {
opacity: 0,
},
to: {
opacity: 1,
},
})
}
const FadeInComponent = () => {
const fadeInState = useFadeIn()
return <MotiView state={fadeInState} />
}

Read more about useAnimationState.