├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── .prettierrc
├── Example
├── .gitignore
├── .vscode
│ ├── launch.json
│ └── settings.json
├── .watchmanconfig
├── App.js
├── app.json
├── assets
│ ├── icon.png
│ └── splash.png
├── babel.config.js
├── components
│ ├── Button
│ │ ├── Button.js
│ │ └── styles.js
│ ├── ListItem
│ │ ├── ListItem.js
│ │ └── styles.js
│ ├── Separator
│ │ ├── Separator.js
│ │ └── styles.js
│ └── index.js
├── navigation
│ ├── AppNavigator.js
│ └── screens.js
├── package.json
├── rn-cli.config.js
├── screens
│ ├── DelegateScroll
│ │ ├── DelegateScroll.js
│ │ ├── components
│ │ │ ├── Header
│ │ │ │ ├── Header.js
│ │ │ │ ├── constants.js
│ │ │ │ └── styles.js
│ │ │ ├── Placeholder
│ │ │ │ ├── Placeholder.js
│ │ │ │ └── styles.js
│ │ │ ├── SearchBox
│ │ │ │ ├── SearchBox.js
│ │ │ │ └── styles.js
│ │ │ └── index.js
│ │ └── styles.js
│ ├── Examples
│ │ └── Examples.js
│ ├── InterpolationTransition
│ │ └── InterpolationTransition.js
│ ├── Keyframes
│ │ └── Keyframes.js
│ └── TransitionBase
│ │ └── TransitionBase.js
├── services
│ ├── NavigationService.js
│ └── index.js
├── styles
│ ├── colors.js
│ └── index.js
└── utils
│ ├── createKeys.js
│ └── index.js
├── LICENSE
├── README.md
├── lib
├── components
│ ├── DelegateAnimation.js
│ ├── InterpolationTransitionAnimation.js
│ ├── KeyframesAnimation.js
│ ├── LoopAnimation.js
│ ├── Reanimatable.js
│ ├── ScrollView.js
│ ├── TransitionAnimation.js
│ ├── index.js
│ └── propTypes.js
├── core
│ ├── animations.js
│ ├── constants.js
│ ├── createConfig.js
│ ├── createDelegate.js
│ ├── createDelegateAnimation.js
│ ├── createInterpolationTransitionAnimation.js
│ ├── createKeyframesAnimation.js
│ └── createTransitionAnimation.js
├── index.js
└── utils
│ ├── createKeys.js
│ ├── index.js
│ └── objectPath.js
└── package.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["module:metro-react-native-babel-preset"],
3 | "plugins": [
4 | "@babel/plugin-proposal-export-namespace-from"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "globals": { "fetch": false },
5 | "env": {
6 | "jest": true
7 | },
8 | "plugins": ["react", "react-native"],
9 | "settings": {
10 | "import/resolver": {
11 | "node": {
12 | "extensions": [".js", ".android.js", ".ios.js"]
13 | }
14 | }
15 | },
16 | "rules": {
17 | "jsx-a11y/href-no-hash": "off",
18 | "max-len": [2, 100, 2],
19 | "import/no-extraneous-dependencies": [
20 | "error",
21 | {
22 | "devDependencies": true,
23 | "optionalDependencies": false,
24 | "peerDependencies": false
25 | }
26 | ],
27 | "function-paren-newline": 0,
28 | "import/prefer-default-export": 0,
29 | "no-trailing-spaces": ["error", { "skipBlankLines": true }],
30 | "no-underscore-dangle": 0,
31 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
32 | "react/forbid-prop-types": 0,
33 | "react/require-default-props": 0,
34 | "react-native/no-unused-styles": 2,
35 | "react-native/split-platform-components": 2,
36 | "react-native/no-inline-styles": 2,
37 | "react-native/no-color-literals": 2,
38 | "class-methods-use-this": "off",
39 | "arrow-parens": "off",
40 | "import/no-unresolved": ["error", { "ignore": ["^react$", "^react-native$", "^react-native-reanimated$"] }],
41 | "import/extensions": ["error", "never", {"ignorePackages": true, "js": "never"} ],
42 | "react/no-multi-comp": 0
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 | **/.DS_Store
5 |
6 | node_modules/**/*
7 | dest/
8 | npm-debug.*
9 | package-lock.json
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | Example/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": false,
3 | "printWidth": 70,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "trailingComma": "all",
7 | "jsxBracketSameLine": false,
8 | "parser": "babylon",
9 | "noSemi": false,
10 | "rcVerbose": true,
11 | "arrowParens": "always"
12 | }
--------------------------------------------------------------------------------
/Example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | package-lock.json
5 | *.jks
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | .vscode/.react
--------------------------------------------------------------------------------
/Example/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Debug Android",
9 | "program": "${workspaceRoot}/.vscode/launchReactNative.js",
10 | "type": "reactnative",
11 | "request": "launch",
12 | "platform": "android",
13 | "sourceMaps": true,
14 | "outDir": "${workspaceRoot}/.vscode/.react"
15 | },
16 | {
17 | "name": "Debug iOS",
18 | "program": "${workspaceRoot}/.vscode/launchReactNative.js",
19 | "type": "reactnative",
20 | "request": "launch",
21 | "platform": "ios",
22 | "sourceMaps": true,
23 | "outDir": "${workspaceRoot}/.vscode/.react"
24 | },
25 | {
26 | "name": "Attach to packager",
27 | "program": "${workspaceRoot}/.vscode/launchReactNative.js",
28 | "type": "reactnative",
29 | "request": "attach",
30 | "platform": "exponent",
31 | "sourceMaps": true,
32 | "outDir": "${workspaceRoot}/.vscode/.react"
33 | },
34 | {
35 | "name": "Debug in Exponent",
36 | "program": "${workspaceRoot}/.vscode/launchReactNative.js",
37 | "type": "reactnative",
38 | "request": "launch",
39 | "platform": "exponent",
40 | "sourceMaps": true,
41 | "outDir": "${workspaceRoot}/.vscode/.react"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/Example/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "java.configuration.updateBuildConfiguration": "disabled"
3 | }
--------------------------------------------------------------------------------
/Example/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/Example/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StatusBar } from 'react-native';
3 | import AppNavigator from './navigation/AppNavigator';
4 | import { NavigationService } from './services';
5 | // import screens from './navigation/screens';
6 |
7 | class App extends React.PureComponent {
8 | componentDidMount() {
9 | // use it to navigate to proper screen on mount
10 | // useful for developing
11 | // NavigationService.navigate(screens.DelegateScroll);
12 | }
13 |
14 | render() {
15 | return (
16 |
17 |
22 | NavigationService.init(ref)} />
23 |
24 | );
25 | }
26 | }
27 |
28 | export default App;
29 |
--------------------------------------------------------------------------------
/Example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "Example",
4 | "slug": "Example",
5 | "privacy": "public",
6 | "sdkVersion": "32.0.0",
7 | "platforms": [
8 | "ios",
9 | "android"
10 | ],
11 | "version": "1.0.0",
12 | "orientation": "portrait",
13 | "icon": "./assets/icon.png",
14 | "splash": {
15 | "image": "./assets/splash.png",
16 | "resizeMode": "contain",
17 | "backgroundColor": "#ffffff"
18 | },
19 | "updates": {
20 | "fallbackToCacheTimeout": 0
21 | },
22 | "assetBundlePatterns": [
23 | "**/*"
24 | ],
25 | "ios": {
26 | "supportsTablet": true
27 | },
28 | "androidStatusBar": {
29 | "barStyle": "dark-content"
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/Example/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/terrysahaidak/react-native-reanimatable/f4e26e27a785c6c6d38024ff71c55be527a8794b/Example/assets/icon.png
--------------------------------------------------------------------------------
/Example/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/terrysahaidak/react-native-reanimatable/f4e26e27a785c6c6d38024ff71c55be527a8794b/Example/assets/splash.png
--------------------------------------------------------------------------------
/Example/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/Example/components/Button/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import T from 'prop-types';
3 | import { View, TouchableOpacity, Text } from 'react-native';
4 | import s from './styles';
5 |
6 | const Button = ({ onPress, text }) => (
7 |
8 |
9 | {text}
10 |
11 |
12 | );
13 |
14 | Button.propTypes = {
15 | text: T.string,
16 | onPress: T.func,
17 | };
18 |
19 | export default Button;
20 |
--------------------------------------------------------------------------------
/Example/components/Button/styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { colors } from '../../styles';
3 |
4 | const styles = StyleSheet.create({
5 | container: {
6 | padding: 8,
7 | backgroundColor: colors.green,
8 | borderRadius: 6,
9 | margin: 8,
10 | },
11 | text: {
12 | color: colors.white,
13 | },
14 | });
15 |
16 | export default styles;
17 |
--------------------------------------------------------------------------------
/Example/components/ListItem/ListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import T from 'prop-types';
3 | import { View, TouchableWithoutFeedback, Text } from 'react-native';
4 | import { Feather } from '@expo/vector-icons';
5 | import { colors } from '../../styles';
6 | import s from './styles';
7 |
8 | const ListItem = ({ onPress, title }) => (
9 |
10 |
11 | {title}
12 |
13 |
18 |
19 |
20 | );
21 |
22 | ListItem.propTypes = {
23 | onPress: T.func,
24 | title: T.string,
25 | };
26 |
27 | export default ListItem;
28 |
--------------------------------------------------------------------------------
/Example/components/ListItem/styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { colors } from '../../styles';
3 |
4 | const styles = StyleSheet.create({
5 | container: {
6 | height: 52,
7 | flex: 1,
8 | flexDirection: 'row',
9 | alignItems: 'center',
10 | justifyContent: 'space-between',
11 | paddingHorizontal: 16,
12 | },
13 | title: {
14 | fontSize: 17,
15 | color: colors.text,
16 | },
17 | });
18 |
19 | export default styles;
20 |
--------------------------------------------------------------------------------
/Example/components/Separator/Separator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import T from 'prop-types';
3 | import { View } from 'react-native';
4 | import s from './styles';
5 |
6 | const Separator = () => (
7 |
8 | );
9 |
10 | Separator.propTypes = {
11 |
12 | };
13 |
14 | export default Separator;
15 |
--------------------------------------------------------------------------------
/Example/components/Separator/styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { colors } from '../../styles';
3 |
4 | const styles = StyleSheet.create({
5 | container: {
6 | height: StyleSheet.hairlineWidth,
7 | flex: 1,
8 | backgroundColor: colors.border,
9 | },
10 | });
11 |
12 | export default styles;
13 |
--------------------------------------------------------------------------------
/Example/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Separator } from './Separator/Separator';
2 | export { default as ListItem } from './ListItem/ListItem';
3 | export { default as Button } from './Button/Button';
4 |
--------------------------------------------------------------------------------
/Example/navigation/AppNavigator.js:
--------------------------------------------------------------------------------
1 | import {
2 | createStackNavigator,
3 | createAppContainer,
4 | } from 'react-navigation';
5 | import screens from './screens';
6 | import Examples from '../screens/Examples/Examples';
7 | import TransitionBase from '../screens/TransitionBase/TransitionBase';
8 | import InterpolationTransition from '../screens/InterpolationTransition/InterpolationTransition';
9 | import Keyframes from '../screens/Keyframes/Keyframes';
10 | import DelegateScroll from '../screens/DelegateScroll/DelegateScroll';
11 |
12 | const AppNavigator = createStackNavigator(
13 | {
14 | [screens.Examples]: Examples,
15 | [screens.TransitionBase]: TransitionBase,
16 | [screens.InterpolationTransition]: InterpolationTransition,
17 | [screens.Keyframes]: Keyframes,
18 | [screens.DelegateScroll]: DelegateScroll,
19 | },
20 | {
21 | defaultNavigationOptions: ({ navigation }) => ({
22 | title: navigation.getParam('title'),
23 | }),
24 | },
25 | );
26 |
27 | export default createAppContainer(AppNavigator);
28 |
--------------------------------------------------------------------------------
/Example/navigation/screens.js:
--------------------------------------------------------------------------------
1 | import { createKeys } from '../utils';
2 |
3 | const screens = createKeys([
4 | 'Examples',
5 | 'TransitionBase',
6 | 'InterpolationTransition',
7 | 'Keyframes',
8 | 'DelegateScroll',
9 | ]);
10 |
11 | export default screens;
12 |
--------------------------------------------------------------------------------
/Example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "eject": "expo eject"
8 | },
9 | "dependencies": {
10 | "@expo/vector-icons": "^9.0.0",
11 | "expo": "^32.0.0",
12 | "prop-types": "^15.7.1",
13 | "react": "16.5.0",
14 | "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
15 | "react-native-reanimatable": "*",
16 | "react-native-reanimated": "^1.0.0-alpha.12",
17 | "react-navigation": "^3.2.1"
18 | },
19 | "devDependencies": {
20 | "babel-preset-expo": "^5.0.0"
21 | },
22 | "private": true
23 | }
24 |
--------------------------------------------------------------------------------
/Example/rn-cli.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | const path = require('path');
4 |
5 | const extraNodeModules = {
6 | 'react-native': path.resolve(
7 | __dirname,
8 | 'node_modules/react-native',
9 | ),
10 | react: path.resolve(__dirname, 'node_modules/react'),
11 | 'react-native-reanimated': path.resolve(
12 | __dirname,
13 | 'node_modules/react-native-reanimated',
14 | ),
15 | };
16 | const blacklistRegexes = [
17 | /react-native-reanimatable[/\\]node_modules[/\\]react-native[/\\].*/,
18 | ];
19 | const watchFolders = [path.resolve('../')];
20 |
21 | const metroVersion = require('metro/package.json').version;
22 |
23 | const metroVersionComponents = metroVersion.match(
24 | /^(\d+)\.(\d+)\.(\d+)/,
25 | );
26 | if (
27 | metroVersionComponents[1] === '0' &&
28 | parseInt(metroVersionComponents[2], 10) >= 43
29 | ) {
30 | module.exports = {
31 | resolver: {
32 | extraNodeModules,
33 | blacklistRE: require('metro-config/src/defaults/blacklist')(
34 | blacklistRegexes,
35 | ),
36 | },
37 | watchFolders,
38 | };
39 | } else {
40 | module.exports = {
41 | extraNodeModules,
42 | getBlacklistRE: () =>
43 | require('metro/src/blacklist')(blacklistRegexes),
44 | getProjectRoots: () =>
45 | [path.resolve(__dirname)].concat(watchFolders),
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/DelegateScroll.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import { ScrollView } from 'react-native-reanimatable';
4 | import s from './styles';
5 | import { Placeholder, Header } from './components';
6 | import {
7 | MAX_HEADER_HEIGHT,
8 | delegate,
9 | } from './components/Header/constants';
10 |
11 | const snapPoints = [
12 | { from: 0, to: 28, snap: 0 },
13 | { from: 28, to: 51, snap: 51 },
14 | ];
15 |
16 | const App = () => (
17 |
18 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 |
38 | App.navigationOptions = {
39 | header: null,
40 | };
41 |
42 | export default App;
43 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 | import Animated from 'react-native-reanimated';
4 | import {
5 | createAnimationConfig,
6 | Reanimatable,
7 | } from 'react-native-reanimatable';
8 | import s from './styles';
9 | import { MAX_HEADER_HEIGHT, delegate } from './constants';
10 | import { SearchBox } from '../';
11 |
12 | const animation = createAnimationConfig({
13 | animation: {
14 | delegate,
15 | },
16 | keyframes: {
17 | '-100': {
18 | fontSize: 36,
19 | height: MAX_HEADER_HEIGHT + 100,
20 | },
21 | 0: {
22 | fontSize: 34,
23 | height: MAX_HEADER_HEIGHT,
24 | },
25 | 88: {
26 | headingOpacity: 1,
27 | opacity: 0,
28 | },
29 | 90: {
30 | headingOpacity: 0,
31 | opacity: 1,
32 | },
33 | 103: {
34 | height: MAX_HEADER_HEIGHT - 103,
35 | },
36 | },
37 | });
38 |
39 | const Header = () => (
40 |
41 | {({
42 | height,
43 | fontSize,
44 | paddingBottom,
45 | opacity,
46 | headingOpacity,
47 | }) => (
48 |
49 |
50 |
56 | Delegation
57 |
58 |
59 |
60 |
61 |
62 |
63 | Delegation
64 |
65 |
66 |
67 | )}
68 |
69 | );
70 |
71 | export default Header;
72 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/components/Header/constants.js:
--------------------------------------------------------------------------------
1 | import { createDelegate } from 'react-native-reanimatable';
2 |
3 | export const MAX_HEADER_HEIGHT = 168;
4 | export const delegate = createDelegate('scroll', [
5 | 'contentOffset',
6 | 'y',
7 | ]);
8 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/components/Header/styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { colors } from '../../../../styles';
3 |
4 | const styles = StyleSheet.create({
5 | container: {
6 | flex: 1,
7 | borderBottomColor: colors.border,
8 | borderBottomWidth: StyleSheet.hairlineWidth,
9 | position: 'absolute',
10 | left: 0,
11 | right: 0,
12 | backgroundColor: colors.background,
13 | },
14 | bottomContainer: {
15 | paddingHorizontal: 16,
16 | position: 'absolute',
17 | bottom: 0,
18 | left: 0,
19 | right: 0,
20 | },
21 | topContainer: {
22 | paddingHorizontal: 16,
23 | position: 'absolute',
24 | alignItems: 'center',
25 | justifyContent: 'center',
26 | backgroundColor: colors.background,
27 | top: 20, // status bar height
28 | left: 0,
29 | right: 0,
30 | height: 44,
31 | },
32 | bigHeader: {
33 | color: colors.text,
34 | fontWeight: '700',
35 | marginBottom: 8,
36 | },
37 | smallHeader: {
38 | color: colors.text,
39 | fontWeight: '600',
40 | fontSize: 17,
41 | },
42 | searchContainer: {
43 | justifyContent: 'center',
44 | backgroundColor: colors.backgroundSecondary,
45 | flex: 1,
46 | borderRadius: 11,
47 | paddingLeft: 16,
48 | },
49 | searchText: {
50 | color: colors.textSecondary,
51 | fontSize: 17,
52 | },
53 | });
54 |
55 | export default styles;
56 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/components/Placeholder/Placeholder.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, Text } from 'react-native';
3 | import s from './styles';
4 |
5 | const Placeholder = () => (
6 |
7 | Placeholder
8 |
9 | );
10 |
11 | export default Placeholder;
12 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/components/Placeholder/styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | const styles = StyleSheet.create({
4 | container: {
5 | height: 200,
6 | },
7 | });
8 |
9 | export default styles;
10 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/components/SearchBox/SearchBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Animated from 'react-native-reanimated';
3 | import {
4 | createAnimationConfig,
5 | Reanimatable,
6 | } from 'react-native-reanimatable';
7 | import Ionicons from '@expo/vector-icons/Ionicons';
8 | import s from './styles';
9 | import { delegate } from '../Header/constants';
10 | import { colors } from '../../../../styles';
11 |
12 | const searchAnimation = createAnimationConfig({
13 | animation: {
14 | delegate,
15 | },
16 | keyframes: {
17 | 0: {
18 | height: 36,
19 | opacity: 1,
20 | paddingBottom: 15,
21 | },
22 | 8: {
23 | opacity: 0,
24 | },
25 | 36: {
26 | height: 0,
27 | paddingBottom: 15,
28 | },
29 | 51: {
30 | paddingBottom: 0,
31 | },
32 | },
33 | });
34 |
35 | const Search = () => (
36 |
37 | {({ height, opacity, paddingBottom }) => (
38 |
39 |
40 |
41 |
46 |
47 |
48 |
49 | Search
50 |
51 |
52 |
53 | )}
54 |
55 | );
56 |
57 | export default Search;
58 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/components/SearchBox/styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { colors } from '../../../../styles';
3 |
4 | const styles = StyleSheet.create({
5 | container: {
6 | flexDirection: 'row',
7 | alignItems: 'center',
8 | backgroundColor: colors.backgroundSecondary,
9 | flex: 1,
10 | borderRadius: 11,
11 | paddingLeft: 8,
12 | },
13 | text: {
14 | color: colors.textSecondary,
15 | fontSize: 17,
16 | marginLeft: 7,
17 | },
18 | icon: {
19 | top: 1.5,
20 | },
21 | });
22 |
23 | export default styles;
24 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Placeholder } from './Placeholder/Placeholder';
2 | export { default as Header } from './Header/Header';
3 | export { default as SearchBox } from './SearchBox/SearchBox';
4 |
--------------------------------------------------------------------------------
/Example/screens/DelegateScroll/styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { colors } from '../../styles';
3 |
4 | export default StyleSheet.create({
5 | container: {
6 | flex: 1,
7 | backgroundColor: colors.white,
8 | },
9 | placeholder: {},
10 | header: {
11 | alignItems: 'center',
12 | justifyContent: 'center',
13 | backgroundColor: colors.green,
14 | flex: 1,
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/Example/screens/Examples/Examples.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FlatList } from 'react-native';
3 | import screens from '../../navigation/screens';
4 | import { ListItem, Separator } from '../../components';
5 | import { NavigationService } from '../../services';
6 |
7 | const list = [
8 | { title: 'Base transition', screen: screens.TransitionBase },
9 | {
10 | title: 'Interpolation transition',
11 | screen: screens.InterpolationTransition,
12 | },
13 | {
14 | title: 'Lazy Interpolation transition',
15 | screen: screens.InterpolationTransition,
16 | params: {
17 | lazy: true,
18 | },
19 | },
20 | { title: 'Keyframes (interpolation)', screen: screens.Keyframes },
21 | { title: 'Delegate (Scroll)', screen: screens.DelegateScroll },
22 | ];
23 |
24 | const Examples = () => {
25 | const renderItem = ({ item }) => (
26 |
29 | NavigationService.navigate(item.screen, {
30 | title: item.title,
31 | params: item.params,
32 | })
33 | }
34 | />
35 | );
36 |
37 | return (
38 | item.title}
44 | />
45 | );
46 | };
47 |
48 | Examples.navigationOptions = {
49 | title: 'Examples',
50 | };
51 |
52 | export default Examples;
53 |
--------------------------------------------------------------------------------
/Example/screens/InterpolationTransition/InterpolationTransition.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, View, Dimensions, FlatList } from 'react-native';
3 | import {
4 | Reanimatable,
5 | createAnimationConfig,
6 | } from 'react-native-reanimatable';
7 | import Animated from 'react-native-reanimated';
8 | import { Button } from '../../components';
9 |
10 | const { width: windowWidth } = Dimensions.get('window');
11 |
12 | const colors = {
13 | red: '#e74c3c',
14 | white: 'white',
15 | green: '#2ecc71',
16 | };
17 |
18 | const config = {
19 | animation: {
20 | type: 'timing',
21 | duration: 300,
22 | interpolation: true,
23 | lazy: false,
24 | },
25 | values: {
26 | width: { from: 50, to: 200 },
27 | height: { from: 50, to: 200 },
28 | left: { from: 20, to: windowWidth - 20 - 200 },
29 | borderRadius: { from: 0, to: 100 },
30 | },
31 | };
32 |
33 | const defaultConfig = createAnimationConfig(config);
34 |
35 | config.animation.lazy = true;
36 | const lazyConfig = createAnimationConfig(config);
37 |
38 | const s = StyleSheet.create({
39 | scroll: {
40 | paddingVertical: 20,
41 | },
42 | animationContainer: {
43 | height: 250,
44 | justifyContent: 'center',
45 | },
46 | animatableView: {
47 | backgroundColor: colors.red,
48 | },
49 | row: {
50 | flexDirection: 'row',
51 | alignSelf: 'center',
52 | },
53 | });
54 |
55 | class Example extends React.PureComponent {
56 | state = {
57 | value: true,
58 | };
59 | initialValue = this.state.value;
60 |
61 | toggleAnimation() {
62 | this.setState((state) => ({ value: !state.value }));
63 | }
64 |
65 | render() {
66 | return (
67 |
68 |
73 | {({ translateX, ...animatedValues }) => (
74 |
77 | )}
78 |
79 |
80 |
81 |
86 |
87 | );
88 | }
89 | }
90 |
91 | export default function App(props) {
92 | // performance test
93 | const range = Array.from(new Array(10));
94 |
95 | const lazy = props.navigation.getParam('lazy');
96 |
97 | return (
98 | }
102 | keyExtractor={(_, i) => i.toString()}
103 | />
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/Example/screens/Keyframes/Keyframes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | StyleSheet,
4 | View,
5 | TouchableOpacity,
6 | Text,
7 | Dimensions,
8 | ScrollView,
9 | } from 'react-native';
10 | import {
11 | Reanimatable,
12 | createAnimationConfig,
13 | } from 'react-native-reanimatable';
14 | import Animated from 'react-native-reanimated';
15 |
16 | const { width: windowWidth } = Dimensions.get('window');
17 |
18 | const colors = {
19 | red: '#e74c3c',
20 | white: 'white',
21 | green: '#2ecc71',
22 | };
23 |
24 | const size = 100;
25 |
26 | const config = createAnimationConfig({
27 | animation: {
28 | duration: 2000,
29 | },
30 | keyframes: {
31 | 0: {
32 | opacity: 0,
33 | scale: 1,
34 | left: 0,
35 | top: 0,
36 | },
37 | 25: {
38 | opacity: 0.7,
39 | scale: 0.3,
40 | left: (windowWidth - size) / 4,
41 | top: 100,
42 | },
43 | 50: {
44 | opacity: 1,
45 | scale: 1,
46 | left: (windowWidth - size) / 2,
47 | top: 0,
48 | },
49 | 75: {
50 | opacity: 0.7,
51 | scale: 0.3,
52 | left: (windowWidth - size) / 1.5,
53 | top: 100,
54 | },
55 | 100: {
56 | opacity: 0,
57 | scale: 1,
58 | left: windowWidth - size,
59 | top: 0,
60 | },
61 | },
62 | });
63 |
64 | const s = StyleSheet.create({
65 | container: {
66 | height: 300,
67 | backgroundColor: colors.white,
68 | paddingTop: 50,
69 | },
70 | animationContainer: {
71 | marginBottom: 100,
72 | },
73 | animatableView: {
74 | height: size,
75 | backgroundColor: colors.red,
76 | width: size,
77 | },
78 | button: {
79 | padding: 8,
80 | backgroundColor: colors.green,
81 | borderRadius: 6,
82 | },
83 | buttonText: {
84 | color: colors.white,
85 | },
86 | row: {
87 | flexDirection: 'row',
88 | position: 'absolute',
89 | bottom: 50,
90 | alignSelf: 'center',
91 | },
92 | });
93 |
94 | class Example extends React.PureComponent {
95 | state = {
96 | value: false,
97 | };
98 | animationRef = React.createRef();
99 |
100 | toggleAnimation() {
101 | this.animationRef.current.reset();
102 | }
103 |
104 | render() {
105 | return (
106 |
107 |
113 | {({ scale, ...animationValues }) => (
114 |
123 | )}
124 |
125 |
126 |
127 | this.animationRef.current.reset()}
129 | style={s.button}
130 | >
131 | Restart
132 |
133 |
134 |
135 | );
136 | }
137 | }
138 |
139 | export default class App extends React.PureComponent {
140 | state = {
141 | showSecond: false,
142 | };
143 |
144 | componentDidMount() {
145 | // testing each animation has its own state
146 | setTimeout(() => this.setState({ showSecond: true }), 1000);
147 | }
148 |
149 | render() {
150 | return (
151 |
152 |
153 |
154 | {this.state.showSecond && }
155 |
156 | );
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/Example/screens/TransitionBase/TransitionBase.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, View, Dimensions, FlatList } from 'react-native';
3 | import {
4 | Reanimatable,
5 | createAnimationConfig,
6 | } from 'react-native-reanimatable';
7 | import Animated from 'react-native-reanimated';
8 | import { Button } from '../../components';
9 |
10 | const { width: windowWidth } = Dimensions.get('window');
11 |
12 | const colors = {
13 | red: '#e74c3c',
14 | white: 'white',
15 | green: '#2ecc71',
16 | };
17 |
18 | const config = createAnimationConfig({
19 | animation: {
20 | type: 'timing',
21 | duration: 300,
22 | },
23 | values: {
24 | width: { from: 50, to: 200 },
25 | height: { from: 50, to: 200 },
26 | left: { from: 20, to: windowWidth - 20 - 200 },
27 | borderRadius: { from: 0, to: 100 },
28 | },
29 | });
30 |
31 | const s = StyleSheet.create({
32 | scroll: {
33 | paddingVertical: 20,
34 | },
35 | animationContainer: {
36 | height: 250,
37 | justifyContent: 'center',
38 | },
39 | animatableView: {
40 | backgroundColor: colors.red,
41 | },
42 | row: {
43 | flexDirection: 'row',
44 | alignSelf: 'center',
45 | },
46 | });
47 |
48 | class Example extends React.PureComponent {
49 | state = {
50 | value: true,
51 | };
52 | reanimatableRef = React.createRef();
53 | initialValue = this.state.value;
54 |
55 | toggleAnimation() {
56 | this.setState((state) => ({ value: !state.value }));
57 | }
58 |
59 | toggleReset() {
60 | this.initialValue = !this.initialValue;
61 | this.reanimatableRef.current.resetTo(this.initialValue);
62 | }
63 |
64 | render() {
65 | return (
66 |
67 |
73 | {({ translateX, ...animatedValues }) => (
74 |
77 | )}
78 |
79 |
80 |
81 |
96 |
97 | );
98 | }
99 | }
100 |
101 | export default function App() {
102 | // performance test
103 | const range = Array.from(new Array(10));
104 |
105 | return (
106 | }
110 | keyExtractor={(_, i) => i.toString()}
111 | />
112 | );
113 | }
114 |
--------------------------------------------------------------------------------
/Example/services/NavigationService.js:
--------------------------------------------------------------------------------
1 | class NavigationService {
2 | navigation = null;
3 |
4 | init(ref) {
5 | this._ref = ref;
6 | this.navigation = this._ref._navigation;
7 | }
8 |
9 | navigate(screen, params) {
10 | const route =
11 | typeof params !== 'object'
12 | ? screen
13 | : { routeName: screen, params };
14 |
15 | this.navigation.navigate(route);
16 | }
17 | }
18 |
19 | export default new NavigationService();
20 |
--------------------------------------------------------------------------------
/Example/services/index.js:
--------------------------------------------------------------------------------
1 | export { default as NavigationService } from './NavigationService';
2 |
--------------------------------------------------------------------------------
/Example/styles/colors.js:
--------------------------------------------------------------------------------
1 | export default {
2 | border: '#b4b4b3',
3 | text: 'black',
4 | textSecondary: '#8E8D92',
5 | icon: 'black',
6 | white: 'white',
7 | green: '#2ecc71',
8 | background: '#F5F5F7',
9 | backgroundSecondary: '#E1E2E7',
10 | };
11 |
--------------------------------------------------------------------------------
/Example/styles/index.js:
--------------------------------------------------------------------------------
1 | export { default as colors } from './colors';
2 |
--------------------------------------------------------------------------------
/Example/utils/createKeys.js:
--------------------------------------------------------------------------------
1 | export default function createKeys(keys) {
2 | return keys.reduce((acc, key) => {
3 | acc[key] = key;
4 | return acc;
5 | }, {});
6 | }
7 |
--------------------------------------------------------------------------------
/Example/utils/index.js:
--------------------------------------------------------------------------------
1 | export { default as createKeys } from './createKeys';
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Terry Sahaidak
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-reanimatable
2 |
3 | > Easy way to create 60fps animations using [react-native-reanimated](https://github.com/kmagiera/react-native-reanimated).
4 |
5 | ## Warning
6 |
7 | This is still very _*work in progress*_. Many things may change. It's not recommended to use it in production right now (but I will :D).
8 |
9 | Many things are still missing. Check out our [roadmap](#roadmap).
10 |
11 | ## Installation
12 |
13 | Install the library from npm:
14 |
15 | ```bash
16 | npm i --save react-native-reanimatable
17 | ```
18 | or
19 | ```bash
20 | yarn add react-native-reanimatable
21 | ```
22 |
23 | After that, follow the [Getting Started](https://github.com/kmagiera/react-native-reanimated#getting-started) of Reanimated, because the library uses it under the hood.
24 |
25 | ## Usage
26 |
27 | Please, check out [Example](/Example) folder for now. You can run examples using [Expo](https://expo.io/learn).
28 |
29 | Detailed usage and explanation will come later.
30 |
31 | ## Roadmap
32 | - [ ] Add support for decay and spring type of animations
33 | - [x] Add support for keyframes (interpolation)
34 | - [x] Add support for scroll event delegation
35 | - [ ] Add support for pan events delegation
36 | - [ ] Add support for animated snap point of delegation
37 | - [ ] Add support for mounting/unmounting animations
38 | - [ ] Add more examples
39 | - [ ] Add docs
40 | - [ ] Add typings
41 | - [ ] Add some out of the box animations (bounces, zooms etc)
42 | - [ ] Add animation lifecycle
43 | - [ ] Add support for color animations
44 | - [ ] Add ability to control the animation (play, pause, reset)
45 |
46 | ## Example
47 | ```jsx
48 | import React from 'react';
49 | import {
50 | StyleSheet,
51 | Text,
52 | View,
53 | Button,
54 | Animated,
55 | } from 'react-native';
56 | import { Reanimatable, createAnimationConfig } from 'react-native-reanimatable';
57 | import Animated from 'react-native-reanimated';
58 |
59 | const config = createAnimationConfig({
60 | animation: {
61 | type: 'timing',
62 | duration: 300,
63 | },
64 | values: {
65 | width: { from: 100, to: 150 },
66 | height: { from: 100, to: 150 },
67 | translateY: { from: 0, to: 200 },
68 | },
69 | });
70 |
71 | const s = StyleSheet.create({
72 | container: {
73 | flex: 1,
74 | backgroundColor: '#fff',
75 | alignItems: 'center',
76 | justifyContent: 'center',
77 | },
78 | animationContainer: {
79 | marginBottom: 100,
80 | },
81 | animatableView: {
82 | height: 100,
83 | backgroundColor: 'red',
84 | },
85 | });
86 |
87 | export default class App extends React.PureComponent {
88 | state = {
89 | value: false,
90 | };
91 |
92 | toggleAnimation() {
93 | this.setState((state) => ({ value: !state.value }));
94 | }
95 |
96 | render() {
97 | return (
98 |
99 |
104 | {({ width, height, translateY }) => (
105 |
111 | )}
112 |
113 |
114 | this.toggleAnimation()}
117 | style={s.button}
118 | />
119 |
120 | );
121 | }
122 | }
123 | ```
124 |
125 | ## Inspirations
126 |
127 | Inspired by incredible [react-native-animatable](https://github.com/oblador/react-native-animatable/).
128 |
129 | ## License
130 | [MIT](LICENSE) © Terry Sahaidak 2019
131 |
--------------------------------------------------------------------------------
/lib/components/DelegateAnimation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import T from 'prop-types';
3 |
4 | class DelegateAnimation extends React.PureComponent {
5 | constructor(props) {
6 | super(props);
7 |
8 | const { values } = props.generate();
9 |
10 | this.state = {
11 | values,
12 | };
13 | }
14 |
15 | render() {
16 | return this.props.children(this.state.values);
17 | }
18 | }
19 |
20 | DelegateAnimation.propTypes = {
21 | children: T.func,
22 | generate: T.func,
23 | };
24 |
25 | export default DelegateAnimation;
26 |
--------------------------------------------------------------------------------
/lib/components/InterpolationTransitionAnimation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { InteractionManager } from 'react-native';
3 | import T from 'prop-types';
4 | import A from 'react-native-reanimated';
5 | import { ANIMATION_STATE } from '../core/constants';
6 |
7 | class InterpolationTransitionAnimation extends Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | const { value, generate } = props;
12 | const { lazy, initialAnimationValues, operations } = generate();
13 |
14 | this._lazy = lazy;
15 | this._operations = operations;
16 | this._initialAnimationValues = initialAnimationValues;
17 | this.animationState = null;
18 | this.interpolator = null;
19 | this._initialized = false;
20 |
21 | this.state = {
22 | animationValues: initialAnimationValues[value ? 'to' : 'from'],
23 | };
24 | }
25 |
26 | componentDidMount() {
27 | InteractionManager.runAfterInteractions(() => {
28 | if (!this._lazy) {
29 | this._initAnimation();
30 | }
31 | });
32 | }
33 |
34 | componentDidUpdate(prevProps) {
35 | if (this.props.value !== prevProps.value) {
36 | if (this._lazy && !this._initialized) {
37 | this._initAnimation(prevProps.value);
38 |
39 | this._initialized = true;
40 | } else {
41 | this._toggleAnimationState();
42 | }
43 | }
44 | }
45 |
46 | _initAnimation(value = this.props.value) {
47 | const {
48 | animationValues,
49 | interpolator,
50 | animationState,
51 | } = this._operations.createAnimation(value);
52 | this.interpolator = interpolator;
53 | this.animationState = animationState;
54 |
55 | this.setState(
56 | {
57 | animationValues,
58 | },
59 | () => {
60 | if (this._lazy) {
61 | this._toggleAnimationState();
62 | }
63 | },
64 | );
65 | }
66 |
67 | _toggleAnimationState() {
68 | this.animationState.setValue(
69 | this.props.value
70 | ? ANIMATION_STATE.PLAY_FORWARD
71 | : ANIMATION_STATE.PLAY_BACKWARD,
72 | );
73 | }
74 |
75 | // reset() {
76 | // this.resetTo(this.initialValue);
77 | // }
78 |
79 | // resetTo(value) {
80 | // const animationValues = this._initAnimation();
81 |
82 | // this.setState(
83 | // {
84 | // animationValues,
85 | // },
86 | // () =>
87 | // this.animationState.setValue(
88 | // !value
89 | // ? ANIMATION_STATE.END_POINT
90 | // : ANIMATION_STATE.START_POINT,
91 | // ),
92 | // );
93 | // }
94 |
95 | render() {
96 | return (
97 |
98 | {this.props.children(this.state.animationValues)}
99 |
100 | );
101 | }
102 | }
103 |
104 | InterpolationTransitionAnimation.propTypes = {
105 | value: T.bool,
106 | children: T.func,
107 | generate: T.func,
108 | };
109 |
110 | export default InterpolationTransitionAnimation;
111 |
--------------------------------------------------------------------------------
/lib/components/KeyframesAnimation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { InteractionManager } from 'react-native';
3 | import T from 'prop-types';
4 | import A from 'react-native-reanimated';
5 |
6 | class KeyframesAnimation extends React.PureComponent {
7 | constructor(props) {
8 | super(props);
9 |
10 | const { values, operations } = props.generate();
11 | this._operations = operations;
12 |
13 | this.state = {
14 | values,
15 | };
16 | }
17 |
18 | componentDidMount() {
19 | InteractionManager.runAfterInteractions(() => {
20 | const animation = this._operations.createAnimation();
21 |
22 | this.setState({
23 | animation,
24 | });
25 | });
26 | }
27 |
28 | reset() {
29 | this._operations.reset();
30 | }
31 |
32 | render() {
33 | return (
34 |
35 | {this.state.animation && (
36 |
37 | )}
38 | {this.props.children(this.state.values)}
39 |
40 | );
41 | }
42 | }
43 |
44 | KeyframesAnimation.propTypes = {
45 | children: T.func,
46 | generate: T.func,
47 | };
48 |
49 | export default KeyframesAnimation;
50 |
--------------------------------------------------------------------------------
/lib/components/LoopAnimation.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import T from 'prop-types';
3 | import TransitionAnimation from './TransitionAnimation';
4 | import { animationConfigPropTypes } from './propTypes';
5 |
6 | class LoopAnimation extends PureComponent {
7 | constructor(props) {
8 | super(props);
9 |
10 | this.count = props.iterationCount;
11 | this.state = {
12 | value: false,
13 | };
14 | }
15 |
16 | componentDidMount() {
17 | if (this.props.autoplay) {
18 | this.play();
19 | }
20 | }
21 |
22 | componentWillUnmount() {
23 | this.stop();
24 | }
25 |
26 | toggleState() {
27 | this.setState(
28 | (state) => ({ value: !state.value }),
29 | () => {
30 | if (typeof this.count === 'number') {
31 | if (this.count > 0) {
32 | this.count = this.count - 1;
33 | } else {
34 | clearInterval(this._interval);
35 | }
36 | }
37 | },
38 | );
39 | }
40 |
41 | play(count) {
42 | this.count = count || this.count;
43 |
44 | const { duration } = this.props.config.animation;
45 |
46 | this._interval = setInterval(() => {
47 | this.toggleState();
48 | }, duration);
49 | }
50 |
51 | stop() {
52 | clearInterval(this._interval);
53 | }
54 |
55 | render() {
56 | return React.cloneElement(TransitionAnimation, {
57 | ...this.props,
58 | value: this.state.value,
59 | });
60 | }
61 | }
62 |
63 | LoopAnimation.propTypes = {
64 | iterationCount: T.number,
65 | config: animationConfigPropTypes.isRequired,
66 | autoplay: T.bool,
67 | };
68 |
69 | LoopAnimation.defaultProps = {
70 | autoplay: true,
71 | };
72 |
73 | export default LoopAnimation;
74 |
--------------------------------------------------------------------------------
/lib/components/Reanimatable.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ViewPropTypes, View } from 'react-native';
3 | import T from 'prop-types';
4 | import { animationConfigPropTypes } from './propTypes';
5 | import {
6 | KeyframesAnimation,
7 | InterpolationTransitionAnimation,
8 | TransitionAnimation,
9 | DelegateAnimation,
10 | } from './';
11 | import { ANIMATION_TYPE } from '../core/constants';
12 |
13 | const validateConfig = (config) => {
14 | if (typeof config.generate !== 'function') {
15 | throw new Error(
16 | 'Invalid config provided for the Reanimatable component. Did you forget to use "createAnimationConfig"?',
17 | );
18 | }
19 | };
20 | // prettier-ignore
21 | const Reanimatable = React.forwardRef(({
22 | config,
23 | value,
24 | containerStyle,
25 | ...rest
26 | }, ref) => {
27 | // prettier-ignore
28 | validateConfig(config);
29 |
30 | const animationConfig = Object.assign({}, config, {
31 | initialValue: value,
32 | });
33 | let content = null;
34 |
35 | switch (animationConfig.type) {
36 | case ANIMATION_TYPE.KEYFRAMES:
37 | content = (
38 |
43 | );
44 | break;
45 | case ANIMATION_TYPE.DELEGATE:
46 | content = (
47 |
52 | );
53 | break;
54 | case ANIMATION_TYPE.INTERPOLATION_TRANSITION:
55 | content = (
56 |
62 | );
63 | break;
64 | case ANIMATION_TYPE.TRANSITION:
65 | default:
66 | content = (
67 |
73 | );
74 | break;
75 | }
76 |
77 | if (containerStyle) {
78 | return {content};
79 | }
80 |
81 | return content;
82 | });
83 |
84 | Reanimatable.propTypes = {
85 | config: animationConfigPropTypes,
86 | value: T.bool,
87 | containerStyle: ViewPropTypes.style,
88 | };
89 |
90 | export default Reanimatable;
91 |
--------------------------------------------------------------------------------
/lib/components/ScrollView.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import T from 'prop-types';
3 | import A from 'react-native-reanimated';
4 |
5 | class ScrollView extends React.PureComponent {
6 | componentDidMount() {
7 | if (this.props.initialScrollPosition) {
8 | this.scrollTo({
9 | ...this.props.initialScrollPosition,
10 | animated: false,
11 | });
12 | }
13 | }
14 |
15 | componentWillUnmount() {
16 | this.props.delegate.reset();
17 | }
18 |
19 | _ref = React.createRef();
20 | scrollPosition = 0;
21 |
22 | scrollTo(value) {
23 | global.requestAnimationFrame(() =>
24 | this._ref.current._component.scrollTo(value),
25 | );
26 | }
27 |
28 | _onScroll(value) {
29 | if (this.props.onScroll) {
30 | this.props.onScroll(value);
31 | }
32 |
33 | this.scrollPosition = value;
34 | }
35 |
36 | _onScrollEndDrag(evt) {
37 | this.props.snapPoints.forEach((item) => {
38 | const { from, to, snap } = item;
39 |
40 | if (this.scrollPosition > from && this.scrollPosition <= to) {
41 | this.scrollTo(
42 | this.props.vertical ? { x: snap } : { y: snap },
43 | );
44 | }
45 | });
46 |
47 | if (this.props.onScrollEndDrag) {
48 | this.props.onScrollEndDrag(evt);
49 | }
50 | }
51 |
52 | render() {
53 | const { delegate } = this.props;
54 |
55 | const props = {
56 | ...this.props,
57 | onScroll: delegate.event,
58 | onScrollEndDrag: (evt) => this._onScrollEndDrag(evt),
59 | };
60 |
61 | return (
62 |
63 | this._onScroll(r[0])),
66 | delegate.value,
67 | ])}
68 | />
69 |
70 |
71 | );
72 | }
73 | }
74 |
75 | ScrollView.propTypes = {
76 | delegate: T.shape({
77 | event: T.object,
78 | reset: T.func,
79 | }),
80 | onScroll: T.func,
81 | onScrollEndDrag: T.func,
82 | vertical: T.bool,
83 | initialScrollPosition: T.shape({
84 | x: T.number,
85 | y: T.number,
86 | }),
87 | snapPoints: T.arrayOf(
88 | T.shape({
89 | from: T.number,
90 | to: T.number,
91 | snap: T.number,
92 | }),
93 | ),
94 | };
95 |
96 | export default ScrollView;
97 |
--------------------------------------------------------------------------------
/lib/components/TransitionAnimation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { InteractionManager } from 'react-native';
3 | import T from 'prop-types';
4 | import A from 'react-native-reanimated';
5 | import { ANIMATION_STATE } from '../core/constants';
6 |
7 | class TransitionAnimation extends Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.initialValue = props.value;
12 |
13 | const {
14 | animationState,
15 | operations,
16 | values,
17 | } = this.props.generate();
18 |
19 | this._operations = operations;
20 |
21 | this.animationState = animationState;
22 |
23 | this.state = {
24 | values,
25 | };
26 | }
27 |
28 | componentDidMount() {
29 | // in order to reset animation values and apply it to layout
30 | // we have to do it on a mounted component
31 | // the user won't notice it
32 | this.reset();
33 |
34 | InteractionManager.runAfterInteractions(() => {
35 | const animation = this._operations.createAnimation();
36 |
37 | this.setState({
38 | animation,
39 | });
40 | });
41 | }
42 |
43 | componentDidUpdate(prevProps) {
44 | if (this.props.value !== prevProps.value) {
45 | this.animationState.setValue(
46 | this.props.value
47 | ? ANIMATION_STATE.PLAY_FORWARD
48 | : ANIMATION_STATE.PLAY_BACKWARD,
49 | );
50 | }
51 | }
52 |
53 | reset() {
54 | this.resetTo(this.initialValue);
55 | }
56 |
57 | resetTo(value) {
58 | this._operations.reset(value);
59 | this.animationState.setValue(
60 | !value
61 | ? ANIMATION_STATE.END_POINT
62 | : ANIMATION_STATE.START_POINT,
63 | );
64 | }
65 |
66 | render() {
67 | return (
68 |
69 | {this.state.animation && (
70 |
71 | )}
72 | {this.props.children(this.state.values)}
73 |
74 | );
75 | }
76 | }
77 |
78 | TransitionAnimation.propTypes = {
79 | value: T.bool,
80 | children: T.func,
81 | generate: T.func,
82 | };
83 |
84 | export default TransitionAnimation;
85 |
--------------------------------------------------------------------------------
/lib/components/index.js:
--------------------------------------------------------------------------------
1 | import KeyframesAnimation from './KeyframesAnimation';
2 | import LoopAnimation from './LoopAnimation';
3 | import TransitionAnimation from './TransitionAnimation';
4 | import InterpolationTransitionAnimation from './InterpolationTransitionAnimation';
5 | import DelegateAnimation from './DelegateAnimation';
6 |
7 | module.exports = {
8 | KeyframesAnimation,
9 | LoopAnimation,
10 | TransitionAnimation,
11 | DelegateAnimation,
12 | InterpolationTransitionAnimation,
13 | };
14 |
--------------------------------------------------------------------------------
/lib/components/propTypes.js:
--------------------------------------------------------------------------------
1 | import T from 'prop-types';
2 |
3 | export const animationConfigPropTypes = T.shape({
4 | animation: T.object,
5 | values: T.object,
6 | });
7 |
--------------------------------------------------------------------------------
/lib/core/animations.js:
--------------------------------------------------------------------------------
1 | import A, { Easing } from 'react-native-reanimated';
2 |
3 | export const runTiming = ({
4 | clock,
5 | duration,
6 | oppositeClock,
7 | value,
8 | dest,
9 | easing,
10 | onFinish,
11 | }) => {
12 | const state = {
13 | finished: new A.Value(0),
14 | position: new A.Value(0),
15 | time: new A.Value(0),
16 | frameTime: new A.Value(0),
17 | };
18 |
19 | const config = {
20 | duration,
21 | toValue: new A.Value(0),
22 | easing: easing || Easing.inOut(Easing.ease),
23 | };
24 |
25 | return A.block([
26 | // stop opposite clock before running our animation
27 | // to set last (previous) position as a current one
28 | oppositeClock
29 | ? A.cond(
30 | A.clockRunning(oppositeClock),
31 | A.stopClock(oppositeClock),
32 | )
33 | : 0,
34 | // run our animation clock
35 | A.cond(
36 | A.clockRunning(clock),
37 | // do nothing if our clock is already running
38 | 0,
39 | // otherwise pre set all the values
40 | [
41 | // If the clock isn't running we reset all the animation params and start the clock
42 | A.set(state.finished, 0),
43 | A.set(state.time, 0),
44 | A.set(state.position, value),
45 | A.set(state.frameTime, 0),
46 | A.set(config.toValue, dest),
47 | A.startClock(clock),
48 | ],
49 | ),
50 | // we run the step here that is going to update position
51 | A.timing(clock, state, config),
52 | // if the animation is over we stop the clock
53 | A.cond(state.finished, A.block([A.stopClock(clock), onFinish])),
54 | // we made the block return the updated position
55 | A.set(value, state.position),
56 | ]);
57 | };
58 |
59 | export function getProperAnimation(
60 | reanimatableConfig,
61 | animationConfig,
62 | ) {
63 | const { type, ...animation } = reanimatableConfig.animation;
64 |
65 | switch (type) {
66 | case 'timing':
67 | return runTiming(Object.assign({}, animationConfig, animation));
68 |
69 | default:
70 | throw new Error(
71 | `Unsupported animation of type: ${type}.\nSupported are timing, spring, decay.`,
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/core/constants.js:
--------------------------------------------------------------------------------
1 | import { createKeys } from '../utils';
2 |
3 | export const ANIMATION_STATE = {
4 | START_POINT: 0,
5 | PLAY_FORWARD: 1,
6 | END_POINT: 2,
7 | PLAY_BACKWARD: 3,
8 | };
9 |
10 | export const ANIMATION_TYPE = createKeys([
11 | 'TRANSITION',
12 | 'INTERPOLATION_TRANSITION',
13 | 'KEYFRAMES',
14 | 'DELEGATE',
15 | ]);
16 |
--------------------------------------------------------------------------------
/lib/core/createConfig.js:
--------------------------------------------------------------------------------
1 | import { ANIMATION_TYPE } from './constants';
2 | import createKeyframesAnimation from './createKeyframesAnimation';
3 | import createTransitionAnimation from './createTransitionAnimation';
4 | import createInterpolationTransitionAnimation from './createInterpolationTransitionAnimation';
5 | import createDelegateAnimation from './createDelegateAnimation';
6 |
7 | export default function createConfig(animationConfig) {
8 | const config = {};
9 |
10 | if (animationConfig.animation.delegate) {
11 | config.type = ANIMATION_TYPE.DELEGATE;
12 | config.generate = createDelegateAnimation(animationConfig);
13 | } else if (animationConfig.keyframes) {
14 | config.type = ANIMATION_TYPE.KEYFRAMES;
15 | config.generate = createKeyframesAnimation(animationConfig);
16 | } else if (animationConfig.animation.interpolation) {
17 | config.type = ANIMATION_TYPE.INTERPOLATION_TRANSITION;
18 | config.generate = createInterpolationTransitionAnimation(
19 | animationConfig,
20 | );
21 | } else {
22 | config.type = ANIMATION_TYPE.TRANSITION;
23 | config.generate = createTransitionAnimation(animationConfig);
24 | }
25 |
26 | return config;
27 | }
28 |
--------------------------------------------------------------------------------
/lib/core/createDelegate.js:
--------------------------------------------------------------------------------
1 | import Animated from 'react-native-reanimated';
2 | import * as objectPath from '../utils/objectPath';
3 |
4 | export default function createDelegate(eventType, path) {
5 | const value = new Animated.Value(0);
6 |
7 | const eventMap = objectPath.map(
8 | ['nativeEvent'].concat(path),
9 | value,
10 | );
11 |
12 | const event = Animated.event([eventMap]);
13 |
14 | function reset() {
15 | value.setValue(0);
16 | }
17 |
18 | return {
19 | event,
20 | value,
21 | reset,
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/lib/core/createDelegateAnimation.js:
--------------------------------------------------------------------------------
1 | import A from 'react-native-reanimated';
2 |
3 | const numberSort = (a, b) => a - b;
4 |
5 | function generateRanges(pairs) {
6 | return pairs.reduce(
7 | (acc, current) => {
8 | const [frame, value] = current;
9 | const frameNumber = +frame;
10 | acc.inputRange.push(frameNumber);
11 | acc.outputRange.push(+value);
12 |
13 | return acc;
14 | },
15 | {
16 | inputRange: [],
17 | outputRange: [],
18 | },
19 | );
20 | }
21 |
22 | export function normalizeValues(keyframes) {
23 | return Object.keys(keyframes)
24 | .sort(numberSort)
25 | .reduce((acc, frameName) => {
26 | const currentFrame = keyframes[frameName];
27 |
28 | Object.keys(currentFrame).forEach((propName) => {
29 | const stylePairs = [frameName, currentFrame[propName]];
30 | if (Array.isArray(acc[propName])) {
31 | acc[propName].push(stylePairs);
32 | } else {
33 | acc[propName] = [stylePairs];
34 | }
35 | });
36 |
37 | return acc;
38 | }, {});
39 | }
40 |
41 | export function generateInterpolations({ keyframes, baseValue }) {
42 | const normalized = normalizeValues(keyframes);
43 |
44 | return Object.keys(normalized).reduce((acc, name) => {
45 | const pairs = normalized[name];
46 |
47 | const { inputRange, outputRange } = generateRanges(pairs);
48 |
49 | const animatedValue = A.interpolate(baseValue, {
50 | inputRange,
51 | outputRange,
52 | extrapolate: A.Extrapolate.CLAMP,
53 | });
54 |
55 | acc[name] = animatedValue;
56 |
57 | return acc;
58 | }, {});
59 | }
60 |
61 | export default function createDelegateAnimation(config) {
62 | const {
63 | keyframes,
64 | animation: { delegate },
65 | } = config;
66 |
67 | const values = generateInterpolations({
68 | keyframes,
69 | baseValue: delegate.value,
70 | });
71 |
72 | return () => ({
73 | values,
74 | });
75 | }
76 |
--------------------------------------------------------------------------------
/lib/core/createInterpolationTransitionAnimation.js:
--------------------------------------------------------------------------------
1 | import A from 'react-native-reanimated';
2 | import { ANIMATION_STATE } from './constants';
3 | import { getProperAnimation } from './animations';
4 |
5 | export default function createTransitionAnimation(config) {
6 | const valueNames = Object.keys(config.values);
7 | const initialAnimationValues = {
8 | from: {},
9 | to: {},
10 | };
11 |
12 | valueNames.forEach((key) => {
13 | const current = config.values[key];
14 |
15 | initialAnimationValues.from[key] = current.from;
16 | initialAnimationValues.to[key] = current.to;
17 | });
18 |
19 | return () => {
20 | function createAnimation(stateValue) {
21 | const animationState = new A.Value(ANIMATION_STATE.START_POINT);
22 |
23 | // interpolator will be used as a position tracker of animation
24 | // using interpolation technique
25 | const interpolator = new A.Value(0);
26 |
27 | const animationValues = {};
28 |
29 | valueNames.forEach((key, index) => {
30 | const first = index === 0;
31 | const animatedBlock = [];
32 |
33 | // add tracking of animationState and running animation
34 | // via animating interpolator only for very first
35 | // animation value to get rid of Animation.Code
36 | if (first) {
37 | const forwardAnimationClock = new A.Clock();
38 | const backwardAnimationClock = new A.Clock();
39 |
40 | const forwardAnimationConfig = {
41 | clock: forwardAnimationClock,
42 | oppositeClock: backwardAnimationClock,
43 | value: interpolator,
44 | dest: config.animation.duration,
45 | };
46 |
47 | const backwardAnimationConfig = {
48 | clock: backwardAnimationClock,
49 | oppositeClock: forwardAnimationClock,
50 | value: interpolator,
51 | dest: 0,
52 | };
53 |
54 | if (first) {
55 | forwardAnimationConfig.onFinish = [
56 | A.set(animationState, ANIMATION_STATE.END_POINT),
57 | ];
58 |
59 | backwardAnimationConfig.onFinish = [
60 | A.set(animationState, ANIMATION_STATE.START_POINT),
61 | ];
62 | }
63 |
64 | const forwardTiming = getProperAnimation(
65 | config,
66 | !stateValue
67 | ? forwardAnimationConfig
68 | : backwardAnimationConfig,
69 | );
70 | const backwardTiming = getProperAnimation(
71 | config,
72 | !stateValue
73 | ? backwardAnimationConfig
74 | : forwardAnimationConfig,
75 | );
76 |
77 | animatedBlock.push(
78 | A.cond(
79 | A.eq(animationState, ANIMATION_STATE.PLAY_FORWARD),
80 | forwardTiming,
81 | ),
82 | A.cond(
83 | A.eq(animationState, ANIMATION_STATE.PLAY_BACKWARD),
84 | backwardTiming,
85 | ),
86 | );
87 | }
88 |
89 | const item = config.values[key];
90 |
91 | const inputRange = [0, config.animation.duration];
92 | const outputRange = [item.from, item.to];
93 |
94 | // reverse input range
95 | // in order to animate value in reverse way
96 | // from `to` to `from`
97 | if (stateValue) {
98 | outputRange.reverse();
99 | }
100 |
101 | const valueAnimation = A.interpolate(interpolator, {
102 | inputRange,
103 | outputRange,
104 | extrapolate: A.Extrapolate.CLAMP,
105 | });
106 |
107 | animatedBlock.push(valueAnimation);
108 |
109 | animationValues[key] = A.block(animatedBlock);
110 | });
111 |
112 | return {
113 | animationState,
114 | animationValues,
115 | interpolator,
116 | };
117 | }
118 |
119 | return {
120 | valueNames,
121 | lazy: config.animation.lazy,
122 | initialAnimationValues,
123 | operations: {
124 | createAnimation,
125 | },
126 | };
127 | };
128 | }
129 |
--------------------------------------------------------------------------------
/lib/core/createKeyframesAnimation.js:
--------------------------------------------------------------------------------
1 | import A from 'react-native-reanimated';
2 | import { runTiming } from './animations';
3 |
4 | function generateRanges(pairs, duration) {
5 | return pairs.reduce(
6 | (acc, current) => {
7 | const [frame, value] = current;
8 | const frameNumber = +frame;
9 | acc.inputRange.push(
10 | frameNumber === 0 ? 0 : (frameNumber * duration) / 100,
11 | );
12 | acc.outputRange.push(+value);
13 |
14 | return acc;
15 | },
16 | {
17 | inputRange: [],
18 | outputRange: [],
19 | },
20 | );
21 | }
22 |
23 | function normalizeValues(keyframes) {
24 | return Object.keys(keyframes).reduce((acc, frameName) => {
25 | const currentFrame = keyframes[frameName];
26 |
27 | Object.keys(currentFrame).forEach((propName) => {
28 | const stylePairs = [frameName, currentFrame[propName]];
29 | if (Array.isArray(acc[propName])) {
30 | acc[propName].push(stylePairs);
31 | } else {
32 | acc[propName] = [stylePairs];
33 | }
34 | });
35 |
36 | return acc;
37 | }, {});
38 | }
39 |
40 | function createRanges(keyframes, duration) {
41 | const normalized = normalizeValues(keyframes);
42 |
43 | return Object.keys(normalized).map((name) => {
44 | const pairs = normalized[name];
45 | // pairs = [[frameName, value], [frameName, value]]
46 | return [name, generateRanges(pairs, duration)];
47 | });
48 | }
49 |
50 | function generateInterpolations({ ranges, duration, baseValue }) {
51 | return ranges.reduce((acc, [name, range]) => {
52 | const animatedValue = A.interpolate(baseValue, {
53 | ...range,
54 | extrapolate: A.Extrapolate.CLAMP,
55 | });
56 |
57 | acc[name] = animatedValue;
58 |
59 | return acc;
60 | }, {});
61 | }
62 |
63 | export default function createKeyframesAnimation(config) {
64 | const {
65 | keyframes,
66 | animation: { duration },
67 | } = config;
68 |
69 | const ranges = createRanges(keyframes, duration);
70 |
71 | return () => {
72 | const interpolation = new A.Value(0);
73 |
74 | function reset() {
75 | interpolation.setValue(0);
76 | }
77 |
78 | const values = generateInterpolations({
79 | ranges,
80 | duration,
81 | baseValue: interpolation,
82 | });
83 | const clock = new A.Clock();
84 |
85 | const createAnimation = () =>
86 | runTiming({
87 | clock,
88 | value: interpolation,
89 | dest: duration,
90 | duration,
91 | });
92 |
93 | return {
94 | interpolation,
95 | values,
96 | operations: {
97 | createAnimation,
98 | reset,
99 | },
100 | };
101 | };
102 | }
103 |
--------------------------------------------------------------------------------
/lib/core/createTransitionAnimation.js:
--------------------------------------------------------------------------------
1 | import A from 'react-native-reanimated';
2 | import { ANIMATION_STATE } from './constants';
3 | import { getProperAnimation } from './animations';
4 |
5 | function createValues(valueNames) {
6 | return valueNames.reduce((acc, valueName) => {
7 | acc[valueName] = new A.Value(0);
8 | return acc;
9 | }, {});
10 | }
11 |
12 | function setValues(configValues, animationValues) {
13 | return (initialValue) =>
14 | Object.keys(animationValues).forEach((valueName) => {
15 | const { from, to } = configValues[valueName];
16 | animationValues[valueName].setValue(!initialValue ? from : to);
17 | });
18 | }
19 |
20 | export default function createTransitionAnimation(config) {
21 | const valueNames = Object.keys(config.values);
22 | return () => {
23 | const values = createValues(valueNames);
24 | const animationState = new A.Value(ANIMATION_STATE.START_POINT);
25 |
26 | function createAnimation() {
27 | const {
28 | forwardAnimations,
29 | backwardAnimations,
30 | } = valueNames.reduce(
31 | (acc, key, index) => {
32 | const animation = config.values[key];
33 | const { from, to } = animation;
34 | const currentValue = values[key];
35 |
36 | const first = index === 0;
37 |
38 | const forwardAnimationClock = new A.Clock();
39 | const backwardAnimationClock = new A.Clock();
40 |
41 | const forwardAnimationConfig = {
42 | clock: forwardAnimationClock,
43 | oppositeClock: backwardAnimationClock,
44 | value: currentValue,
45 | dest: to,
46 | };
47 |
48 | const backwardAnimationConfig = {
49 | clock: backwardAnimationClock,
50 | oppositeClock: forwardAnimationClock,
51 | value: currentValue,
52 | dest: from,
53 | };
54 |
55 | if (first) {
56 | forwardAnimationConfig.onFinish = A.block([
57 | A.set(animationState, ANIMATION_STATE.END_POINT),
58 | ]);
59 |
60 | backwardAnimationConfig.onFinish = A.block([
61 | A.set(animationState, ANIMATION_STATE.START_POINT),
62 | ]);
63 | }
64 |
65 | const forwardTiming = getProperAnimation(
66 | config,
67 | forwardAnimationConfig,
68 | );
69 | const backwardTiming = getProperAnimation(
70 | config,
71 | backwardAnimationConfig,
72 | );
73 |
74 | acc.forwardAnimations.push(forwardTiming);
75 |
76 | acc.backwardAnimations.push(backwardTiming);
77 |
78 | return acc;
79 | },
80 | {
81 | forwardAnimations: [],
82 | backwardAnimations: [],
83 | },
84 | );
85 |
86 | return A.block([
87 | A.cond(
88 | A.eq(animationState, ANIMATION_STATE.PLAY_FORWARD),
89 | // run all the forward animations
90 | A.block(forwardAnimations),
91 | ),
92 |
93 | A.cond(
94 | A.eq(animationState, ANIMATION_STATE.PLAY_BACKWARD),
95 | // run all the backward animations
96 | A.block(backwardAnimations),
97 | ),
98 | ]);
99 | }
100 |
101 | return {
102 | values,
103 | animationState,
104 | operations: {
105 | createAnimation,
106 | reset: setValues(config.values, values),
107 | },
108 | };
109 | };
110 | }
111 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import LoopAnimation from './components/LoopAnimation';
2 | import Reanimatable from './components/Reanimatable';
3 | import ScrollView from './components/ScrollView';
4 | import * as animations from './core/animations';
5 | import createConfig from './core/createConfig';
6 | import createDelegate from './core/createDelegate';
7 |
8 | module.exports = {
9 | Reanimatable,
10 | ScrollView,
11 | LoopAnimation,
12 | animations,
13 | createDelegate,
14 | createAnimationConfig: createConfig,
15 | };
16 |
--------------------------------------------------------------------------------
/lib/utils/createKeys.js:
--------------------------------------------------------------------------------
1 | export default function createKeys(keys) {
2 | return keys.reduce((acc, key) => {
3 | acc[key] = key;
4 | return acc;
5 | }, {});
6 | }
7 |
--------------------------------------------------------------------------------
/lib/utils/index.js:
--------------------------------------------------------------------------------
1 | import createKeys from './createKeys';
2 |
3 | module.exports = {
4 | createKeys,
5 | };
6 |
--------------------------------------------------------------------------------
/lib/utils/objectPath.js:
--------------------------------------------------------------------------------
1 | export function get(path, nestedObj) {
2 | return path.reduce(
3 | (obj, key) =>
4 | (obj && typeof obj[key] !== 'undefined' ? obj[key] : undefined),
5 | nestedObj,
6 | );
7 | }
8 |
9 | export function map(path, value) {
10 | let localPath = [...path];
11 | const obj = {};
12 | let current = obj;
13 | while (localPath.length > 1) {
14 | const [head, ...tail] = localPath;
15 | localPath = tail;
16 | if (current[head] === undefined) {
17 | current[head] = {};
18 | }
19 | current = current[head];
20 | }
21 | current[localPath[0]] = value;
22 | return obj;
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-reanimatable",
3 | "version": "0.5.0",
4 | "description": "Wrapper for Reanimated with an easy declarative API.",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [
10 | "animations",
11 | "react-native",
12 | "reactnative",
13 | "reanimated",
14 | "animatable"
15 | ],
16 | "author": {
17 | "name": "Terry Sahaidak",
18 | "email": "terry@sahaidak.com"
19 | },
20 | "license": "MIT",
21 | "devDependencies": {
22 | "@babel/core": "^7.2.2",
23 | "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
24 | "@babel/runtime": "^7.3.1",
25 | "babel-core": "^6.26.3",
26 | "babel-eslint": "^8.0.1",
27 | "eslint": "^4.2.0",
28 | "eslint-config-airbnb": "^16.1.0",
29 | "eslint-formatter-pretty": "^1.1.0",
30 | "eslint-plugin-import": "^2.7.0",
31 | "eslint-plugin-jsx-a11y": "^6.0.2",
32 | "eslint-plugin-react": "^7.1.0",
33 | "eslint-plugin-react-native": "^3.1.0",
34 | "metro-react-native-babel-preset": "^0.45.3"
35 | },
36 | "peerDependencies": {
37 | "react-native": "*",
38 | "react": "*",
39 | "react-native-reanimated": "*"
40 | },
41 | "dependencies": {
42 | "prop-types": "^15.6.2"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------