@huds0n/animations
v1.6.0-beta63
Published
Animates View, FlatList, and LazyList
Downloads
28
Readme
📝 Table of Contents
🧐 About
A simple and declarative way to provide complex, interactive animations for React Native.
<AnimatedText
style={{
color: "orange",
fontSize: 48,
}}
animate={[
{
to: { transform: [{ scale: 1 }] },
type: "SPRING",
bounciness: 10,
speed: 1,
},
{
to: { color: "red" },
duration: 150,
loop: true,
},
]}
>
Welcome
</AnimatedText>
✅ List of Features
- Simple: No need to deal with Animated.Values to make great animations.
- Declarative: Describe your animations rather than build them.
- Powerful: Run multiple independent animations at once.
- Serializable: Schedule animations to follow one another.
- Smooth: Use useNativeDriver for extra performance.
- Type-Safe: Fully integrated with typescript out-of-the-box.
🏁 Getting Started
Prerequisites
Works with any project implementing React 16.8 onwards
Installing
npm i @huds0n/animations
🧑💻 Basic Usage
Base Style
Animated components style identically to their static counterpart. Updating style will immediately change the appearance of a component.
import { AnimatedView } from "@huds0n/animations";
// Inside component
<AnimatedView
style={{
height: 50,
width: 100,
backgroundColor: isSelected ? "red" : "blue",
}}
/>;
Basic Animation
Passing an animationProps into animate will cause the component to transition to the new style. Animated Components will automatically detect and start animations on animate change.
<AnimatedView
style={{
backgroundColor: "blue",
width: 100,
}}
animate={{
to: {
backgroundColor: "red",
height: 50,
},
delay: 500,
duration: 1000,
}}
/>
Note that animations will intelligently calculate style property start values even if it is already in mid animation! However, it's important not to mix numbered dimensions with string dimensions. If no value is specified it assumes 0, '0%', '0deg', and 'transparent' for numbers, percentage dimensions, rotations, and colors respectively.
Looping
Loop can be added to an animationProps and can be easily controlled in three ways:
- boolean - permanent looping;
- number (multiples of 0.5) - select number of loops before stopping;
- ref ({ current: boolean }) - detect and stops looping once set to false, e.g loopRef.current = false.
// Loop ref
const loopRef = useRef(true)
// Component
<AnimatedView
style={{
backgroundColor: 'blue',
width: 100,
}}
animate={{
to: {
backgroundColor: 'red',
height: 50,
},
delay: 500,
duration: 1000,
loop: loopRef,
}}
/>
Parallel Animations
Multiple animations can be started at once on a component. To do this simply pass the animations in as an array.
<AnimatedView
style={{
backgroundColor: "blue",
width: 100,
}}
animate={[
{
to: { height: 50 },
delay: 500,
duration: 1000,
},
{
to: { backgroundColor: "red" },
duration: 250,
loop: true,
},
]}
/>
Sequence Animations
To stack animations pass the following animationProps as a return value in the function onAnimationEnd.
<AnimatedView
style={{
height: 50,
width: 100,
backgroundColor: "blue",
}}
animate={{
to: { height: 50 },
delay: 500,
duration: 1000,
onAnimationEnd: () => ({
to: { backgroundColor: "red" },
duration: 250,
loop: true,
}),
}}
/>
🧑🔬 Advanced Usage
Create Animated Component
Any component which has a style prop can be turned into an animated component using createAnimatedComponent.
import { Image } from "react-native";
import { createAnimatedComponent } from "@huds0n/animations";
const AnimatedImage = createAnimatedComponent(Image);
Use Native Driver
For extra performance pass in the useNativeDriver prop. However, this can only work if you are animating native driver compatible props, such as opacity and transforms.
<AnimatedView
style={{
backgroundColor: "blue",
height: 100,
width: 100,
}}
animate={{
opacity: 1,
}}
useNativeDriver
/>
Attach Animations
Sometimes you may want to attach an animation to another Animated.Value, e.g. a touch event created by PanResponder. By using the attach prop you set styles at specific Animated.Value input points. The component will then animate fluidly between the input points.
<AnimatedView
{...panResponder.panHandlers}
attach={{
at: [
{
input: -200,
style: { backgroundColor: "blue", transform: [{ translateY: -200 }] },
},
{
input: 0,
style: { backgroundColor: "black" },
},
{
input: 200,
style: { backgroundColor: "red", transform: [{ translateY: 200 }] },
},
],
animatedValue: panY,
}}
/>
For more complex animation paths you can set a style function to run over a set range of inputs. The smoothness of the path will depend on the number of points you chose but will require increasing calculations.
<AnimatedView
attach={{
over: {
inputStart: -200,
inputEnd: 200,
points: 20,
fn: (input) => ({
transform: [
{ translateY: input },
{
translateX: Math.pow(input / 200, 3) * 80,
},
],
}),
},
animatedValue: panY,
}}
/>
See example for use case with PanResponder.
📖 Reference
Animation Props
| Prop | Required/Default | Description | Type | | ---------------- | :----------------: | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------- | | to | ✔️ | Style to animate to | textStyle or viewStyle | | loop | false | See looping | boolean, number, or number ref | | onAnimationEnd | - | Called when animation endsPasses array of style props attached to animationAny animationProp returned is ran in sequence | (attachedProps: string array) => void or animationProps | | onAnimationStart | - | Called when animation starts | () => void | | type | 'TIMING' | Type of animation method | 'TIMING', 'SPRING', or 'DECAY' |
All Animation method specific props can also be used, such as duration, velocity, and friction.
🎁 Extras
ColorFader
Container extending React Native's View that smoothly animates background color.
<ColorFader backgroundColor={isSelected ? "red" : "blue"}>
// View's children
</ColorFader>
Additional props:
| Prop | Required/Default | Description | Type | | --------------- | :--------------------------------: | --------------------------------------------- | ---------------------------------------------- | | animate | true | Toggles color animation | boolean | | animation | { type: 'TIMING', duration: 100} | Color animation config | AnimationProps with 'to' | | backgroundColor | null | Background color | string or null | | overrideColor | - | Overrides backgroundColor without animation | string |
ContentsFader
Container extending React Native's View that fades between it's child components.
<ContentsFader dependencies={[isSelected]}>
{isSelected && <SelectedComponent />}
{!isSelected && <UnSelectedComponent />}
</ContentsFader>
Additional props:
| Prop | Required/Default | Description | Type | | ----------------- | :----------------: | ---------------------------------------------- | --------------------------- | | animate | true | Toggles fade animation | boolean | | animationDuration | 500 | Duration of cross fade | number | | dependencies | ✔️ | Array of variables which on change invoke fade | array of any | | easing | - | Animation easing | (value: number) => number | | fadeOverlap | 1/3 | Ratio of cross fade overlapping | number | | useNativeDriver | true | Whether animation uses native driver | boolean |
Transition
Combines ColorFader and ContentsFader with automatic resizing based on content size.
<TransitionContainer
backgroundColor={currentButton.backgroundColor}
style={{
borderWidth: StyleSheet.hairlineWidth,
height: 40,
padding: 10,
borderRadius: 10,
}}
dependencies={[currentButton.text]}
>
<Text style={{ textAlign: "center" }}>{currentButton.text}</Text>
</TransitionContainer>
Lists
Adds animation attached to flatlist scroll with automatic animate in.
Additional props:
| Prop | Required/Default | Description | Type | | ----------------- | :----------------: | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | animationDelay | 0 | Delay in ms on animating in | number | | animationDuration | 2500 | Duration in ms of animating in | number | | at | - | Set styles at position points to animate between | (ElementPosition) => attachAtProp | | itemLength | ✔️ | Length of item element in direction of scroll | number | | footerOffset | - | Footer offset | number | | headerOffset | - | Header offset | number | | ListComponent | FlatList | List component to animate (will inherit props) | React List Component | | offsetAnim | - | Scroll animated valueCan be attached to other Animated Components for complex animations | Animated Value | | onAnimationEnd | - | Called when animation in complete | () => void | | over | - | Set style function animate over | (ElementPosition) => attachOverProp | | renderItem | ✔️ | Item render functionPassed ElementPosition with ListRenderItemInfo | (ElementPosition & ListRenderItemInfo) => React Component | | useNativeDriver | false | Whether animation uses native driver | boolean |
ElementPosition:
| Prop | Description | Type | | ----- | ----------------------------------------------------------------- | -------- | | index | Item index | number | | row | Item row | number | | start | Scroll offset at which the element is after the start of the view | number | | end | Scroll offset at which the element is after the end of the view | number |
<AnimatedList
animationDuration={4000}
itemLength={150}
at={({ index, start, end }) => [
/* At 150px above the top of the view items have an opacity 0.5 */
{ input: start + 150, style: { opacity: 0.5 } },
/* When the item is in full view they have opacity 1 */
{ input: start, style: { opacity: 1 } },
/* At 200px above the bottom of the view the items will start to fade from opacity 1 */
{
input: end + 200,
style: { opacity: 1 },
},
/* At 100px above the bottom of the view the elements start their complex movement out */
{
input: end + 100,
style: {
transform: [
{ translateX: 0 },
{ translateY: 0 },
{ scale: 1 },
{ rotate: "0deg" },
],
},
},
/* By the time the element reaches the bottom of the screen has completed it's complex movement.
Note how remainder is use to calculate column */
{
input: end,
style: {
opacity: 0,
transform: [
{ translateX: index % 2 ? 150 : -150 },
{ translateY: -150 },
{ scale: 0 },
{ rotate: index % 2 ? "-360deg" : "360deg" },
],
},
},
]}
data={DEMO_DATA}
keyName="value"
numColumns={2}
renderItem={({ item }) => {
return (
<View style={styles.itemContainer}>
<Image source={{ uri: item.PICTURE_URI }} style={styles.itemPicture} />
</View>
);
}}
useNativeDriver
reverseZIndex
style={styles.flatlist}
/>
📲 Example
Clone or fork the repo at https://github.com/JontiHudson/modules-huds0n-animations
Go to the __example__ folder. Run npm install to install the expo project, then expo start to launch the example.
✍️ Authors
- @JontiHudson - Idea & Initial work
- @MartinHudson - Support & Development
See also the list of contributors who participated in this project.
🎉 Acknowledgements
- Special thanks to my fiance, Arma, who has been so patient with all my extra-curricular work.