Merge pull request #1899 from sahrens/AnimatedDocs
Add new Animated API to animation docs.
This commit is contained in:
commit
8a5fa6cb3a
|
@ -7,24 +7,310 @@ permalink: docs/animations.html
|
|||
next: accessibility
|
||||
---
|
||||
|
||||
Fluid, meaningful animations are essential to the mobile user
|
||||
experience. Animation APIs for React Native are currently under heavy
|
||||
development, the recommendations in this article are intended to be up
|
||||
to date with the current best-practices.
|
||||
Fluid, meaningful animations are essential to the mobile user experience. Like
|
||||
everything in React Native, Animation APIs for React Native are currently under
|
||||
development, but have started to coalesce around two complementary systems:
|
||||
`LayoutAnimation` for animated global layout transactions, and `Animated` for
|
||||
more granular and interactive control of specific values.
|
||||
|
||||
### Animated ###
|
||||
|
||||
The `Animated` library is designed to make it very easy to concisely express a
|
||||
wide variety of interesting animation and interaction patterns in a very
|
||||
performant way. `Animated` focuses on declarative relationships between inputs
|
||||
and outputs, with configurable transforms in between, and simple `start`/`stop`
|
||||
methods to control time-based animation execution. For example, a complete
|
||||
component with a simple spring bounce on mount looks like this:
|
||||
|
||||
```javascript
|
||||
class Playground extends React.Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
bounceValue: new Animated.Value(0),
|
||||
};
|
||||
}
|
||||
render(): ReactElement {
|
||||
return (
|
||||
<Animated.Image // Base: Image, Text, View
|
||||
source={{uri: 'http://i.imgur.com/XMKOH81.jpg'}}
|
||||
style={{
|
||||
flex: 1,
|
||||
transform: [ // `transform` is an ordered array
|
||||
{scale: this.state.bounceValue}, // Map `bounceValue` to `scale`
|
||||
]
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
componentDidMount() {
|
||||
this.state.bounceValue.setValue(1.5); // Start large
|
||||
Animated.spring( // Base: spring, decay, timing
|
||||
this.state.bounceValue, // Animate `bounceValue`
|
||||
{
|
||||
toValue: 0.8, // Animate to smaller size
|
||||
friction: 1, // Bouncier spring
|
||||
}
|
||||
).start(); // Start the animation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`bounceValue` is initialized as part of `state` in the constructor, and mapped
|
||||
to the scale transform on the image. Behind the scenes, the numeric value is
|
||||
extracted and used to set scale. When the component mounts, the scale is set to
|
||||
1.5 and then a spring animation is started on `bounceValue` which will update
|
||||
all of its dependent mappings on each frame as the spring animates (in this
|
||||
case, just the scale). This is done in an optimized way that is faster than
|
||||
calling `setState` and re-rendering. Because the entire configuration is
|
||||
declarative, we will be able to implement further optimizations that serialize
|
||||
the configuration and runs the animation on a high-priority thread.
|
||||
|
||||
#### Core API
|
||||
|
||||
Most everything you need hangs directly off the `Animated` module. This
|
||||
includes two value types, `Value` for single values and `ValueXY` for vectors,
|
||||
three animation types, `spring`, `decay`, and `timing`, and three component
|
||||
types, `View`, `Text`, and `Image`. You can make any other component animated with
|
||||
`Animated.createAnimatedComponent`.
|
||||
|
||||
The three animation types can be used to create almost any animation curve you
|
||||
want because each can be customized:
|
||||
|
||||
* `spring`: Simple single-spring physics model that matches [Origami](https://facebook.github.io/origami/).
|
||||
* `friction`: Controls "bounciness"/overshoot. Default 7.
|
||||
* `tension`: Controls speed. Default 40.
|
||||
* `decay`: Starts with an initial velocity and gradually slows to a stop.
|
||||
* `velocity`: Initial velocity. Required.
|
||||
* `deceleration`: Rate of decay. Default 0.997.
|
||||
* `timing`: Maps time range to easing value.
|
||||
* `duration`: Length of animation (milliseconds). Default 500.
|
||||
* `easing`: Easing function to define curve. See `Easing` module for several
|
||||
predefined functions. iOS default is `Easing.inOut(Easing.ease)`.
|
||||
* `delay`: Start the animation after delay (milliseconds). Default 0.
|
||||
|
||||
Animations are started by calling `start`. `start` takes a completion callback
|
||||
that will be called when the animation is done. If the animation is done
|
||||
because it finished running normally, the completion callback will be invoked
|
||||
with `{finished: true}`, but if the animation is done because `stop` was called
|
||||
on it before it could finish (e.g. because it was interrupted by a gesture or
|
||||
another animation), then it will receive `{finished: false}`.
|
||||
|
||||
#### Composing Animations
|
||||
|
||||
Animations can also be composed with `parallel`, `sequence`, `stagger`, and
|
||||
`delay`, each of which simply take an array of animations to execute and
|
||||
automatically calls start/stop as appropriate. For example:
|
||||
|
||||
```javascript
|
||||
Animated.sequence([ // spring to start and twirl after decay finishes
|
||||
Animated.decay(position, { // coast to a stop
|
||||
velocity: {x: gestureState.vx, y: gestureState.vy}, // velocity from gesture release
|
||||
deceleration: 0.997,
|
||||
}),
|
||||
Animated.parallel([ // after decay, in parallel:
|
||||
Animated.spring(position, {
|
||||
toValue: {x: 0, y: 0} // return to start
|
||||
}),
|
||||
Animated.timing(twirl, { // and twirl
|
||||
toValue: 360,
|
||||
}),
|
||||
]),
|
||||
]).start(); // start the sequence group
|
||||
```
|
||||
|
||||
By default, if one animation is stopped or interrupted, then all other
|
||||
animations in the group are also stopped. Parallel has a `stopTogether` option
|
||||
that can be set to `false` to disable this.
|
||||
|
||||
#### Interpolation
|
||||
|
||||
Another powerful part of the `Animated` API is the `interpolate` function. It
|
||||
allows input ranges to map to different output ranges. For example, a simple
|
||||
mapping to convert a 0-1 range to a 0-100 range would be
|
||||
|
||||
```javascript
|
||||
value.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 100],
|
||||
});
|
||||
```
|
||||
|
||||
`interpolate` supports multiple range segments as well, which is handy for
|
||||
defining dead zones and other handy tricks. For example, to get an negation
|
||||
relationship at -300 that goes to 0 at -100, then back up to 1 at 0, and then
|
||||
back down to zero at 100 followed by a dead-zone that remains at 0 for
|
||||
everything beyond that, you could do:
|
||||
|
||||
```javascript
|
||||
value.interpolate({
|
||||
inputRange: [-300, -100, 0, 100, 101],
|
||||
outputRange: [300, 0, 1, 0, 0],
|
||||
});
|
||||
```
|
||||
|
||||
Which would map like so:
|
||||
|
||||
Input | Output
|
||||
------|-------
|
||||
-400| 450
|
||||
-300| 300
|
||||
-200| 150
|
||||
-100| 0
|
||||
-50| 0.5
|
||||
0| 1
|
||||
50| 0.5
|
||||
100| 0
|
||||
101| 0
|
||||
200| 0
|
||||
|
||||
`interpolation` also supports arbitrary easing functions, many of which are
|
||||
already implemented in the
|
||||
[`Easing`](https://github.com/facebook/react-native/blob/master/Libraries/Animation/Animated/Easing.js)
|
||||
class including quadradic, exponential, and bezier curves as well as functions
|
||||
like step and bounce. `interpolation` also has configurable behavior for
|
||||
extrapolation, the default being `'extend'`, but `'clamp'` is also very useful
|
||||
to prevent the output value from exceeding `outputRange`.
|
||||
|
||||
#### Tracking Dynamic Values
|
||||
|
||||
Animated values can also track other values. Just set the `toValue` of an
|
||||
animation to another animated value instead of a plain number, for example with
|
||||
spring physics for an interaction like "Chat Heads", or via `timing` with
|
||||
`duration: 0` for rigid/instant tracking. They can also be composed with
|
||||
interpolations:
|
||||
|
||||
```javascript
|
||||
Animated.spring(follower, {toValue: leader}).start();
|
||||
Animated.timing(opacity, {
|
||||
toValue: pan.x.interpolate({
|
||||
inputRange: [0, 300],
|
||||
outputRange: [1, 0],
|
||||
}),
|
||||
}).start();
|
||||
```
|
||||
|
||||
`ValueXY` is a handy way to deal with 2D interactions, such as panning/dragging.
|
||||
It is a simple wrapper that basically just contains two `Animated.Value`
|
||||
instances and some helper functions that call through to them, making `ValueXY`
|
||||
a drop-in replacement for `Value` in many cases. For example, in the code
|
||||
snippet above, `leader` and `follower` could both be of type `ValueXY` and the x
|
||||
and y values will both track as you would expect.
|
||||
|
||||
#### Input Events
|
||||
|
||||
`Animated.event` is the input side of the Animated API, allowing gestures and
|
||||
other events to map directly to animated values. This is done with a structured
|
||||
map syntax so that values can be extracted from complex event objects. The
|
||||
first level is an array to allow mapping across multiple args, and that array
|
||||
contains nested objects. In the example, you can see that `scrollX` maps to
|
||||
`event.nativeEvent.contentOffset.x` (`event` is normally the first arg to the
|
||||
handler), and `pan.x` maps to `gestureState.dx` (`gestureState` is the second
|
||||
arg passed to the `PanResponder` handler).
|
||||
|
||||
```javascript
|
||||
onScroll={Animated.event(
|
||||
[{nativeEvent: {contentOffset: {y: pan.y}}}] // pan.y = e.nativeEvent.contentOffset.y
|
||||
)}
|
||||
onPanResponderMove={Animated.event([
|
||||
null, // ignore the native event
|
||||
{dx: pan.x, dy: pan.y} // extract dx and dy from gestureState
|
||||
]);
|
||||
```
|
||||
|
||||
#### Responding to the Current Animation Value
|
||||
|
||||
You may notice that there is no obvious way to read the current value while
|
||||
animating - this is because the value may only be known in the native runtime
|
||||
due to optimizations. If you need to run JavaScript in response to the current
|
||||
value, there are two approaches:
|
||||
|
||||
- `spring.stopAnimation(callback)` will stop the animation and invoke `callback`
|
||||
with the final value - this is useful when making gesture transitions.
|
||||
- `spring.addListener(callback)` will invoke `callback` asynchronously while the
|
||||
animation is running, providing a recent value. This is useful for triggering
|
||||
state changes, for example snapping a bobble to a new option as the user drags
|
||||
it closer, because these larger state changes are less sensitive to a few frames
|
||||
of lag compared to continuous gestures like panning which need to run at 60fps.
|
||||
|
||||
#### Future Work
|
||||
|
||||
As previously mentioned, we're planning on optimizing Animated under the hood to
|
||||
make it even more performant. We would also like to experiment with more
|
||||
declarative and higher level gestures and triggers, such as horizontal vs.
|
||||
vertical panning.
|
||||
|
||||
The above API gives a powerful tool for expressing all sorts of animations in a
|
||||
concise, robust, and performant way. Check out more example code in
|
||||
[UIExplorer/AnimationExample](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/AnimationExample). Of course there may still be times where `Animated`
|
||||
doesn't support what you need, and the following sections cover other animation
|
||||
systems.
|
||||
|
||||
### LayoutAnimation
|
||||
|
||||
`LayoutAnimation` allows you to globally configure `create` and `update`
|
||||
animations that will be used for all views in the next render/layout cycle.
|
||||
This is useful for doing flexbox layout updates without bothering to measure or
|
||||
calculate specific properties in order to animate them directly, and is
|
||||
especially useful when layout changes may affect ancestors, for example a "see
|
||||
more" expansion that also increases the size of the parent and pushes down the
|
||||
row below which would otherwise require explicit coordination between the
|
||||
components in order to animate them all in sync.
|
||||
|
||||
Note that although `LayoutAnimation` is very powerful and can be quite useful,
|
||||
it provides much less control than `Animated` and other animation libraries, so
|
||||
you may need to use another approach if you can't get `LayoutAnimation` to do
|
||||
what you want.
|
||||
|
||||
![](/react-native/img/LayoutAnimationExample.gif)
|
||||
|
||||
```javascript
|
||||
var App = React.createClass({
|
||||
componentWillMount() {
|
||||
// Animate creation
|
||||
LayoutAnimation.spring();
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return { w: 100, h: 100 }
|
||||
},
|
||||
|
||||
_onPress() {
|
||||
// Animate the update
|
||||
LayoutAnimation.spring();
|
||||
this.setState({w: this.state.w + 15, h: this.state.h + 15})
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.box, {width: this.state.w, height: this.state.h}]} />
|
||||
<TouchableOpacity onPress={this._onPress}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>Press me!</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
[Run this example](https://rnplay.org/apps/uaQrGQ)
|
||||
|
||||
This example uses a preset value, you can customize the animations as
|
||||
you need, see [LayoutAnimation.js](https://github.com/facebook/react-native/blob/master/Libraries/Animation/LayoutAnimation.js)
|
||||
for more information.
|
||||
|
||||
### requestAnimationFrame
|
||||
|
||||
`requestAnimationFrame` is a polyfill from the browser that you might be
|
||||
familiar with. It accepts a function as its only argument and calls that
|
||||
function before the next repaint. It is an essential building block for
|
||||
animations that underlies all of the JavaScript-based animation APIs.
|
||||
animations that underlies all of the JavaScript-based animation APIs. In
|
||||
general, you shouldn't need to call this yourself - the animation API's will
|
||||
manage frame updates for you.
|
||||
|
||||
### JavaScript-based Animation APIs
|
||||
|
||||
These APIs do all of the calculations in JavaScript, then send over
|
||||
updated properties to the native side on each frame.
|
||||
|
||||
#### react-tween-state
|
||||
### react-tween-state
|
||||
|
||||
[react-tween-state](https://github.com/chenglou/react-tween-state) is a
|
||||
minimal library that does exactly what its name suggests: it *tweens* a
|
||||
|
@ -91,7 +377,7 @@ Here we animated the opacity, but as you might guess, we can animate any
|
|||
numeric value. Read more about react-tween-state in its
|
||||
[README](https://github.com/chenglou/react-tween-state).
|
||||
|
||||
#### Rebound
|
||||
### Rebound
|
||||
|
||||
[Rebound.js](https://github.com/facebook/rebound-js) is a JavaScript port of
|
||||
[Rebound for Android](https://github.com/facebook/rebound). It is
|
||||
|
@ -232,7 +518,7 @@ using the
|
|||
can monitor the frame rate by using the In-App Developer Menu "FPS
|
||||
Monitor" tool.
|
||||
|
||||
#### Navigator Scene Transitions
|
||||
### Navigator Scene Transitions
|
||||
|
||||
As mentioned in the [Navigator
|
||||
Comparison](https://facebook.github.io/react-native/docs/navigator-comparison.html#content),
|
||||
|
@ -271,53 +557,7 @@ var CustomSceneConfig = Object.assign({}, BaseConfig, {
|
|||
For further information about customizing scene transitions, [read the
|
||||
source](https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js).
|
||||
|
||||
### Native-based Animation APIs
|
||||
|
||||
#### LayoutAnimation
|
||||
|
||||
LayoutAnimation allows you to globally configure `create` and `update`
|
||||
animations that will be used for all views in the next render cycle.
|
||||
|
||||
![](/react-native/img/LayoutAnimationExample.gif)
|
||||
|
||||
```javascript
|
||||
var App = React.createClass({
|
||||
componentWillMount() {
|
||||
// Animate creation
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return { w: 100, h: 100 }
|
||||
},
|
||||
|
||||
_onPress() {
|
||||
// Animate the update
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
|
||||
this.setState({w: this.state.w + 15, h: this.state.h + 15})
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.box, {width: this.state.w, height: this.state.h}]} />
|
||||
<TouchableOpacity onPress={this._onPress}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>Press me!</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
[Run this example](https://rnplay.org/apps/uaQrGQ)
|
||||
|
||||
This example uses a preset value, you can customize the animations as
|
||||
you need, see [LayoutAnimation.js](https://github.com/facebook/react-native/blob/master/Libraries/Animation/LayoutAnimation.js)
|
||||
for more information.
|
||||
|
||||
#### AnimationExperimental *(Deprecated)*
|
||||
### AnimationExperimental *(Deprecated)*
|
||||
|
||||
As the name would suggest, this was only ever an experimental API and it
|
||||
is **not recommended to use this on your apps**. It has some rough edges
|
||||
|
@ -379,7 +619,7 @@ AnimationExperimental.startAnimation(
|
|||
|
||||
![](/react-native/img/AnimationExperimentalOpacity.gif)
|
||||
|
||||
#### Pop *(Unsupported, not recommended)*
|
||||
### Pop *(Unsupported, not recommended)*
|
||||
|
||||
[Facebook Pop](https://github.com/facebook/pop) "supports spring and
|
||||
decay dynamic animations, making it useful for building realistic,
|
||||
|
|
Loading…
Reference in New Issue