├── .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 | --------------------------------------------------------------------------------