├── .DS_Store
├── .babelrc
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── examples
├── HocScroll.tsx
├── NativeScroll.tsx
└── ReanimatedScroll.tsx
├── lib
├── Context
│ ├── SharedAnimation.js
│ ├── SharedAnimationContext.js
│ ├── SharedAnimationProvider.js
│ └── types.js
├── HOC
│ ├── connectSharedAnimation.js
│ └── types.js
├── constants
│ └── index.js
├── helpers
│ └── getValues.js
├── index.js
├── types
│ └── index.js
└── useSharedAnimation
│ └── index.js
├── nandorojo-shared-animations-1.0.7.tgz
├── package-lock.json
├── package.json
├── readme.md
├── src
├── Context
│ ├── SharedAnimation.tsx
│ ├── SharedAnimationContext.tsx
│ ├── SharedAnimationProvider.tsx
│ └── types.ts
├── HOC
│ ├── connectSharedAnimation.tsx
│ └── types.ts
├── constants
│ └── index.tsx
├── helpers
│ └── getValues.ts
├── index.ts
├── readme.md
├── types
│ ├── index.ts
│ └── index.tsx
└── useSharedAnimation
│ └── index.ts
├── tests
└── index.tsx
├── tsconfig-old.json
├── tsconfig.json
├── tslint.json
├── yarn-error.log
└── yarn.lock
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/shared-animations/3035b75c2b7a9e1f608b045216d497ff4b0aeb19/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env", "@babel/typescript", "@babel/react"],
3 | "plugins": [
4 | "@babel/plugin-proposal-numeric-separator",
5 | "@babel/proposal-class-properties",
6 | "@babel/proposal-object-rest-spread"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
4 | # files generated by bob
5 | lib
6 | node_modules/**/*
7 | .expo/*
8 | npm-debug.*
9 | *.jks
10 | *.p8
11 | *.p12
12 | *.key
13 | *.mobileprovision
14 | *.orig.*
15 | web-build/
16 | web-report/
17 | examples
18 | node_modules
19 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": false,
3 | "semi": false,
4 | "tabWidth": 2
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Fernando Rojo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🕺shared-animations for React Native
2 |
3 | A global state management tool, built for react-native's `Animated` values.
4 |
5 | Like redux for animations.
6 |
7 | ## Why?
8 |
9 | **Sharing animated values across components should be easy.** However, it currently requires so much prop drilling that any complex animation in `react-native` becomes a hassle to manage.
10 |
11 | ## Quick setup
12 |
13 | The boilerplate setup takes about 15 seconds and is similar to redux in how it works. Just wrap your entire app with the `` component.
14 |
15 | **App.js**
16 |
17 | ```javascript
18 | import React from 'react';
19 | import { SharedAnimationProvider } from '@fernandorojo/react-native-shared-animation';
20 | import Animated from 'react-native-reanimated';
21 | import App from './src/App';
22 |
23 | export default () => {
24 | const animatedValues = { myCoolAnimatedValue: new Animated.Value(0) };
25 | return (
26 |
27 |
28 |
29 | );
30 | };
31 | ```
32 |
33 | In some other nested component, all you'd need to do is this:
34 |
35 | ```javascript
36 | import React from 'react';
37 | import { useSharedAnimation } from '@fernandorojo/react-native-shared-animation';
38 | import Animated from 'react-native-reanimated';
39 |
40 | export default () => {
41 | // here we get the value from our global store using react hooks
42 | const { getValue } = useSharedAnimation();
43 | const coolValue = getValue('myCoolAnimatedValue');
44 |
45 | return ;
46 | };
47 | ```
48 |
49 | You can also use the `connectSharedAnimation` HOC or the `` component if you don't want to use the `useSharedAnimation` hook.
50 |
51 | 🎉 **All set. Your app is now ready to share animated values across components.**
52 |
53 | Below I'll expand on all the ways that you're able to **1) initialize animated values** and **2) access animated values**.
54 |
55 | ## Examples
56 |
57 | **To see full examples, go to the [/examples]() folder.** You can also see the Expo snack of examples [here]().
58 |
59 | ## Installation
60 |
61 | To install, open your react native repository in the terminal and run this command:
62 |
63 | ```
64 | npm i @fernandorojo/react-native-shared-animation
65 | ```
66 |
67 | You could use yarn if you prefer that:
68 |
69 | ```
70 | yarn add @fernandorojo/react-native-shared-animation
71 | ```
72 |
73 | **Recommended:** If you want to use [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated) for animations, run this afterwards:
74 |
75 | ```
76 | npm i react-native-reanimated react-native-gesture-handler
77 | ```
78 |
79 | ### This works with...
80 |
81 | ✅ `react-native-reanimated`
82 |
83 | ✅ `Animated` from `react-native` if you prefer that.
84 |
85 | ✅ `Expo`
86 |
87 | ✅ `Typescript`
88 |
89 | ✅ `react-native-gesture-handler`
90 |
91 | ## 1) Initializing shared animated values
92 |
93 | You have two options for initializing shared animation values: global initialization or on-the-fly initializiation in components.
94 |
95 | ### i) [Recommended] Initialize global animated values
96 |
97 | Simply pass an `animatedValues` object as a prop to the `` component. This will act as the initial set of animated values.
98 |
99 | You can initialize as many animated values as you'd like.
100 |
101 | **App.js**
102 |
103 | ```javascript
104 | import React from 'react'
105 | import { SharedAnimationProvider } from '@fernandorojo/react-native-shared-animation'
106 | import Animated from 'react-native-reanimated'
107 | import App from './src/App' // path to your root component
108 |
109 | export default () => {
110 | const mainScrollValue = new Animated.Value(0)
111 | const animatedValues = { mainScrollValue }
112 |
113 |
114 |
115 |
116 | }
117 |
118 | ```
119 |
120 | **🐻 That's it! Your app now has globally-accessible animated values.**
121 |
122 | In this case, `mainScrollValue` can be accessed by **any** component.
123 |
124 | If you come from a redux background, you can think of this like setting the initial store value.
125 |
126 | #### Why this is the better option:
127 |
128 | From a style perspective, it is useful to know what values will be accessible across your app upon initialization. And when it comes to performance, this is less prone to bugs, since you'll never try to access a value that hasn't been initialized.
129 |
130 | That said, you also have the `newValue` method to your disposal, as described in the next option.
131 |
132 | ### **ii) [Careful] Initialize animated values on the fly in components**
133 |
134 | You can also initialize animated values directly in components. The thing is, this option is more prone to bugs, since you might try to access an animated value before it's been initialized.
135 |
136 | Overall, I'd suggest only using this one on a case-by-case basis.
137 |
138 | It can be achieved with the `newValue(name, value)` function, documented [below]().
139 |
140 | ## 2) Accessing animated values
141 |
142 | Here's the fun part.
143 |
144 | ---
145 |
146 | ### There are 3 ways to access animated values
147 |
148 | These are the 3 options you have:
149 |
150 | 1. **`useSharedAnimation` hook:** The `useSharedAnimation` hook is super simple (and is my favorite to use). Only works in function components. See react hooks to learn more.
151 |
152 | 2. **`connectSharedAnimation` HOC:** You can use the `connectSharedAnimation` higher-order component. Useful for class components and function components. Good for taking animation logic out of a component, too.
153 |
154 | 3. **`SharedAnimation` component:** You can also wrap any component with `` to connect it to the global animation state.
155 |
156 | ---
157 |
158 | ### Option 1: `useSharedAnimation`
159 |
160 | Call `useSharedAnimation` in the root of a function component.
161 |
162 | **Example:**
163 |
164 | ```javascript
165 | ...
166 | import { useSharedAnimation } from '@fernandorojo/react-native-shared-animation'
167 |
168 | export default () => {
169 | const { getValue, newValue, animatedValues } = useSharedAnimation();
170 | const scroll = getValue('scroll')
171 | // same as...
172 | const { scroll } = animatedValues;
173 |
174 | return
175 | }
176 |
177 | ```
178 |
179 | So simple!
180 |
181 | 🤑🤑
182 |
183 | **If you're using a version of react / react-native that supports hooks, you can happily stop reading the docs here and just use this.**
184 |
185 | --
186 |
187 | ### Option 2: `connectSharedAnimation(mapValuesToProps)(Component)`
188 |
189 | You can also use the `connectSharedAnimation` higher-order component to pass animated values as props.
190 |
191 | This option gives you some customization options beyond the `useSharedAnimation`, such as taking global animated code out of your actual component.
192 |
193 | **This HOC passes `newValue`, `getValue`, and any animated values you choose as props to your `Component`.**
194 |
195 | **Example:**
196 |
197 | ```javascript
198 | ...
199 | import { connectSharedAnimation } from 'react-native-shared-animation'
200 |
201 | const ConnectedComponent = ({ getValue, newValue, scroll }) => {
202 | return
203 | }
204 |
205 | // determine which values you want to pass to this component
206 | const mapValuesToProps = animatedValues => ({
207 | scroll: animatedValues.scroll
208 | })
209 |
210 | // could also have done this:
211 | // const mapValuesToProps = 'scroll'
212 |
213 | // ...or this:
214 | // const mapValuesToProps = ['scroll', 'someOtherValue']
215 |
216 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
217 |
218 | ```
219 |
220 | #### `mapValuesToProps` (required)
221 |
222 | This is the first and only argument for `connectSharedAnimation`. It determines which animated values will be passed to the component as direct props.
223 |
224 | This value can be either a `string`, `array of strings`, a `function`, or `null`.
225 |
226 | **mapValuesToProps as a string**
227 |
228 | The string should correspond to an existing global animated value.
229 |
230 | ```javascript
231 | ...
232 | import { connectSharedAnimation } from '@fernandorojo/react-native-shared-animation'
233 |
234 | const ConnectedComponent = ({ getValue, newValue, scroll }) => {
235 | return
236 | }
237 |
238 | const mapValuesToProps = 'scroll'
239 |
240 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
241 |
242 | ```
243 |
244 | **mapValuesToProps as an array of strings**
245 |
246 | Enter the names of multiple global animated values you want passed as direct props.
247 |
248 | ```javascript
249 | ...
250 | import { connectSharedAnimation } from '@fernandorojo/react-native-shared-animation'
251 |
252 | const ConnectedComponent = ({ getValue, newValue, scroll, someOtherValue }) => {
253 | return
254 | }
255 |
256 | // const mapValuesToProps = ['scroll', 'someOtherValue']
257 |
258 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
259 |
260 | ```
261 |
262 | **mapValuesToProps a function**
263 |
264 | A function that takes in `animatedValues` as its first argument and returns an object that references these values. This can be useful if you want to abstract some animation logic out of your component.
265 |
266 | ```javascript
267 | ...
268 | import { connectSharedAnimation } from '@fernandorojo/react-native-shared-animation'
269 |
270 | const ConnectedComponent = ({ getValue, newValue, scroll, opacity }) => {
271 | return
272 | }
273 |
274 | const mapValuesToProps = animatedValues => {
275 | const { scroll } = animatedValues;
276 | const opacity = interpolate(scroll, {
277 | inputRange: [0, 400],
278 | outputRange: [1, 0]
279 | })
280 | return {
281 | scroll,
282 | opacity
283 | }
284 | }
285 |
286 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
287 |
288 | ```
289 |
290 | _The function works the same as redux's `mapStateToProps`._
291 |
292 | **mapStateToValues as null**
293 |
294 | You can pass `null` to it if you don't want any animated values explicitly passed to the component. If you pass `null`, you will still have access to `getValue` and `newValue`.
295 |
296 | ### Option 3: `SharedAnimation` Component
297 |
298 | A basic component that uses the render props method. (Similar in principle to the `[Query]()` component from `react-apollo`.)
299 |
300 | ```javascript
301 | ...
302 | import { SharedAnimation } from '@fernandorojo/react-native-shared-animation'
303 |
304 | export default () => {
305 | return (
306 |
307 | {({ getValue, newValue }) => {
308 | const scroll = getValue('scroll')
309 | return (
310 |
311 | )
312 | })}
313 |
314 | )
315 | }
316 |
317 | ```
318 |
319 | ---
320 |
321 | ### **`getValue(name)`**
322 |
323 | A function that takes in the name of a global animated value and returns the animated value itself.
324 |
325 | _This is the most important function that you'll find yourself using all the time._
326 |
327 | **Example**
328 |
329 | ```javascript
330 | const SomeComponent = () => {
331 | const { getValue } = useSharedAnimation();
332 |
333 | const opacity = getValue('opacity');
334 |
335 | return ;
336 | };
337 | ```
338 |
339 | ### **`animatedValues`**
340 |
341 | A dictionary containing the current global state of animated values. You can use this to access the global store directly, but I recommend using `getValue` instead, since it has some added convenience checks.
342 |
343 | **Example**
344 |
345 | ```javascript
346 | const SomeComponent = () => {
347 | const { animatedValues } = useSharedAnimation();
348 |
349 | const { opacity } = animatedValues;
350 |
351 | return ;
352 | };
353 | ```
354 |
355 | ### **`newValue(name, value)`**
356 |
357 | A function that creates a new global animated value. Takes a name as the first argument, and an animated value (or node) as the second argument.
358 |
359 | **Returns:** the animated value it just created
360 |
361 | **Example**
362 |
363 | ```javascript
364 | const SomeComponent = () => {
365 | const { newValue } = useSharedAnimation();
366 |
367 | const opacity = newValue('opacity', new Animated.Value(1));
368 |
369 | return ;
370 | };
371 | ```
372 |
373 | ---
374 |
375 | ## Documentation Recap
376 |
377 | ### ``
378 |
379 | | **Prop** | Required | Type | Example |
380 | | --- | --- | --- | --- |
381 | | `animatedValues` | no (but recommended) | `dictionary` | `{ scroll: new Animated.Value(0) }` |
382 | | `children` | yes | `React.Node` | Your app JSX should be a child component of this provider. |
383 |
384 | ## Illustrative example
385 |
386 | Sharing animated values across your entire app is as easy as this:
387 |
388 | ```javascript
389 | import React from 'react';
390 | import Animated from 'react-native-reanimated';
391 | import { SharedAnimationProvider, useSharedAnimation } from '@fernandorojo/react-native-shared-animation';
392 |
393 | export default function App() {
394 | const animatedValues = { scroll: new Animated.Value(0) };
395 | return (
396 |
397 |
398 |
399 |
400 | );
401 | }
402 |
403 | const OtherComponentThatAccessesScroll = () => {
404 | const { getValue } = useSharedAnimation();
405 | const scroll = getValue('scroll');
406 |
407 | return ;
408 | };
409 |
410 | const ComponentWithScrollView = () => {
411 | const { getValue } = useSharedAnimation();
412 | const scroll = getValue('scroll');
413 | const onScroll = Animated.event([
414 | {
415 | nativeEvent: {
416 | contentOffset: {
417 | y: scroll,
418 | },
419 | },
420 | },
421 | ]);
422 |
423 | return ;
424 | };
425 | ```
426 |
427 | Yup, that's it. No prop drilling at all.
428 |
--------------------------------------------------------------------------------
/examples/HocScroll.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, StyleSheet, Animated, Text } from 'react-native';
3 | import useSharedAnimation from '../lib/useSharedAnimation/index';
4 | import SharedAnimationProvider from '../lib/Context/SharedAnimationProvider';
5 | import { ReText } from 'react-native-redash';
6 | import connectSharedAnimation from '../lib/HOC/connectSharedAnimation';
7 |
8 | const { Value, event, divide } = Animated;
9 |
10 | export default () => {
11 | const scroll = new Value(0);
12 |
13 | const animatedValues = { scroll };
14 |
15 | return (
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | const App = () => {
23 | const { newValue, getValue } = useSharedAnimation();
24 |
25 | const scroll = getValue('scroll');
26 |
27 | const opacity = scroll.interpolate({
28 | inputRange: [0, 800],
29 | outputRange: [1, 0],
30 | });
31 | newValue('opacity', opacity);
32 |
33 | // notice...no props passed 👀
34 | return (
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | class Scroller extends React.Component {
43 | private onScroll;
44 | constructor(props) {
45 | super(props);
46 |
47 | this.onScroll = event([
48 | {
49 | nativeEvent: {
50 | contentOffset: {
51 | y: props.scroll,
52 | },
53 | },
54 | },
55 | ]);
56 | }
57 | render() {
58 | let backgrounds = ['red', 'green', 'pink', 'yellow', 'orange', 'black', 'purple'];
59 | backgrounds.forEach(() => backgrounds.push(...backgrounds));
60 |
61 | return (
62 |
63 | {``}
64 |
65 | Scroller and Slider have no props passed to them, but they still share an
66 | animated value!
67 |
68 | Scroll below!
69 |
70 | {backgrounds.map((backgroundColor, index) => (
71 |
72 | ))}
73 |
74 |
75 | );
76 | }
77 | }
78 |
79 | const ConnectedScroller = connectSharedAnimation('scroll')(Scroller);
80 |
81 | // you can also use the HOC with a functional component
82 | const Slider = ({ scroll, opacity }) => {
83 | const translateX = divide(scroll, 4);
84 |
85 | return (
86 |
87 | {``}
88 |
101 |
102 |
103 | );
104 | };
105 |
106 | const mapValuesToProps = animatedValues => ({
107 | scroll: animatedValues.scroll,
108 | opacity: animatedValues.opacity,
109 | });
110 |
111 | const ConnectedSlider = connectSharedAnimation(mapValuesToProps)(Slider);
112 |
113 | const styles = StyleSheet.create({
114 | text: {
115 | padding: 10,
116 | },
117 | bold: {
118 | fontWeight: 'bold',
119 | },
120 | header: {
121 | fontSize: 20,
122 | },
123 | box: {
124 | height: 100,
125 | width: 100,
126 | backgroundColor: 'blue',
127 | },
128 | slider: { flex: 0.5, justifyContent: 'center', padding: 10 },
129 | scroller: { flex: 1, borderTopWidth: 1 },
130 | });
131 |
--------------------------------------------------------------------------------
/examples/NativeScroll.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, StyleSheet, Animated, Text } from 'react-native';
3 | import useSharedAnimation from '../lib/useSharedAnimation/index';
4 | import SharedAnimationProvider from '../lib/Context/SharedAnimationProvider';
5 | import { ReText } from 'react-native-redash';
6 |
7 | // This example uses react-native's Animated
8 | // If you want to see the same example with react-native-reanimated, see ReanimatedScroll
9 | const { Value, event, divide } = Animated;
10 |
11 | export default () => {
12 | // first, declare a new animated value called scroll
13 | const scroll = new Value(0);
14 |
15 | // dictionary with all your initialized animatedValues
16 | // pass this as a prop to SharedAnimationProvider
17 | const animatedValues = { scroll };
18 |
19 | return (
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | const ScrollAndOpacity = () => {
27 | const {
28 | newValue,
29 | // read in the scroll value created above
30 | animatedValues: { scroll },
31 | } = useSharedAnimation();
32 |
33 | // just for fun, let's declare another global animated node here...
34 | // we initialize a SECOND animated value, opacity, nested inside of the provider
35 | const opacity = scroll.interpolate({
36 | inputRange: [0, 800],
37 | outputRange: [1, 0],
38 | });
39 | newValue('opacity', opacity);
40 |
41 | // notice that we pass do *not* need to pass this value as a prop
42 | // why? because it's globally accessible
43 | return (
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
51 | const Scroller = () => {
52 | // you can destructure an animation value like this
53 | // (if you do, remember to do .animatedValues at the end of the hook)
54 | const { getValue } = useSharedAnimation();
55 |
56 | // get the global scroll animated value
57 | const scroll = getValue('scroll');
58 | // update the scroll value when you scroll using Animated.event
59 | const onScroll = event([
60 | {
61 | nativeEvent: {
62 | contentOffset: {
63 | y: scroll,
64 | },
65 | },
66 | },
67 | ]);
68 |
69 | // make a big list of colors
70 | let backgrounds = ['red', 'green', 'pink', 'yellow', 'orange', 'black', 'purple'];
71 | backgrounds.forEach(() => backgrounds.push(...backgrounds));
72 |
73 | return (
74 |
75 | {``}
76 |
77 | Scroller and Slider have no props passed to them, but they still share an animated
78 | value!
79 |
80 | Scroll below!
81 |
82 | {backgrounds.map((backgroundColor, index) => (
83 |
84 | ))}
85 |
86 |
87 | );
88 | };
89 |
90 | const Slider = () => {
91 | // we can also access the values using the getValue function, unlike Scroller()
92 | const { getValue } = useSharedAnimation();
93 |
94 | const scroll = getValue('scroll');
95 | const opacity = getValue('opacity');
96 |
97 | const translateX = divide(scroll, 4);
98 |
99 | return (
100 |
101 | {``}
102 |
115 |
116 |
117 | );
118 | };
119 |
120 | const styles = StyleSheet.create({
121 | text: {
122 | padding: 10,
123 | },
124 | bold: {
125 | fontWeight: 'bold',
126 | },
127 | header: {
128 | fontSize: 20,
129 | },
130 | box: {
131 | height: 100,
132 | width: 100,
133 | backgroundColor: 'blue',
134 | },
135 | slider: { flex: 0.5, justifyContent: 'center', padding: 10 },
136 | scroller: { flex: 1, borderTopWidth: 1 },
137 | });
138 |
--------------------------------------------------------------------------------
/examples/ReanimatedScroll.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, StyleSheet, Text } from 'react-native';
3 | import useSharedAnimation from '../lib/useSharedAnimation/index';
4 | import Animated from 'react-native-reanimated';
5 | import { ReText } from 'react-native-redash';
6 |
7 | import SharedAnimationProvider from '../lib/Context/SharedAnimationProvider';
8 |
9 | const { Value, event, interpolate, divide } = Animated;
10 |
11 | export default () => {
12 | // first, declare a new animated value called scroll
13 | const scroll = new Value(0);
14 |
15 | // dictionary with all your initialized animatedValues
16 | // pass this as a prop to SharedAnimationProvider
17 | const animatedValues = { scroll };
18 |
19 | return (
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | const ScrollAndOpacity = () => {
27 | const {
28 | newValue,
29 | // read in the scroll value created above
30 | animatedValues: { scroll },
31 | } = useSharedAnimation();
32 |
33 | // just for fun, let's declare another global animated node here...
34 | // we initialize a SECOND animated value, opacity, nested inside of the provider
35 | const opacity = interpolate(scroll, {
36 | inputRange: [0, 800],
37 | outputRange: [1, 0],
38 | });
39 | newValue('opacity', opacity);
40 |
41 | // notice that we pass do *not* need to pass this value as a prop
42 | // why? because it's globally accessible
43 | return (
44 |
45 |
46 |
47 |
48 | );
49 | };
50 |
51 | const Scroller = () => {
52 | // you can destructure an animation value like this
53 | // (if you do, remember to do .animatedValues at the end of the hook)
54 | const { getValue } = useSharedAnimation();
55 |
56 | // get the global scroll animated value
57 | const scroll = getValue('scroll');
58 | // update the scroll value when you scroll using Animated.event
59 | const onScroll = event([
60 | {
61 | nativeEvent: {
62 | contentOffset: {
63 | y: scroll,
64 | },
65 | },
66 | },
67 | ]);
68 |
69 | // make a big list of colors
70 | let backgrounds = ['red', 'green', 'pink', 'yellow', 'orange', 'black', 'purple'];
71 | backgrounds.forEach(() => backgrounds.push(...backgrounds));
72 |
73 | return (
74 |
75 | Scroll below!
76 |
77 | This component doesn't share any direct props from the one above it.
78 |
79 |
80 |
81 | {backgrounds.map((backgroundColor, index) => (
82 |
83 | ))}
84 |
85 |
86 | );
87 | };
88 |
89 | const Slider = () => {
90 | // we can also access the values using the getValue function, unlike Scroller()
91 | const { getValue } = useSharedAnimation();
92 |
93 | const scroll = getValue('scroll');
94 | const opacity = getValue('opacity');
95 |
96 | const translateX = divide(scroll, 4);
97 |
98 | return (
99 |
100 |
113 | Animated Value:
114 | {/* */}
115 |
116 | );
117 | };
118 |
119 | const styles = StyleSheet.create({
120 | text: {
121 | padding: 10,
122 | },
123 | bold: {
124 | fontWeight: 'bold',
125 | fontSize: 20,
126 | },
127 | box: {
128 | height: 100,
129 | width: 100,
130 | backgroundColor: 'blue',
131 | },
132 | slider: { flex: 0.5, justifyContent: 'center', padding: 10 },
133 | scroller: { flex: 1, borderTopWidth: 1 },
134 | });
135 |
--------------------------------------------------------------------------------
/lib/Context/SharedAnimation.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4 |
5 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
6 |
7 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
8 |
9 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
10 |
11 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
12 |
13 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
16 |
17 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
18 |
19 | import React from 'react';
20 | import AnimationContext from './SharedAnimationContext';
21 |
22 | var SharedAnimation =
23 | /*#__PURE__*/
24 | function (_React$Component) {
25 | _inherits(SharedAnimation, _React$Component);
26 |
27 | function SharedAnimation() {
28 | _classCallCheck(this, SharedAnimation);
29 |
30 | return _possibleConstructorReturn(this, _getPrototypeOf(SharedAnimation).apply(this, arguments));
31 | }
32 |
33 | _createClass(SharedAnimation, [{
34 | key: "render",
35 | value: function render() {
36 | var _this = this;
37 |
38 | return null;
39 | return React.createElement(AnimationContext.Consumer, null, function (context) {
40 | return _this.props.children(context);
41 | });
42 | }
43 | }]);
44 |
45 | return SharedAnimation;
46 | }(React.Component);
47 |
48 | export { SharedAnimation as default };
--------------------------------------------------------------------------------
/lib/Context/SharedAnimationContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 | // const SharedAnimationContext = createContext({
3 | // getValue: () => console.error('SharedAnimation not properly initialized'),
4 | // setValue: () => console.error('SharedAnimation not properly initialized'),
5 | // animatedValues: {},
6 | // });
7 | var SharedAnimationContext = createContext({
8 | getValue: function getValue(name) {
9 | return name;
10 | },
11 | newValue: function newValue(_, value) {
12 | return value;
13 | },
14 | animatedValues: {}
15 | }); // const SharedAnimationContext = createContext();
16 |
17 | SharedAnimationContext.displayName = 'SharedAnimation';
18 | export default SharedAnimationContext;
--------------------------------------------------------------------------------
/lib/Context/SharedAnimationProvider.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4 |
5 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
6 |
7 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
8 |
9 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
10 |
11 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
12 |
13 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
16 |
17 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
18 |
19 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
20 |
21 | import React from 'react';
22 | import AnimationContext from './SharedAnimationContext';
23 | import { LIBRARY_NAME } from '../constants/index';
24 | import getValues from '../helpers/getValues';
25 |
26 | var SharedAnimationProvider =
27 | /*#__PURE__*/
28 | function (_React$Component) {
29 | _inherits(SharedAnimationProvider, _React$Component);
30 |
31 | function SharedAnimationProvider(props) {
32 | var _this;
33 |
34 | _classCallCheck(this, SharedAnimationProvider);
35 |
36 | _this = _possibleConstructorReturn(this, _getPrototypeOf(SharedAnimationProvider).call(this, props));
37 |
38 | _defineProperty(_assertThisInitialized(_this), "animatedValues", void 0);
39 |
40 | _this.animatedValues = props.animatedValues || {};
41 | _this.getValue = _this.getValue.bind(_assertThisInitialized(_this));
42 | _this.newValue = _this.newValue.bind(_assertThisInitialized(_this));
43 | _this.getState = _this.getState.bind(_assertThisInitialized(_this));
44 | _this.get = _this.get.bind(_assertThisInitialized(_this));
45 | return _this;
46 | }
47 |
48 | _createClass(SharedAnimationProvider, [{
49 | key: "getState",
50 | value: function getState() {
51 | return this.animatedValues;
52 | }
53 | }, {
54 | key: "get",
55 | value: function get(mapValuesToProps) {
56 | return getValues(mapValuesToProps, this);
57 | }
58 | }, {
59 | key: "getValue",
60 | value: function getValue(name) {
61 | try {
62 | var value = this.animatedValues[name];
63 |
64 | if (!value) {
65 | throw new Error("\nError in ".concat(LIBRARY_NAME, "'s getValue() function.\n\uD83D\uDC4Banimation value {").concat(name, "} is related to the problem\nTried to get animated value ").concat(name, ", but it failed...\nThis probably means this Animated value was never initialized, or it isn't a valid Animated value from react-native-reanimated.\nAre you sure you initialized an Animated value using setValue(name, animatedValue) where the name is ").concat(name, "?\nIf that doesn't do it, try running npm i react-native-reanimated react-native-gesture-handler in the terminal.\n\t\t\t"));
66 | }
67 |
68 | return value;
69 | } catch (e) {
70 | console.error(e);
71 | }
72 | }
73 | }, {
74 | key: "newValue",
75 | value: function newValue(name, value) {
76 | try {
77 | if (!value) {
78 | throw new Error("\nError in ".concat(LIBRARY_NAME, "'s newValue() function.\n\uD83D\uDC4Banimation value ").concat(name, " is related to the problem\nExpected a valid Animated.Value as the second argument, but got ").concat(value, " instead.\nIt should look something like this:\nimport Animated from 'react-native-reanimated'\nconst { Value } = Animated;\n...\nconst value = new Value(initialValue)\nnewValue(name, value);\n\nIf that doesn't do it, try running npm i react-native-reanimated react-native-gesture-handler in the terminal.\n\t\t\t\t"));
79 | }
80 |
81 | this.animatedValues[name] = value;
82 | return value;
83 | } catch (e) {
84 | console.error(e);
85 | }
86 | }
87 | }, {
88 | key: "render",
89 | value: function render() {
90 | var children = this.props.children;
91 | var newValue = this.newValue,
92 | getValue = this.getValue,
93 | animatedValues = this.animatedValues;
94 | return React.createElement(AnimationContext.Provider, {
95 | value: {
96 | newValue: newValue,
97 | getValue: getValue,
98 | animatedValues: animatedValues
99 | }
100 | }, children);
101 | }
102 | }]);
103 |
104 | return SharedAnimationProvider;
105 | }(React.Component); // const AnimationProvider = ({ children }: ProviderProps) => {
106 | // return {children};
107 | // };
108 |
109 |
110 | export { SharedAnimationProvider as default };
--------------------------------------------------------------------------------
/lib/Context/types.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/shared-animations/3035b75c2b7a9e1f608b045216d497ff4b0aeb19/lib/Context/types.js
--------------------------------------------------------------------------------
/lib/HOC/connectSharedAnimation.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
4 |
5 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
6 |
7 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
8 |
9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
10 |
11 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
12 |
13 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
14 |
15 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
16 |
17 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
18 |
19 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
20 |
21 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
22 |
23 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
24 |
25 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
26 |
27 | import React from 'react';
28 | import AnimationContext from '../Context/SharedAnimationContext';
29 | import getValues from '../helpers/getValues';
30 |
31 | var connectSharedAnimation = function connectSharedAnimation(mapValuesToProps) {
32 | return function (WrappedComponent) {
33 | var _class, _temp;
34 |
35 | return _temp = _class =
36 | /*#__PURE__*/
37 | function (_React$Component) {
38 | _inherits(_class, _React$Component);
39 |
40 | function _class() {
41 | _classCallCheck(this, _class);
42 |
43 | return _possibleConstructorReturn(this, _getPrototypeOf(_class).apply(this, arguments));
44 | }
45 |
46 | _createClass(_class, [{
47 | key: "values",
48 | value: function values() {
49 | var context = this.context;
50 | return getValues(mapValuesToProps, context);
51 | }
52 | }, {
53 | key: "render",
54 | value: function render() {
55 | var _this$context = this.context,
56 | newValue = _this$context.newValue,
57 | getValue = _this$context.getValue;
58 | var values = this.values;
59 | return React.createElement(WrappedComponent, _extends({}, this.props, _objectSpread({
60 | getValue: getValue,
61 | newValue: newValue
62 | }, values())));
63 | }
64 | }]);
65 |
66 | return _class;
67 | }(React.Component), _defineProperty(_class, "contextType", AnimationContext), _temp;
68 | };
69 | };
70 |
71 | export default connectSharedAnimation;
--------------------------------------------------------------------------------
/lib/HOC/types.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/shared-animations/3035b75c2b7a9e1f608b045216d497ff4b0aeb19/lib/HOC/types.js
--------------------------------------------------------------------------------
/lib/constants/index.js:
--------------------------------------------------------------------------------
1 | export var LIBRARY_NAME = 'react-native-shared-animation';
--------------------------------------------------------------------------------
/lib/helpers/getValues.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
4 |
5 | export default (function (mapValuesToProps, context) {
6 | try {
7 | if (typeof mapValuesToProps === 'string') {
8 | var _key = mapValuesToProps;
9 | return _defineProperty({}, _key, context.getValue(_key));
10 | } else if (Array.isArray(mapValuesToProps)) {
11 | if (!mapValuesToProps.length) {
12 | throw new Error('Error in connectSharedAnimation HOC first argument. Cannot pass empty array, expected names of valid animation values.');
13 | }
14 |
15 | var validArray = mapValuesToProps.reduce(function (a, b) {
16 | return a && typeof b === 'string';
17 | }, true);
18 |
19 | if (!validArray) {
20 | throw new Error("Error in first argument of connectSharedAnimation HOC. An array was passed, but values were not all strings.\nThis argument should either be a string, array of strings, or object creator function that takes animatedValues as the first argument. In the first two cases, strings should correspond to the names of initialized animated values.");
21 | }
22 |
23 | var values = {};
24 | var valueList = mapValuesToProps;
25 | valueList.forEach(function (name) {
26 | values[name] = context.getValue(name);
27 | });
28 | return values;
29 | } else if (typeof mapValuesToProps === 'function') {
30 | return mapValuesToProps(context.animatedValues);
31 | } else if (mapValuesToProps === undefined) {
32 | throw new Error('mapValuesToProps argument was undefined. check instance of connectSharedAnimation HOC If you do not want to get any values, pass null as the first argument.');
33 | } else if (mapValuesToProps === null) {
34 | return {};
35 | }
36 |
37 | throw new Error("\nError with first argument passed to connectSharedAnimation() HOC\nRemember to make it null if you don't want to pass any values directly.\n\nOtherwise, this argument should either be a string, array of strings, or object creator function that takes animatedValues as the first argument.\nInstead, it got ".concat(mapValuesToProps, ": (").concat(_typeof(mapValuesToProps), " as the type)\n\t\t"));
38 | } catch (e) {
39 | console.error(e);
40 | return {};
41 | }
42 | });
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import SharedAnimationProvider from './Context/SharedAnimationProvider';
2 | import connectSharedAnimation from './HOC/connectSharedAnimation';
3 | import SharedAnimation from './Context/SharedAnimation';
4 | import useSharedAnimation from './useSharedAnimation/index';
5 | export default {
6 | SharedAnimationProvider: SharedAnimationProvider,
7 | connectSharedAnimation: connectSharedAnimation,
8 | SharedAnimation: SharedAnimation,
9 | useSharedAnimation: useSharedAnimation
10 | };
--------------------------------------------------------------------------------
/lib/types/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/shared-animations/3035b75c2b7a9e1f608b045216d497ff4b0aeb19/lib/types/index.js
--------------------------------------------------------------------------------
/lib/useSharedAnimation/index.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import AnimationContext from '../Context/SharedAnimationContext';
3 |
4 | var useSharedAnimation = function useSharedAnimation() {
5 | var _useContext = useContext(AnimationContext),
6 | getValue = _useContext.getValue,
7 | newValue = _useContext.newValue,
8 | animatedValues = _useContext.animatedValues;
9 |
10 | var obj = {
11 | getValue: getValue,
12 | newValue: newValue,
13 | animatedValues: animatedValues
14 | };
15 | return obj;
16 | };
17 |
18 | export default useSharedAnimation;
--------------------------------------------------------------------------------
/nandorojo-shared-animations-1.0.7.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/shared-animations/3035b75c2b7a9e1f608b045216d497ff4b0aeb19/nandorojo-shared-animations-1.0.7.tgz
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nandorojo/shared-animations",
3 | "description": "A nimble global animtion-state management tool for react-native's `Animated` values. Think of it like a simple redux for react-native animation values.",
4 | "version": "1.0.28",
5 | "main": "lib/module/index",
6 | "module": "lib/module/index",
7 | "react-native": "lib/module/index",
8 | "types": "lib/typescript/index",
9 | "homepage": "https://github.com/nandorojo/shared-animations",
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/nandorojo/shared-animations"
13 | },
14 | "scripts": {
15 | "type-check": "tsc --noEmit",
16 | "type-check:watch": "npm run type-check -- --watch",
17 | "build": "npm run build:types && npm run build:js",
18 | "bundle": "babel src --out-file bundle/index.js --extensions \".ts,.tsx\" --source-maps inline",
19 | "build:tsc": "npm run build:types && npm run build:test",
20 | "build:redash:types": "tsc --noEmit",
21 | "build:types": "tsc --emitDeclarationOnly --skipLibCheck",
22 | "build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
23 | "build:test": "rm -R lib && babel src-og --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
24 | "build:babel": "rm -R lib && babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
25 | "build:final": "mkdir -p bundle && npm run bundle && npm run build:types",
26 | "prepare": "bob build"
27 | },
28 | "peerDependencies": {
29 | "react": "*",
30 | "react-native": "*",
31 | "react-native-reanimated": "*"
32 | },
33 | "devDependencies": {
34 | "@babel/cli": "^7.2.3",
35 | "@babel/core": "^7.4.0",
36 | "@babel/plugin-proposal-class-properties": "^7.4.0",
37 | "@babel/plugin-proposal-numeric-separator": "^7.2.0",
38 | "@babel/plugin-proposal-object-rest-spread": "^7.4.0",
39 | "@babel/preset-env": "^7.4.1",
40 | "@babel/preset-react": "^7.0.0",
41 | "@babel/preset-typescript": "^7.3.3",
42 | "@react-native-community/bob": "^0.6.1",
43 | "@types/react-native": "^0.60.4",
44 | "babel-preset-expo": "^5.2.0",
45 | "typescript": "^3.3.3"
46 | },
47 | "dependencies": {
48 | "@types/react": "^16.9.1",
49 | "@types/react-dom": "^16.8.5",
50 | "react": "16.8.3",
51 | "react-dom": "^16.9.0",
52 | "react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",
53 | "react-native-reanimated": "^1.2.0"
54 | },
55 | "bugs": {
56 | "url": "https://github.com/nandorojo/shared-animations/issues"
57 | },
58 | "author": "Fernando Rojo",
59 | "license": "MIT",
60 | "files": [
61 | "lib",
62 | "src"
63 | ],
64 | "@react-native-community/bob": {
65 | "source": "src",
66 | "output": "lib",
67 | "targets": [
68 | "module",
69 | "typescript"
70 | ]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # 🕺shared-animations for React Native
2 |
3 | A global state management tool, built for react-native's `Animated` values.
4 |
5 | Like redux for animations.
6 |
7 | ## Why?
8 |
9 | **Sharing animated values across components should be easy.** However, it currently requires so much prop drilling that any complex animation in `react-native` becomes a hassle to manage.
10 |
11 | ## Quick setup
12 |
13 | The boilerplate setup takes about 15 seconds and is similar to redux in how it works. Just wrap your entire app with the `` component.
14 |
15 | **NestedComponent.js**
16 |
17 | ```javascript
18 | import React from 'react'
19 | import { useSharedAnimation } from 'react-native-shared-animation'
20 | import Animated from 'react-native-reanimated'
21 |
22 | export default () => {
23 | // here we get the value from our global store using react hooks
24 | const { getValue } = useSharedAnimation()
25 | const coolValue = getValue('myCoolAnimatedValue')
26 |
27 | return
28 | }
29 | ```
30 |
31 | **App.js**
32 |
33 | ```javascript
34 | import React from 'react'
35 | import { SharedAnimationProvider } from 'react-native-shared-animation'
36 | import Animated from 'react-native-reanimated'
37 | import App from './src/App'
38 |
39 | export default () => {
40 | const animatedValues = { myCoolAnimatedValue: new Animated.Value(0) }
41 | return (
42 |
43 |
44 |
45 | )
46 | }
47 | ```
48 |
49 | 🎉 **All set. Your app is now ready to share animated values across components.**
50 |
51 | Below I'll expand on all the ways that you're able to **1) initialize animated values** and **2) access animated values**.
52 |
53 | ## Examples
54 |
55 | **To see full examples, go to the [/examples](https://github.com/nandorojo/shared-animations/tree/master/examples) folder.** You can also see the Expo snack of examples [here](https://snack.expo.io/@nandorojo/react-native-shared-animations).
56 |
57 | ## Installation
58 |
59 | To install, open your react native repository in the terminal and run this command:
60 |
61 | ```
62 | npm i @nandorojo/shared-animations
63 | ```
64 |
65 | You could use yarn if you prefer that:
66 |
67 | ```
68 | yarn add @nandorojo/shared-animations
69 | ```
70 |
71 | **Recommended:** If you want to use [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated) for animations, run this afterwards:
72 |
73 | ```
74 | npm i react-native-reanimated react-native-gesture-handler
75 | ```
76 |
77 | ### This works with...
78 |
79 | ✅ `react-native-reanimated`
80 |
81 | ✅ `Animated` from `react-native` if you prefer that.
82 |
83 | ✅ `Expo`
84 |
85 | ✅ `Typescript`
86 |
87 | ✅ `react-native-gesture-handler`
88 |
89 | ## 1) Initializing shared animated values
90 |
91 | You have two options for initializing shared animation values: global initialization or on-the-fly initializiation in components.
92 |
93 | ### i) [Recommended] Initialize global animated values
94 |
95 | Simply pass an `animatedValues` object as a prop to the `` component. This will act as the initial set of animated values.
96 |
97 | You can initialize as many animated values as you'd like.
98 |
99 | **App.js**
100 |
101 | ```javascript
102 | import React from 'react'
103 | import { SharedAnimationProvider } from 'react-native-shared-animation'
104 | import Animated from 'react-native-reanimated'
105 | import App from './src/App' // path to your root component
106 |
107 | export default () => {
108 | const mainScrollValue = new Animated.Value(0)
109 | const animatedValues = { mainScrollValue }
110 |
111 |
112 |
113 |
114 | }
115 |
116 | ```
117 |
118 | **🐻 That's it! Your app now has globally-accessible animated values.**
119 |
120 | In this case, `mainScrollValue` can be accessed by **any** component.
121 |
122 | If you come from a redux background, you can think of this like setting the initial store value.
123 |
124 | #### Why this is the better option:
125 |
126 | From a style perspective, it is useful to know what values will be accessible across your app upon initialization. And when it comes to performance, this is less prone to bugs, since you'll never try to access a value that hasn't been initialized.
127 |
128 | That said, you also have the `newValue` method to your disposal, as described in the next option.
129 |
130 | ### **ii) [Careful] Initialize animated values on the fly in components**
131 |
132 | You can also initialize animated values directly in components. The thing is, this option is more prone to bugs, since you might try to access an animated value before it's been initialized.
133 |
134 | Overall, I'd suggest only using this one on a case-by-case basis.
135 |
136 | It can be achieved with the `newValue(name, value)` function, documented [below]().
137 |
138 | ## 2) Accessing animated values
139 |
140 | Here's the fun part.
141 |
142 | ---
143 |
144 | ### There are 3 ways to access animated values
145 |
146 | These are the 3 options you have:
147 |
148 | 1. **`useSharedAnimation` hook:** The `useSharedAnimation` hook is super simple (and is my favorite to use). Only works in function components. See react hooks to learn more.
149 |
150 | 2. **`connectSharedAnimation` HOC:** You can use the `connectSharedAnimation` higher-order component. Useful for class components and function components. Good for taking animation logic out of a component, too.
151 |
152 | 3. **`SharedAnimation` component:** You can also wrap any component with `` to connect it to the global animation state.
153 |
154 | ---
155 |
156 | ### Option 1: `useSharedAnimation`
157 |
158 | Call `useSharedAnimation` in the root of a function component.
159 |
160 | **Example:**
161 |
162 | ```javascript
163 | ...
164 | import { useSharedAnimation } from 'react-native-shared-animation'
165 |
166 | export default () => {
167 | const { getValue, newValue, animatedValues } = useSharedAnimation();
168 | const scroll = getValue('scroll')
169 | // same as...
170 | const { scroll } = animatedValues;
171 |
172 | return
173 | }
174 |
175 | ```
176 |
177 | So simple!
178 |
179 | 🤑🤑
180 |
181 | **If you're using a version of react / react-native that supports hooks, you can happily stop reading the docs here and just use this.**
182 |
183 | --
184 |
185 | ### Option 2: `connectSharedAnimation(mapValuesToProps)(Component)`
186 |
187 | You can also use the `connectSharedAnimation` higher-order component to pass animated values as props.
188 |
189 | This option gives you some customization options beyond the `useSharedAnimation`, such as taking global animated code out of your actual component.
190 |
191 | **This HOC passes `newValue`, `getValue`, and any animated values you choose as props to your `Component`.**
192 |
193 | **Example:**
194 |
195 | ```javascript
196 | ...
197 | import { connectSharedAnimation } from 'react-native-shared-animation'
198 |
199 | const ConnectedComponent = ({ getValue, newValue, scroll }) => {
200 | return
201 | }
202 |
203 | // determine which values you want to pass to this component
204 | const mapValuesToProps = animatedValues => ({
205 | scroll: animatedValues.scroll
206 | })
207 |
208 | // could also have done this:
209 | // const mapValuesToProps = 'scroll'
210 |
211 | // ...or this:
212 | // const mapValuesToProps = ['scroll', 'someOtherValue']
213 |
214 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
215 |
216 | ```
217 |
218 | #### `mapValuesToProps` (required)
219 |
220 | This is the first and only argument for `connectSharedAnimation`. It determines which animated values will be passed to the component as direct props.
221 |
222 | This value can be either a `string`, `array of strings`, a `function`, or `null`.
223 |
224 | **mapValuesToProps as a string**
225 |
226 | The string should correspond to an existing global animated value.
227 |
228 | ```javascript
229 | ...
230 | import { connectSharedAnimation } from 'react-native-shared-animation'
231 |
232 | const ConnectedComponent = ({ getValue, newValue, scroll }) => {
233 | return
234 | }
235 |
236 | const mapValuesToProps = 'scroll'
237 |
238 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
239 |
240 | ```
241 |
242 | **mapValuesToProps as an array of strings**
243 |
244 | Enter the names of multiple global animated values you want passed as direct props.
245 |
246 | ```javascript
247 | ...
248 | import { connectSharedAnimation } from 'react-native-shared-animation'
249 |
250 | const ConnectedComponent = ({ getValue, newValue, scroll, someOtherValue }) => {
251 | return
252 | }
253 |
254 | // const mapValuesToProps = ['scroll', 'someOtherValue']
255 |
256 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
257 |
258 | ```
259 |
260 | **mapValuesToProps a function**
261 |
262 | A function that takes in `animatedValues` as its first argument and returns an object that references these values. This can be useful if you want to abstract some animation logic out of your component.
263 |
264 | ```javascript
265 | ...
266 | import { connectSharedAnimation } from 'react-native-shared-animation'
267 |
268 | const ConnectedComponent = ({ getValue, newValue, scroll, opacity }) => {
269 | return
270 | }
271 |
272 | const mapValuesToProps = animatedValues => {
273 | const { scroll } = animatedValues;
274 | const opacity = interpolate(scroll, {
275 | inputRange: [0, 400],
276 | outputRange: [1, 0]
277 | })
278 | return {
279 | scroll,
280 | opacity
281 | }
282 | }
283 |
284 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
285 |
286 | ```
287 |
288 | _The function works the same as redux's `mapStateToProps`._
289 |
290 | **mapStateToValues as null**
291 |
292 | You can pass `null` to it if you don't want any animated values explicitly passed to the component. If you pass `null`, you will still have access to `getValue` and `newValue`.
293 |
294 | ### Option 3: `SharedAnimation` Component
295 |
296 | A basic component that uses the render props method. (Similar in principle to the `[Query]()` component from `react-apollo`.)
297 |
298 | ```javascript
299 | ...
300 | import { SharedAnimation } from 'react-native-shared-animation'
301 |
302 | export default () => {
303 | return (
304 |
305 | {({ getValue, newValue }) => {
306 | const scroll = getValue('scroll')
307 | return (
308 |
309 | )
310 | })}
311 |
312 | )
313 | }
314 |
315 | ```
316 |
317 | ---
318 |
319 | ### **`getValue(name)`**
320 |
321 | A function that takes in the name of a global animated value and returns the animated value itself.
322 |
323 | _This is the most important function that you'll find yourself using all the time._
324 |
325 | **Example**
326 |
327 | ```javascript
328 | const SomeComponent = () => {
329 | const { getValue } = useSharedAnimation()
330 |
331 | const opacity = getValue('opacity')
332 |
333 | return
334 | }
335 | ```
336 |
337 | ### **`animatedValues`**
338 |
339 | A dictionary containing the current global state of animated values. You can use this to access the global store directly, but I recommend using `getValue` instead, since it has some added convenience checks.
340 |
341 | **Example**
342 |
343 | ```javascript
344 | const SomeComponent = () => {
345 | const { animatedValues } = useSharedAnimation()
346 |
347 | const { opacity } = animatedValues
348 |
349 | return
350 | }
351 | ```
352 |
353 | ### **`newValue(name, value)`**
354 |
355 | A function that creates a new global animated value. Takes a name as the first argument, and an animated value (or node) as the second argument.
356 |
357 | **Returns:** the animated value it just created
358 |
359 | **Example**
360 |
361 | ```javascript
362 | const SomeComponent = () => {
363 | const { newValue } = useSharedAnimation()
364 |
365 | const opacity = newValue('opacity', new Animated.Value(1))
366 |
367 | return
368 | }
369 | ```
370 |
371 | ---
372 |
373 | ## Documentation Recap
374 |
375 | ### ``
376 |
377 | | **Prop** | Required | Type | Example |
378 | | --- | --- | --- | --- |
379 | | `animatedValues` | no (but recommended) | `dictionary` | `{ scroll: new Animated.Value(0) }` |
380 | | `children` | yes | `React.Node` | Your app JSX should be a child component of this provider. |
381 |
382 | ## Illustrative example
383 |
384 | Sharing animated values across your entire app is as easy as this:
385 |
386 | ```javascript
387 | import React from 'react'
388 | import Animated from 'react-native-reanimated'
389 | import { SharedAnimationProvider, useSharedAnimation } from 'react-native-shared-animation'
390 |
391 | export default function App() {
392 | const animatedValues = { scroll: new Animated.Value(0) }
393 | return (
394 |
395 |
396 |
397 |
398 | )
399 | }
400 |
401 | const OtherComponentThatAccessesScroll = () => {
402 | const { getValue } = useSharedAnimation()
403 | const scroll = getValue('scroll')
404 |
405 | return
406 | }
407 |
408 | const ComponentWithScrollView = () => {
409 | const { getValue } = useSharedAnimation()
410 | const scroll = getValue('scroll')
411 | const onScroll = Animated.event([
412 | {
413 | nativeEvent: {
414 | contentOffset: {
415 | y: scroll,
416 | },
417 | },
418 | },
419 | ])
420 |
421 | return
422 | }
423 | ```
424 |
425 | Yup, that's it. No prop drilling at all.
426 |
--------------------------------------------------------------------------------
/src/Context/SharedAnimation.tsx:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import React from 'react'
3 | import AnimationContext, { AnimationContextType } from './SharedAnimationContext'
4 |
5 | interface AnimationConsumerProps {
6 | children: (context: AnimationContextType) => React.ReactNode
7 | }
8 |
9 | export default class SharedAnimation extends React.Component {
10 | render() {
11 | return (
12 |
13 | {(context: AnimationContextType) => this.props.children(context)}
14 |
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Context/SharedAnimationContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react'
2 | import { AnimatedType } from '../types/index'
3 |
4 | export interface AnimationContextType {
5 | getValue: (name: string) => AnimatedType
6 | newValue: (name: string, value: AnimatedType) => AnimatedType
7 | animatedValues: { [key: string]: AnimatedType }
8 | }
9 |
10 | // const SharedAnimationContext = createContext({
11 | // getValue: () => console.error('SharedAnimation not properly initialized'),
12 | // setValue: () => console.error('SharedAnimation not properly initialized'),
13 | // animatedValues: {},
14 | // });
15 |
16 | // @ts-ignore
17 | const SharedAnimationContext = createContext()
18 | SharedAnimationContext.displayName = 'SharedAnimation'
19 |
20 | export default SharedAnimationContext
21 |
--------------------------------------------------------------------------------
/src/Context/SharedAnimationProvider.tsx:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import React from 'react'
3 | import AnimationContext from './SharedAnimationContext'
4 | import { LIBRARY_NAME } from '../constants/index'
5 | import getValues from '../helpers/getValues'
6 | import { AnimatedType } from '../types/index'
7 | import { MapValuesToProps } from '../HOC/types'
8 |
9 | interface AnimatedValues {
10 | [key: string]: AnimatedType
11 | }
12 | interface ProviderProps {
13 | children: React.ReactNode
14 | animatedValues?: AnimatedValues
15 | }
16 |
17 | export default class SharedAnimationProvider extends React.Component {
18 | public animatedValues: AnimatedValues
19 | constructor(props: ProviderProps) {
20 | super(props)
21 |
22 | this.animatedValues = props.animatedValues || {}
23 | this.getValue = this.getValue.bind(this)
24 | this.newValue = this.newValue.bind(this)
25 | this.getState = this.getState.bind(this)
26 | this.get = this.get.bind(this)
27 | }
28 | getState() {
29 | return this.animatedValues
30 | }
31 | get(mapValuesToProps: MapValuesToProps) {
32 | return getValues(mapValuesToProps, this)
33 | }
34 | getValue(name: string) {
35 | try {
36 | const value: AnimatedType = this.animatedValues[name]
37 | if (!value) {
38 | throw new Error(`
39 | Error in ${LIBRARY_NAME}'s getValue() function.
40 | 👋animation value {${name}} is related to the problem
41 | Tried to get animated value ${name}, but it failed...
42 | This probably means this Animated value was never initialized, or it isn't a valid Animated value from react-native-reanimated.
43 | Are you sure you initialized an Animated value using setValue(name, animatedValue) where the name is ${name}?
44 | If that doesn't do it, try running npm i react-native-reanimated react-native-gesture-handler in the terminal.
45 | `)
46 | }
47 | return value
48 | } catch (e) {
49 | console.error(e)
50 | }
51 | }
52 | newValue(name: string, value: AnimatedType) {
53 | try {
54 | if (!value) {
55 | throw new Error(`
56 | Error in ${LIBRARY_NAME}'s newValue() function.
57 | 👋animation value ${name} is related to the problem
58 | Expected a valid Animated.Value as the second argument, but got ${value} instead.
59 | It should look something like this:
60 | import Animated from 'react-native-reanimated'
61 | const { Value } = Animated;
62 | ...
63 | const value = new Value(initialValue)
64 | newValue(name, value);
65 |
66 | If that doesn't do it, try running npm i react-native-reanimated react-native-gesture-handler in the terminal.
67 | `)
68 | }
69 | this.animatedValues[name] = value
70 | return value
71 | } catch (e) {
72 | console.error(e)
73 | }
74 | }
75 | render() {
76 | const { children } = this.props
77 | const { newValue, getValue, animatedValues } = this
78 | return (
79 |
80 | {children}
81 |
82 | )
83 | }
84 | }
85 |
86 | // const AnimationProvider = ({ children }: ProviderProps) => {
87 | // return {children};
88 | // };
89 |
--------------------------------------------------------------------------------
/src/Context/types.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nandorojo/shared-animations/3035b75c2b7a9e1f608b045216d497ff4b0aeb19/src/Context/types.ts
--------------------------------------------------------------------------------
/src/HOC/connectSharedAnimation.tsx:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import React from 'react'
3 | import AnimationContext, { AnimationContextType } from '../Context/SharedAnimationContext'
4 | import getValues from '../helpers/getValues'
5 | import { MapValuesToProps } from './types'
6 |
7 | const connectSharedAnimation = (mapValuesToProps: MapValuesToProps) => {
8 | return (WrappedComponent: React.ComponentType | React.ElementType) => {
9 | return class extends React.Component {
10 | static contextType = AnimationContext
11 | values() {
12 | const { context }: { context: AnimationContextType } = this
13 | return getValues(mapValuesToProps, context)
14 | }
15 | render() {
16 | const { newValue, getValue } = this.context
17 | const { values } = this
18 | return
19 | }
20 | }
21 | }
22 | }
23 |
24 | export default connectSharedAnimation
25 |
--------------------------------------------------------------------------------
/src/HOC/types.ts:
--------------------------------------------------------------------------------
1 | type MapAnimationValueToProps = (animatedValuesFromContext: object) => object
2 |
3 | export type MapValuesToProps = string | string[] | MapAnimationValueToProps | null
4 |
--------------------------------------------------------------------------------
/src/constants/index.tsx:
--------------------------------------------------------------------------------
1 | export const LIBRARY_NAME = 'react-native-shared-animation'
2 |
--------------------------------------------------------------------------------
/src/helpers/getValues.ts:
--------------------------------------------------------------------------------
1 | import { AnimationContextType } from '../Context/SharedAnimationContext'
2 | import { MapValuesToProps } from '../HOC/types'
3 | import { AnimatedType } from '../types/index'
4 |
5 | export default (mapValuesToProps: MapValuesToProps, context: AnimationContextType) => {
6 | try {
7 | if (typeof mapValuesToProps === 'string') {
8 | const key = mapValuesToProps
9 | return { [key]: context.getValue(key) }
10 | } else if (Array.isArray(mapValuesToProps)) {
11 | if (!mapValuesToProps.length) {
12 | throw new Error(
13 | 'Error in connectSharedAnimation HOC first argument. Cannot pass empty array, expected names of valid animation values.',
14 | )
15 | }
16 | const validArray = mapValuesToProps.reduce((a, b) => a && typeof b === 'string', true)
17 | if (!validArray) {
18 | throw new Error(`Error in first argument of connectSharedAnimation HOC. An array was passed, but values were not all strings.
19 | This argument should either be a string, array of strings, or object creator function that takes animatedValues as the first argument. In the first two cases, strings should correspond to the names of initialized animated values.`)
20 | }
21 | const values: { [key: string]: AnimatedType } = {}
22 | const valueList = mapValuesToProps
23 | valueList.forEach((name: string) => {
24 | values[name] = context.getValue(name)
25 | })
26 | return values
27 | } else if (typeof mapValuesToProps === 'function') {
28 | return mapValuesToProps(context.animatedValues)
29 | } else if (mapValuesToProps === undefined) {
30 | throw new Error(
31 | 'mapValuesToProps argument was undefined. check instance of connectSharedAnimation HOC If you do not want to get any values, pass null as the first argument.',
32 | )
33 | } else if (mapValuesToProps === null) {
34 | return {}
35 | }
36 | throw new Error(`
37 | Error with first argument passed to connectSharedAnimation() HOC
38 | Remember to make it null if you don't want to pass any values directly.
39 |
40 | Otherwise, this argument should either be a string, array of strings, or object creator function that takes animatedValues as the first argument.
41 | Instead, it got ${mapValuesToProps}: (${typeof mapValuesToProps} as the type)
42 | `)
43 | } catch (e) {
44 | console.error(e)
45 | return {}
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import SharedAnimationProvider from './Context/SharedAnimationProvider'
2 | import connectSharedAnimation from './HOC/connectSharedAnimation'
3 | import SharedAnimation from './Context/SharedAnimation'
4 | import useSharedAnimation from './useSharedAnimation/index'
5 |
6 | export { SharedAnimationProvider, connectSharedAnimation, SharedAnimation, useSharedAnimation }
7 |
--------------------------------------------------------------------------------
/src/readme.md:
--------------------------------------------------------------------------------
1 | # 🕺 react-native-shared-animation
2 |
3 | A nimble global animtion-state management tool for react-native's `Animated` values. Think of it like a simple redux for react-native animation values.
4 |
5 | This package is super easy to use and requires no more than 1 minute of learning.
6 |
7 | Also supports `react-native-reanimated` and `react-native-gesture-handler`.
8 |
9 | ## Why?
10 |
11 | **Sharing animated values across components should be easy.** However, it currently requires so much prop drilling that any complex animation in `react-native` becomes a hassle to manage.
12 |
13 | **🤔 When might you use this?**
14 |
15 | Any time you find yourself passing a certain animation value from one component to another more than once, `react-native-shared-animation` feels like a breath of fresh air.
16 |
17 | **Example use case**
18 |
19 | Maybe you have a component with a `ScrollView`, and a header you want to react to its scroll position, but the two components aren't that close together code-wise. If this were the case, without this library, have to declare an animated value high up in the component tree, and then pass it down the three through many layers of components.
20 |
21 | This library aims to fix that.
22 |
23 | I also think this could help make it easy to achieve shared transitions across screens. I haven't put together an example for that yet, so if you do, please submit a PR :)
24 |
25 | **Syntax**
26 |
27 | Much of the syntax is similar to `react-redux`, but if you aren't familiar with how redux works, don't worry; this is much simpler.
28 |
29 |
30 |
31 | ## Quick setup
32 |
33 | The boilerplate setup takes about 15 seconds and is similar to redux in how it works. Just wrap your entire app with the `` component.
34 |
35 | **App.js**
36 |
37 | ```javascript
38 | import React from 'react'
39 | import { SharedAnimationProvider } from 'react-native-shared-animation'
40 | import Animated from 'react-native-reanimated'
41 | import App from './src/App'
42 |
43 | export default () => {
44 | const animatedValues = { myCoolAnimatedValue: new Animated.Value(0) }
45 | return (
46 |
47 |
48 |
49 | )
50 | }
51 |
52 | ```
53 |
54 |
55 | In some other nested component, all you'd need to do is this:
56 |
57 | ```javascript
58 | import React from 'react'
59 | import { useSharedAnimation } from 'react-native-shared-animation'
60 | import Animated from 'react-native-reanimated'
61 |
62 | export default () => {
63 | // here we get the value from our global store using react hooks
64 | const { getValue } = useSharedAnimation();
65 | const coolValue = getValue('myCoolAnimatedValue');
66 |
67 | return
68 | }
69 | ```
70 |
71 | You can also use the `connectSharedAnimation` HOC or the `` component if you don't want to use the `useSharedAnimation` hook.
72 |
73 | 🎉 **All set. Your app is now ready to share animated values across components.**
74 |
75 |
76 | Below I'll expand on all the ways that you're able to **1) initialize animated values** and **2) access animated values**.
77 |
78 | ## Examples
79 |
80 | **To see full examples, go to the [/examples]() folder.** You can also see the Expo snack of examples [here]().
81 |
82 |
83 | ## Installation
84 |
85 | To install, open your react native repository in the terminal and run this command:
86 |
87 | ```
88 | npm i react-native-shared-animation
89 | ```
90 |
91 | You could use yarn if you prefer that:
92 |
93 | ```
94 | yarn react-native-shared-animation
95 | ```
96 |
97 | **Recommended:** If you want to use [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated) for animations, run this afterwards:
98 |
99 | ```
100 | npm i react-native-reanimated react-native-gesture-handler
101 | ```
102 |
103 | ### This works with...
104 | ✅ `react-native-reanimated`
105 |
106 | ✅ `Animated` from `react-native` if you prefer that.
107 |
108 | ✅ `Expo`
109 |
110 | ✅ `Typescript`
111 |
112 | ✅ `react-native-gesture-handler`
113 |
114 | ## 1) Initializing shared animated values
115 | You have two options for initializing shared animation values: global initialization or on-the-fly initializiation in components.
116 |
117 | ### i) [Recommended] Initialize global animated values
118 |
119 | Simply pass an `animatedValues` object as a prop to the `` component. This will act as the initial set of animated values.
120 |
121 | You can initialize as many animated values as you'd like.
122 |
123 | **App.js**
124 |
125 | ```javascript
126 | import React from 'react'
127 | import { SharedAnimationProvider } from 'react-native-shared-animation'
128 | import Animated from 'react-native-reanimated'
129 | import App from './src/App' // path to your root component
130 |
131 | export default () => {
132 | const mainScrollValue = new Animated.Value(0)
133 | const animatedValues = { mainScrollValue }
134 |
135 |
136 |
137 |
138 | }
139 |
140 | ```
141 |
142 | **🐻 That's it! Your app now has globally-accessible animated values.**
143 |
144 | In this case, `mainScrollValue` can be accessed by **any** component.
145 |
146 | If you come from a redux background, you can think of this like setting the initial store value.
147 |
148 | #### Why this is the better option:
149 |
150 | From a style perspective, it is useful to know what values will be accessible across your app upon initialization. And when it comes to performance, this is less prone to bugs, since you'll never try to access a value that hasn't been initialized.
151 |
152 | That said, you also have the `newValue` method to your disposal, as described in the next option.
153 |
154 | ### **ii) [Careful] Initialize animated values on the fly in components**
155 |
156 | You can also initialize animated values directly in components. The thing is, this option is more prone to bugs, since you might try to access an animated value before it's been initialized.
157 |
158 | Overall, I'd suggest only using this one on a case-by-case basis.
159 |
160 | It can be achieved with the `newValue(name, value)` function, documented [below]().
161 |
162 |
163 | ## 2) Accessing animated values
164 |
165 | Here's the fun part.
166 |
167 |
168 | ---
169 |
170 | ### There are 3 ways to access animated values
171 |
172 | These are the 3 options you have:
173 |
174 | 1) **`useSharedAnimation` hook:** The `useSharedAnimation` hook is super simple (and is my favorite to use). Only works in function components. See react hooks to learn more.
175 |
176 | 2) **`connectSharedAnimation` HOC:** You can use the `connectSharedAnimation` higher-order component. Useful for class components and function components. Good for taking animation logic out of a component, too.
177 |
178 | 3) **`SharedAnimation` component:** You can also wrap any component with `` to connect it to the global animation state.
179 |
180 | ---
181 |
182 | ### Option 1: `useSharedAnimation`
183 |
184 | Call `useSharedAnimation` in the root of a function component.
185 |
186 | **Example:**
187 |
188 | ```javascript
189 | ...
190 | import { useSharedAnimation } from 'react-native-shared-animation'
191 |
192 | export default () => {
193 | const { getValue, newValue, animatedValues } = useSharedAnimation();
194 | const scroll = getValue('scroll')
195 | // same as...
196 | const { scroll } = animatedValues;
197 |
198 | return
199 | }
200 |
201 | ```
202 |
203 | So simple!
204 |
205 | 🤑🤑
206 |
207 | **If you're using a version of react / react-native that supports hooks, you can happily stop reading the docs here and just use this.**
208 |
209 | --
210 |
211 | ### Option 2: `connectSharedAnimation(mapValuesToProps)(Component)`
212 |
213 | You can also use the `connectSharedAnimation` higher-order component to pass animated values as props.
214 |
215 | This option gives you some customization options beyond the `useSharedAnimation`, such as taking global animated code out of your actual component.
216 |
217 | **This HOC passes `newValue`, `getValue`, and any animated values you choose as props to your `Component`.**
218 |
219 | **Example:**
220 |
221 | ```javascript
222 | ...
223 | import { connectSharedAnimation } from 'react-native-shared-animation'
224 |
225 | const ConnectedComponent = ({ getValue, newValue, scroll }) => {
226 | return
227 | }
228 |
229 | // determine which values you want to pass to this component
230 | const mapValuesToProps = animatedValues => ({
231 | scroll: animatedValues.scroll
232 | })
233 |
234 | // could also have done this:
235 | // const mapValuesToProps = 'scroll'
236 |
237 | // ...or this:
238 | // const mapValuesToProps = ['scroll', 'someOtherValue']
239 |
240 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
241 |
242 | ```
243 |
244 | #### `mapValuesToProps` (required)
245 |
246 | This is the first and only argument for `connectSharedAnimation`. It determines which animated values will be passed to the component as direct props.
247 |
248 | This value can be either a `string`, `array of strings`, a `function`, or `null`.
249 |
250 | **mapValuesToProps as a string**
251 |
252 | The string should correspond to an existing global animated value.
253 |
254 | ```javascript
255 | ...
256 | import { connectSharedAnimation } from 'react-native-shared-animation'
257 |
258 | const ConnectedComponent = ({ getValue, newValue, scroll }) => {
259 | return
260 | }
261 |
262 | const mapValuesToProps = 'scroll'
263 |
264 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
265 |
266 | ```
267 |
268 |
269 | **mapValuesToProps as an array of strings**
270 |
271 | Enter the names of multiple global animated values you want passed as direct props.
272 |
273 | ```javascript
274 | ...
275 | import { connectSharedAnimation } from 'react-native-shared-animation'
276 |
277 | const ConnectedComponent = ({ getValue, newValue, scroll, someOtherValue }) => {
278 | return
279 | }
280 |
281 | // const mapValuesToProps = ['scroll', 'someOtherValue']
282 |
283 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
284 |
285 | ```
286 |
287 |
288 |
289 | **mapValuesToProps a function**
290 |
291 | A function that takes in `animatedValues` as its first argument and returns an object that references these values. This can be useful if you want to abstract some animation logic out of your component.
292 |
293 | ```javascript
294 | ...
295 | import { connectSharedAnimation } from 'react-native-shared-animation'
296 |
297 | const ConnectedComponent = ({ getValue, newValue, scroll, opacity }) => {
298 | return
299 | }
300 |
301 | const mapValuesToProps = animatedValues => {
302 | const { scroll } = animatedValues;
303 | const opacity = interpolate(scroll, {
304 | inputRange: [0, 400],
305 | outputRange: [1, 0]
306 | })
307 | return {
308 | scroll,
309 | opacity
310 | }
311 | }
312 |
313 | export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
314 |
315 | ```
316 |
317 | *The function works the same as redux's `mapStateToProps`.*
318 |
319 | **mapStateToValues as null**
320 |
321 | You can pass `null` to it if you don't want any animated values explicitly passed to the component. If you pass `null`, you will still have access to `getValue` and `newValue`.
322 |
323 | ### Option 3: `SharedAnimation` Component
324 |
325 | A basic component that uses the render props method. (Similar in principle to the `[Query]()` component from `react-apollo`.)
326 |
327 | ```javascript
328 | ...
329 | import { SharedAnimation } from 'react-native-shared-animation'
330 |
331 | export default () => {
332 | return (
333 |
334 | {({ getValue, newValue }) => {
335 | const scroll = getValue('scroll')
336 | return (
337 |
338 | )
339 | })}
340 |
341 | )
342 | }
343 |
344 | ```
345 | ---
346 |
347 | ### **`getValue(name)`**
348 |
349 | A function that takes in the name of a global animated value and returns the animated value itself.
350 |
351 | *This is the most important function that you'll find yourself using all the time.*
352 |
353 | **Example**
354 |
355 | ```javascript
356 | const SomeComponent = () => {
357 | const { getValue } = useSharedAnimation()
358 |
359 | const opacity = getValue('opacity')
360 |
361 | return (
362 |
363 | )
364 | }
365 |
366 | ```
367 |
368 |
369 | ### **`animatedValues`**
370 |
371 | A dictionary containing the current global state of animated values. You can use this to access the global store directly, but I recommend using `getValue` instead, since it has some added convenience checks.
372 |
373 |
374 | **Example**
375 |
376 | ```javascript
377 | const SomeComponent = () => {
378 | const { animatedValues } = useSharedAnimation()
379 |
380 | const { opacity } = animatedValues
381 |
382 | return (
383 |
384 | )
385 | }
386 |
387 | ```
388 |
389 | ### **`newValue(name, value)`**
390 |
391 | A function that creates a new global animated value. Takes a name as the first argument, and an animated value (or node) as the second argument.
392 |
393 | **Returns:** the animated value it just created
394 |
395 | **Example**
396 |
397 | ```javascript
398 | const SomeComponent = () => {
399 | const { newValue } = useSharedAnimation();
400 |
401 | const opacity = newValue('opacity', new Animated.Value(1))
402 |
403 | return (
404 |
405 | )
406 | }
407 |
408 | ```
409 |
410 |
411 | ---
412 |
413 | ## Documentation Recap
414 |
415 | ### ``
416 |
417 | | **Prop** | Required | Type | Example |
418 | |---|---|---|---|
419 | | `animatedValues` | no (but recommended) | `dictionary` | `{ scroll: new Animated.Value(0) }` |
420 | | `children` | yes | `React.Node` | Your app JSX should be a child component of this provider. |
421 |
422 |
423 |
424 |
425 |
426 | ## Illustrative example
427 |
428 | Sharing animated values across your entire app is as easy as this:
429 |
430 | ```javascript
431 | import React from 'react'
432 | import Animated from 'react-native-reanimated'
433 | import { SharedAnimationProvider, useSharedAnimation} from 'react-native-shared-animation'
434 |
435 | export default function App() {
436 | const animatedValues = { scroll: new Animated.Value(0) }
437 | return (
438 |
439 |
440 |
441 |
442 | )
443 | }
444 |
445 | const OtherComponentThatAccessesScroll = () => {
446 | const { getValue } = useSharedAnimation()
447 | const scroll = getValue('scroll')
448 |
449 | return
450 | }
451 |
452 | const ComponentWithScrollView = () => {
453 | const { getValue } = useSharedAnimation()
454 | const scroll = getValue('scroll')
455 | const onScroll = Animated.event([
456 | {
457 | nativeEvent: {
458 | contentOffset: {
459 | y: scroll
460 | }
461 | }
462 | }
463 | ])
464 |
465 | return (
466 |
467 | )
468 |
469 | }
470 |
471 | ```
472 |
473 | Yup, that's it. No prop drilling at all.
474 |
475 |
476 |
477 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import Animated from 'react-native-reanimated'
3 | import { Animated as NativeAnimated } from 'react-native'
4 |
5 | type ReanimatedType =
6 | | Animated.Value
7 | | Animated.Node
8 | | Animated.Clock
9 | | Animated.SpringConfig
10 | | Animated.SpringState
11 | | Animated.DecayState
12 | | Animated.TimingState
13 | | Animated.Adaptable
14 | | Animated.BackwardCompatibleWrapper
15 | | Animated.EasingFunction
16 |
17 | type NativeType =
18 | | NativeAnimated.AnimatedValue
19 | | NativeAnimated.AnimatedValueXY
20 | | NativeAnimated.AnimatedMultiplication
21 | | NativeAnimated.AnimatedInterpolation
22 |
23 | export type AnimatedType = ReanimatedType | NativeType
24 |
--------------------------------------------------------------------------------
/src/types/index.tsx:
--------------------------------------------------------------------------------
1 | import Animated from 'react-native-reanimated';
2 | import { Animated as NativeAnimated } from 'react-native';
3 |
4 | type ReanimatedType =
5 | | Animated.Value
6 | | Animated.Node
7 | | Animated.Clock
8 | | Animated.SpringConfig
9 | | Animated.SpringState
10 | | Animated.DecayState
11 | | Animated.TimingState
12 | | Animated.Adaptable
13 | | Animated.BackwardCompatibleWrapper
14 | | Animated.EasingFunction;
15 |
16 | type NativeType =
17 | | NativeAnimated.AnimatedValue
18 | | NativeAnimated.AnimatedValueXY
19 | | NativeAnimated.AnimatedMultiplication
20 | | NativeAnimated.AnimatedInterpolation;
21 |
22 | export type AnimatedType = ReanimatedType | NativeType;
23 |
--------------------------------------------------------------------------------
/src/useSharedAnimation/index.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 | import AnimationContext, { AnimationContextType } from '../Context/SharedAnimationContext'
3 |
4 | const useSharedAnimation = () => {
5 | const { getValue, newValue, animatedValues }: AnimationContextType = useContext(
6 | AnimationContext,
7 | )
8 |
9 | const obj: AnimationContextType = { getValue, newValue, animatedValues }
10 | return obj
11 | }
12 | export default useSharedAnimation
13 |
--------------------------------------------------------------------------------
/tests/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export class Cows {
4 | private ox = 10
5 | getX = () => this.ox
6 | setX = (newVal: number) => {
7 | this.ox = newVal
8 | }
9 | }
10 |
11 | // export const Tester = () => >>>>>> a8693f4bc5eaa0b6b9b3fb8ab99f8dab9379818c
9 | "jsx": "react-native",
10 | "strict": true,
11 | "noImplicitAny": true,
12 | "strictNullChecks": true,
13 | "strictFunctionTypes": true,
14 | "strictPropertyInitialization": true,
15 | "noImplicitThis": true,
16 | "alwaysStrict": true,
17 | "noUnusedLocals": true,
18 | "noUnusedParameters": true,
19 | "noImplicitReturns": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "forceConsistentCasingInFileNames": true,
22 | <<<<<<< HEAD
23 | "moduleResolution": "node",
24 | "allowSyntheticDefaultImports": true,
25 | "types": ["react", "react-native"]
26 | },
27 | "include": ["src/index.ts"]
28 | =======
29 | "types": ["react", "react-native"],
30 | "outDir": "libby",
31 | "allowSyntheticDefaultImports": true,
32 | "moduleResolution": "node",
33 | "noEmit": true,
34 | "skipLibCheck": true
35 | },
36 | "include": ["src"]
37 | >>>>>>> a8693f4bc5eaa0b6b9b3fb8ab99f8dab9379818c
38 | }
39 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
3 | "rules": {}
4 | }
5 |
--------------------------------------------------------------------------------
/yarn-error.log:
--------------------------------------------------------------------------------
1 | Arguments:
2 | /usr/local/bin/node /usr/local/Cellar/yarn/0.27.5_1/libexec/bin/yarn.js publish --access public
3 |
4 | PATH:
5 | /Users/Fernando/Library/Android/sdk/platform-tools:/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Users/Fernando/Library/Android/sdk/platform-tools:/Library/Frameworks/Python.framework/Versions/3.6/bin
6 |
7 | Yarn version:
8 | 0.27.5
9 |
10 | Node version:
11 | 8.12.0
12 |
13 | Platform:
14 | darwin x64
15 |
16 | npm manifest:
17 | {
18 | "name": "@nandorojo/shared-animations",
19 | "description": "A nimble global animtion-state management tool for react-native's `Animated` values. Think of it like a simple redux for react-native animation values.",
20 | "version": "1.0.1",
21 | "main": "lib/index.js",
22 | "scripts": {
23 | "test": "echo \"Error: no test specified\" && exit 1",
24 | "build:tsc": "tsc -p .",
25 | "check-types": "tsc",
26 | "build": "npx babel src --extensions '.tsx,.ts' --out-dir lib"
27 | },
28 | "author": "Fernando Rojo",
29 | "license": "MIT",
30 | "peerDependencies": {
31 | "react": ">=16.0.0",
32 | "react-native": "*",
33 | "react-native-reaniamted": "*"
34 | },
35 | "devDependencies": {
36 | "@babel/cli": "^7.5.5",
37 | "@babel/core": "^7.5.5",
38 | "@babel/plugin-proposal-class-properties": "^7.5.5",
39 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
40 | "@babel/preset-typescript": "^7.3.3",
41 | "@types/enzyme": "^3.10.3",
42 | "@types/enzyme-adapter-react-16": "^1.0.5",
43 | "@types/jest": "^24.0.17",
44 | "@types/react": "^16.9.1",
45 | "@types/react-native": "^0.57.65",
46 | "@types/webpack-env": "^1.14.0",
47 | "babel-preset-expo": "^6.0.0",
48 | "expo": "^34.0.1",
49 | "react": "16.8.3",
50 | "react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",
51 | "react-native-gesture-handler": "^1.3.0",
52 | "react-native-reanimated": "^1.1.0",
53 | "react-native-redash": "^7.5.0",
54 | "source-map-loader": "^0.2.4",
55 | "ts-jest": "^24.0.2",
56 | "tslint": "^5.18.0",
57 | "tslint-react": "^4.0.0",
58 | "typescript": "^3.5.3"
59 | },
60 | "dependencies": {
61 | "@babel/preset-react": "^7.0.0",
62 | "@types/react-dom": "^16.8.5",
63 | "@types/react-router-dom": "^4.3.4",
64 | "babel-loader": "^7.1.5",
65 | "tslint-config-prettier": "^1.18.0",
66 | "webpack": "^4.39.1"
67 | }
68 | }
69 |
70 | yarn manifest:
71 | No manifest
72 |
73 | Lockfile:
74 | No lockfile
75 |
76 | Trace:
77 | Error: https://registry.yarnpkg.com/@nandorojo%2fshared-animations: You cannot publish over the previously published versions: 1.0.1.
78 | at Request.params.callback [as _callback] (/usr/local/Cellar/yarn/0.27.5_1/libexec/lib/yarn-cli.js:58200:18)
79 | at Request.self.callback (/usr/local/Cellar/yarn/0.27.5_1/libexec/lib/yarn-cli.js:114242:22)
80 | at emitTwo (events.js:126:13)
81 | at Request.emit (events.js:214:7)
82 | at Request. (/usr/local/Cellar/yarn/0.27.5_1/libexec/lib/yarn-cli.js:115225:10)
83 | at emitOne (events.js:116:13)
84 | at Request.emit (events.js:211:7)
85 | at Gunzip. (/usr/local/Cellar/yarn/0.27.5_1/libexec/lib/yarn-cli.js:115145:12)
86 | at Object.onceWrapper (events.js:313:30)
87 | at emitNone (events.js:111:20)
88 |
--------------------------------------------------------------------------------