├── .circleci └── config.yml ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── index.js ├── package.json ├── release.config.js └── src ├── AnimatedNumber.js ├── AnimatedText.js ├── Opacity.js ├── Scale.js ├── ScaleAndOpacity.js ├── Shake.js ├── SharedElement.js ├── SharedElementRenderer.js ├── TranslateX.js ├── TranslateXY.js ├── TranslateY.js └── TranslateYAndOpacity.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | 7 | # Used Workspace 8 | workspace: &workspace 9 | ~/mui 10 | 11 | # Config for node 12 | node_config: &node_config 13 | working_directory: *workspace 14 | docker: 15 | - image: circleci/node:8 16 | 17 | # Key to cache gradle dependecies 18 | yarn_key: &yarn_key 19 | yarn-cache-{{ checksum "yarn.lock" }} 20 | 21 | 22 | jobs: 23 | install-dependencies: 24 | <<: *node_config 25 | steps: 26 | - checkout 27 | - attach_workspace: 28 | at: *workspace 29 | - restore_cache: 30 | key: *yarn_key 31 | - run: 32 | name: Intal yarn dependencies 33 | command: yarn 34 | - save_cache: 35 | key: *yarn_key 36 | paths: node_modules 37 | - persist_to_workspace: 38 | root: . 39 | paths: . 40 | 41 | lint: 42 | <<: *node_config 43 | steps: 44 | - attach_workspace: 45 | at: *workspace 46 | - run: 47 | name: Lint 48 | command: yarn lint 49 | 50 | test: 51 | <<: *node_config 52 | steps: 53 | - attach_workspace: 54 | at: *workspace 55 | - run: 56 | name: Jest 57 | command: yarn test 58 | 59 | publish: 60 | <<: *node_config 61 | steps: 62 | - attach_workspace: 63 | at: *workspace 64 | - run: 65 | name: Publish 66 | command: yarn semantic-release 67 | 68 | 69 | workflows: 70 | version: 2 71 | test-and-deploy: 72 | jobs: 73 | - install-dependencies 74 | - lint: 75 | requires: 76 | - install-dependencies 77 | - publish: 78 | requires: 79 | - lint 80 | filters: 81 | branches: 82 | only: 'master' 83 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true, 7 | "jest": true 8 | }, 9 | "parserOptions": { 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "jsx": true 13 | } 14 | }, 15 | "extends": ["airbnb", "prettier"], 16 | "plugins": ["import", "react", "prettier", "react-native"], 17 | "rules": { 18 | "react/prop-types": "off", 19 | "coma-dangle": "always", 20 | "curly": ["error", "all"], 21 | "import/order": [ 22 | "error", 23 | { 24 | "groups": [ 25 | "builtin", 26 | "external", 27 | "internal", 28 | "parent", 29 | "sibling", 30 | "index" 31 | ] 32 | } 33 | ], 34 | "no-use-before-define": "off", 35 | "prettier/prettier": "error", 36 | "lines-between-class-members": ["error", "never"], 37 | "react/jsx-wrap-multilines": [ 38 | "error", 39 | { "declaration": false, "assignment": false } 40 | ], 41 | "react/jsx-one-expression-per-line": "off", 42 | "react/default-props-match-prop-types": [ 43 | "error", 44 | { "allowRequiredDefaults": true } 45 | ], 46 | "react/jsx-filename-extension": [ 47 | "error", 48 | { "extensions": [".js", ".jsx"] } 49 | ], 50 | "react/require-default-props": "off" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | fastlane/README* 39 | 40 | # Directories # 41 | ############### 42 | .gradle 43 | coverage 44 | build 45 | node_modules 46 | xcuserdata 47 | android/app/src/main/res 48 | log.android 49 | .idea 50 | webpack-assets.json 51 | *report* 52 | 53 | # Files # 54 | ######### 55 | yarn.lock 56 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.yml 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 React Native Material Design 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Motion 2 | Change your application from the left one to the right one! Animate it! **Easily!** [Animated Transition Article](http://bit.ly/animated-transition) or [Animated Graph Article](http://bit.ly/animated-graph) 3 | 4 | 5 | 6 | ### Getting Started 7 | ```bash 8 | $ yarn add react-native-motion 9 | ``` 10 | 11 | ### Usage of SharedElement 12 | We need to specify source and destination for shared element. This library then will move the shared element from source position to destination position. 13 | 14 | ```js 15 | class Main extends Component { 16 | render() { 17 | return ( 18 | 19 | 20 | 21 | ); 22 | } 23 | } 24 | ``` 25 | ```js 26 | // List items page with source of SharedElement 27 | import { SharedElement } from 'react-native-motion'; 28 | 29 | class ListPage extends Component { 30 | render() { 31 | return ( 32 | 33 | {listItemNode} 34 | 35 | ); 36 | } 37 | } 38 | ``` 39 | ```js 40 | // Detail page with a destination shared element 41 | import { SharedElement } from 'react-native-motion'; 42 | 43 | class DetailPage extends Component { 44 | render() { 45 | return ( 46 | 47 | {listItemNode} 48 | 49 | ); 50 | } 51 | } 52 | ``` 53 | ### Playground for **react-native-motion** library 54 | 55 | - [react-native-motion-playground](https://github.com/xotahal/react-native-motion-playground) repository 56 | - [expo app](https://expo.io/@xotahal/react-native-motion-example) 57 | 58 | ## Author 59 | - [Let me help you](http://link.xotahal.cz/research) with animations in React Native 🤙 60 | - [Facebook Group](https://www.facebook.com/groups/react.native.motion/) about animation in React Native 61 | - [Publication](https://medium.com/react-native-motion) about animation in React Native 🚗 62 | - Personal [Medium Account](https://medium.com/@xotahal) about programming 😍 63 | - Subscribe a [blog](https://blog.xotahal.cz) 📝 64 | - Follow me on [Twitter](http://bit.ly/t-xotahal) 🐦 65 | 66 | 67 | | Jiri Otahal | 68 | | -------------------------------------------------------------------------------------------------------------------------------------- | 69 | | [](http://bit.ly/t-xotahal) | 70 | 71 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Shared element 2 | export { default as SharedElement } from './src/SharedElement'; 3 | export { default as SharedElementRenderer } from './src/SharedElementRenderer'; 4 | // Attentions 5 | export { default as Shake } from './src/Shake'; 6 | // Available Animations 7 | export { default as AnimatedNumber } from './src/AnimatedNumber'; 8 | export { default as AnimatedText } from './src/AnimatedText'; 9 | export { default as ScaleAndOpacity } from './src/ScaleAndOpacity'; 10 | export { default as Scale } from './src/Scale'; 11 | export { default as TranslateYAndOpacity } from './src/TranslateYAndOpacity'; 12 | export { default as TranslateY } from './src/TranslateY'; 13 | export { default as TranslateX } from './src/TranslateX'; 14 | export { default as TranslateXY } from './src/TranslateXY'; 15 | export { default as Opacity } from './src/Opacity'; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-motion", 3 | "version": "0.0.1", 4 | "description": "React Native Motion", 5 | "main": "./index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch", 9 | "coverage": "rimraf coverage && jest --coverage", 10 | "lint": "eslint ./" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/xotahal/react-native-motion" 15 | }, 16 | "jest": { 17 | "preset": "react-native", 18 | "coverageDirectory": "./coverage/", 19 | "collectCoverage": true, 20 | "testMatch": [ 21 | "/__tests__/**/*.test.js?(x)", 22 | "/src/**/*.test.js" 23 | ] 24 | }, 25 | "keywords": [ 26 | "react-native", 27 | "animation", 28 | "motion", 29 | "ui", 30 | "ux", 31 | "ios", 32 | "android" 33 | ], 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/xotahal/react-native-motion/issues" 37 | }, 38 | "homepage": "https://github.com/xotahal/react-native-motion", 39 | "peerDependencies": { 40 | "react": "*", 41 | "react-native": "*" 42 | }, 43 | "dependencies": { 44 | "prop-types": "^15.5.10" 45 | }, 46 | "devDependencies": { 47 | "babel-core": "^6.26.3", 48 | "babel-eslint": "10.0.1", 49 | "babel-polyfill": "^6.23.0", 50 | "babel-preset-es2015": "^6.24.1", 51 | "babel-preset-react-native": "4.0.1", 52 | "eslint": "5.8.0", 53 | "eslint-config-airbnb": "17.1.0", 54 | "eslint-config-prettier": "^3.1.0", 55 | "eslint-plugin-import": "^2.14.0", 56 | "eslint-plugin-jsx-a11y": "6.1.2", 57 | "eslint-plugin-prettier": "^3.0.0", 58 | "eslint-plugin-react": "^7.11.1", 59 | "eslint-plugin-react-native": "^3.5.0", 60 | "prettier": "^1.14.3", 61 | "react": "16.4.1", 62 | "react-native": "^0.57.4", 63 | "semantic-release": "^15.13.3" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branch: 'master', 3 | }; 4 | -------------------------------------------------------------------------------- /src/AnimatedNumber.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Animated, InteractionManager } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types 7 | value: PropTypes.number, 8 | initialValue: PropTypes.number, 9 | animateOnDidMount: PropTypes.bool, 10 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types 11 | }; 12 | const defaultProps = { 13 | type: 'timing', 14 | value: 0, 15 | initialValue: null, 16 | animateOnDidMount: false, 17 | useNativeDriver: false, 18 | }; 19 | /** 20 | */ 21 | class AnimatedNumber extends React.PureComponent { 22 | constructor(props) { 23 | super(props); 24 | const { initialValue } = props; 25 | const firstValue = initialValue !== null ? initialValue : props.value; 26 | const animatedValue = new Animated.Value(firstValue); 27 | animatedValue.addListener(this.onValueChanged); 28 | 29 | this.state = { 30 | animatedValue, 31 | value: firstValue, 32 | }; 33 | } 34 | componentDidMount() { 35 | const { animateOnDidMount } = this.props; 36 | if (animateOnDidMount) { 37 | InteractionManager.runAfterInteractions().then(() => { 38 | this.move(this.props); 39 | }); 40 | } 41 | } 42 | componentDidUpdate(prevProps) { 43 | const { value } = this.props; 44 | 45 | if (prevProps.value !== value) { 46 | this.move(this.props); 47 | } 48 | } 49 | onValueChanged = e => { 50 | this.setState({ 51 | value: e.value, 52 | }); 53 | }; 54 | move = props => { 55 | const { value, style, type, ...rest } = props; 56 | const { animatedValue } = this.state; 57 | 58 | Animated[type](animatedValue, { 59 | toValue: value, 60 | ...rest, 61 | }).start(); 62 | }; 63 | render() { 64 | const { renderValue } = this.props; 65 | const { value } = this.state; 66 | 67 | return renderValue(value); 68 | } 69 | } 70 | 71 | AnimatedNumber.propTypes = propTypes; 72 | AnimatedNumber.defaultProps = defaultProps; 73 | 74 | export default AnimatedNumber; 75 | -------------------------------------------------------------------------------- /src/AnimatedText.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Animated } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types 7 | duration: PropTypes.number, // eslint-disable-line react/no-unused-prop-types 8 | }; 9 | const defaultProps = { 10 | type: 'timing', 11 | duration: 500, 12 | }; 13 | /** 14 | */ 15 | class AnimatedText extends React.PureComponent { 16 | constructor(props) { 17 | super(props); 18 | 19 | const animatedValue = new Animated.Value(1); 20 | animatedValue.addListener(this.onValueChanged); 21 | 22 | const { value } = props; 23 | 24 | this.state = { 25 | animatedValue, 26 | value, 27 | originValue: value, 28 | originLength: value.length, 29 | }; 30 | } 31 | componentDidUpdate(prevProps) { 32 | const { value } = this.props; 33 | 34 | if (prevProps.value !== value) { 35 | this.change(this.props); 36 | } 37 | } 38 | onValueChanged = e => { 39 | const { originValue, originLength } = this.state; 40 | 41 | const sub = Math.ceil(originLength * e.value); 42 | 43 | this.setState({ 44 | value: originValue.substr(0, Math.max(sub, 1)), 45 | }); 46 | }; 47 | change = props => { 48 | const { value, style, type, ...rest } = props; 49 | const { animatedValue } = this.state; 50 | 51 | Animated[type](animatedValue, { 52 | toValue: 0, 53 | ...rest, 54 | }).start(() => { 55 | this.setState({ 56 | originValue: value, 57 | originLength: value.length, 58 | }); 59 | 60 | Animated[type](animatedValue, { 61 | toValue: 1, 62 | ...rest, 63 | }).start(); 64 | }); 65 | }; 66 | render() { 67 | const { renderValue } = this.props; 68 | const { value } = this.state; 69 | 70 | return renderValue(value); 71 | } 72 | } 73 | 74 | AnimatedText.propTypes = propTypes; 75 | AnimatedText.defaultProps = defaultProps; 76 | 77 | export default AnimatedText; 78 | -------------------------------------------------------------------------------- /src/Opacity.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, 7 | }; 8 | const defaultProps = { 9 | type: 'timing', 10 | }; 11 | 12 | class Opacity extends PureComponent { 13 | constructor(props) { 14 | super(props); 15 | 16 | const { value } = props; 17 | 18 | this.state = { 19 | opacityValue: new Animated.Value(value), 20 | }; 21 | } 22 | componentDidUpdate(prevProps) { 23 | const { value } = this.props; 24 | 25 | if (prevProps.value !== value) { 26 | this.move(this.props); 27 | } 28 | } 29 | move = props => { 30 | const { value, style, type, ...rest } = props; 31 | const { opacityValue } = this.state; 32 | 33 | Animated[type](opacityValue, { 34 | toValue: value, 35 | ...rest, 36 | }).start(); 37 | }; 38 | render() { 39 | const { style, children, ...rest } = this.props; 40 | const { opacityValue } = this.state; 41 | 42 | const animatedStyle = { 43 | opacity: opacityValue, 44 | }; 45 | 46 | return ( 47 | 48 | {children} 49 | 50 | ); 51 | } 52 | } 53 | 54 | Opacity.propTypes = propTypes; 55 | Opacity.defaultProps = defaultProps; 56 | 57 | export default Opacity; 58 | -------------------------------------------------------------------------------- /src/Scale.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated, InteractionManager } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types 7 | animateOnDidMount: PropTypes.bool, 8 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types 9 | }; 10 | const defaultProps = { 11 | type: 'timing', 12 | animateOnDidMount: false, 13 | useNativeDriver: true, 14 | }; 15 | 16 | class Scale extends PureComponent { 17 | constructor(props) { 18 | super(props); 19 | 20 | const { value, initValue } = props; 21 | 22 | this.interaction = null; 23 | 24 | this.state = { 25 | scaleValue: new Animated.Value(initValue || value), 26 | }; 27 | } 28 | componentDidMount() { 29 | const { animateOnDidMount } = this.props; 30 | 31 | if (animateOnDidMount) { 32 | InteractionManager.runAfterInteractions().then(() => { 33 | this.move(this.props); 34 | }); 35 | } 36 | } 37 | componentDidUpdate(prevProps) { 38 | const { value, runAfterInteractions } = this.props; 39 | 40 | if (prevProps.value !== value) { 41 | if (this.interaction) { 42 | this.interaction.cancel(); 43 | } 44 | 45 | if (runAfterInteractions) { 46 | this.interaction = InteractionManager.runAfterInteractions(() => { 47 | this.interaction = null; 48 | this.move(this.props); 49 | }); 50 | } else { 51 | this.move(this.props); 52 | } 53 | } 54 | } 55 | move = props => { 56 | const { value, type, onShowComplete, ...rest } = props; 57 | const { scaleValue } = this.state; 58 | 59 | Animated[type](scaleValue, { 60 | toValue: value, 61 | ...rest, 62 | }).start(() => { 63 | if (onShowComplete) { 64 | onShowComplete(props); 65 | } 66 | }); 67 | }; 68 | render() { 69 | const { style, children } = this.props; 70 | const { scaleValue } = this.state; 71 | 72 | const animatedStyle = { 73 | transform: [{ scale: scaleValue }], 74 | }; 75 | 76 | return ( 77 | {children} 78 | ); 79 | } 80 | } 81 | 82 | Scale.propTypes = propTypes; 83 | Scale.defaultProps = defaultProps; 84 | 85 | export default Scale; 86 | -------------------------------------------------------------------------------- /src/ScaleAndOpacity.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated, InteractionManager } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types 7 | opacityMin: PropTypes.number, 8 | scaleMin: PropTypes.number, 9 | duration: PropTypes.number, // eslint-disable-line react/no-unused-prop-types 10 | animateOnDidMount: PropTypes.bool, 11 | isHidden: PropTypes.bool, 12 | delay: PropTypes.number, // eslint-disable-line react/no-unused-prop-types 13 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types 14 | }; 15 | const defaultProps = { 16 | type: 'timing', 17 | opacityMin: 0, 18 | scaleMin: 0.8, 19 | duration: 500, 20 | animateOnDidMount: false, 21 | isHidden: true, 22 | delay: 0, 23 | useNativeDriver: false, 24 | }; 25 | 26 | class ScaleAndOpacity extends PureComponent { 27 | constructor(props) { 28 | super(props); 29 | 30 | const { opacityMin, scaleMin, isHidden } = props; 31 | 32 | this.state = { 33 | opacityValue: new Animated.Value(isHidden ? opacityMin : 1), 34 | scaleValue: new Animated.Value(isHidden ? scaleMin : 1), 35 | }; 36 | } 37 | componentDidMount() { 38 | const { animateOnDidMount } = this.props; 39 | 40 | if (animateOnDidMount) { 41 | InteractionManager.runAfterInteractions().then(() => { 42 | this.show(this.props); 43 | }); 44 | } 45 | } 46 | componentDidUpdate(prevProps) { 47 | const { isHidden } = this.props; 48 | 49 | if (!prevProps.isHidden && isHidden) { 50 | this.hide(this.props); 51 | } 52 | if (prevProps.isHidden && !isHidden) { 53 | this.show(this.props); 54 | } 55 | } 56 | hide = props => { 57 | const { scaleValue, opacityValue } = this.state; 58 | const { type, scaleMin, opacityMin, onHideComplete, ...rest } = props; 59 | 60 | Animated.parallel([ 61 | Animated[type](scaleValue, { 62 | toValue: scaleMin, 63 | ...rest, 64 | }), 65 | Animated[type](opacityValue, { 66 | toValue: opacityMin, 67 | ...rest, 68 | }), 69 | ]).start(() => { 70 | if (onHideComplete) { 71 | onHideComplete(props); 72 | } 73 | }); 74 | }; 75 | show = props => { 76 | const { scaleValue, opacityValue } = this.state; 77 | const { type, onShowComplete, ...rest } = props; 78 | 79 | Animated.parallel([ 80 | Animated[type](scaleValue, { 81 | toValue: 1, 82 | ...rest, 83 | }), 84 | Animated[type](opacityValue, { 85 | toValue: 1, 86 | ...rest, 87 | }), 88 | ]).start(() => { 89 | if (onShowComplete) { 90 | onShowComplete(props); 91 | } 92 | }); 93 | }; 94 | render() { 95 | const { style, children } = this.props; 96 | const { opacityValue, scaleValue } = this.state; 97 | 98 | const animatedStyle = { 99 | opacity: opacityValue, 100 | transform: [ 101 | { 102 | scale: scaleValue, 103 | }, 104 | ], 105 | }; 106 | 107 | return ( 108 | {children} 109 | ); 110 | } 111 | } 112 | 113 | ScaleAndOpacity.propTypes = propTypes; 114 | ScaleAndOpacity.defaultProps = defaultProps; 115 | 116 | export default ScaleAndOpacity; 117 | -------------------------------------------------------------------------------- /src/Shake.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, 7 | }; 8 | const defaultProps = { 9 | type: 'timing', 10 | }; 11 | 12 | class Shake extends PureComponent { 13 | constructor(props) { 14 | super(props); 15 | 16 | this.currentValue = 0; 17 | 18 | this.state = { 19 | animatedValue: new Animated.Value(this.currentValue), 20 | }; 21 | } 22 | componentDidUpdate(prevProps) { 23 | const { value } = this.props; 24 | 25 | // Perform the shake if our `value` prop has been changed and is 26 | // being changed to a truthy value. 27 | if (prevProps.value !== value && !!value) { 28 | this.move(this.props); 29 | } 30 | } 31 | move = props => { 32 | const { value, style, type, ...rest } = props; 33 | const { animatedValue } = this.state; 34 | 35 | Animated[type](animatedValue, { 36 | toValue: this.currentValue === 0 ? 1 : 0, 37 | ...rest, 38 | }).start(() => { 39 | this.currentValue = this.currentValue === 0 ? 1 : 0; 40 | }); 41 | }; 42 | render() { 43 | const { animatedValue } = this.state; 44 | const { style, children, ...rest } = this.props; 45 | 46 | const translateX = animatedValue.interpolate({ 47 | inputRange: [0, 0.2, 0.4, 0.6, 0.8, 1], 48 | outputRange: [0, -10, 10, -10, 10, 0], 49 | }); 50 | 51 | const animatedStyle = { 52 | transform: [{ translateX }], 53 | }; 54 | 55 | return ( 56 | 57 | {children} 58 | 59 | ); 60 | } 61 | } 62 | 63 | Shake.propTypes = propTypes; 64 | Shake.defaultProps = defaultProps; 65 | 66 | export default Shake; 67 | -------------------------------------------------------------------------------- /src/SharedElement.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const propTypes = { 5 | // One of these are required 6 | id: PropTypes.string, // eslint-disable-line react/no-unused-prop-types 7 | sourceId: PropTypes.string, 8 | // Set to true if we want to start with animation immediately when a 9 | // destination element is mounted 10 | startOnDestinationDidMount: PropTypes.bool, 11 | // Set to true if we want to start with animation immediately when a 12 | // destination element is unmounted 13 | startOnDestinationWillUnmount: PropTypes.bool, 14 | }; 15 | const defaultProps = { 16 | startOnDestinationDidMount: false, 17 | startOnDestinationWillUnmount: false, 18 | }; 19 | const contextTypes = { 20 | moveSharedElement: PropTypes.func.isRequired, 21 | }; 22 | 23 | const elements = {}; 24 | // Test if the shred element is destination or source 25 | const isDestination = props => !!props.sourceId; 26 | // Destination element has id as a sourceId and source element has an id as an 27 | // id prop 28 | const getKey = props => props.id || props.sourceId; 29 | // Create a element with provided id 30 | const initElement = props => { 31 | const { id, sourceId } = props; 32 | const key = id || sourceId; 33 | 34 | elements[key] = elements[key] || { 35 | id: key, 36 | // source element of this shared element 37 | source: { 38 | // we want to keep ref to measure position on screen 39 | ref: null, 40 | // to be able fire events 41 | props: null, 42 | // last known position of source - when we lost ref (element was unmounted) 43 | // we are still able to show animation even when the element is already 44 | // doesn't exist, because we actualy will create new one for animation 45 | // anyway, if the source element was part of scrollview for example and 46 | // his position was changed since last measure, it will start in bad 47 | // position - we need to solve this somehow 48 | position: null, 49 | }, 50 | // destination element of this shared element 51 | destination: { 52 | // it's the same like source, only for destination 53 | ref: null, 54 | props: null, 55 | position: null, 56 | }, 57 | }; 58 | }; 59 | // To store props of source and destination. We want to fire event even for 60 | // source element if the destination was animated. 61 | const setProps = props => { 62 | const key = getKey(props); 63 | 64 | if (isDestination(props)) { 65 | elements[key].destination.props = props; 66 | } else { 67 | elements[key].source.props = props; 68 | } 69 | }; 70 | // To store references of elements. 71 | const setRef = (props, node) => { 72 | const key = getKey(props); 73 | 74 | if (isDestination(props)) { 75 | elements[key].destination.ref = node; 76 | } else { 77 | elements[key].source.ref = node; 78 | } 79 | }; 80 | // Node which will be animated. We are trying to observe element as a clone of 81 | // children. But if we want to do something with that children (for example 82 | // set an opacity once it is moved, it is better to provide getNode) 83 | const setNode = props => { 84 | const key = getKey(props); 85 | 86 | // if we already have a node 87 | if (elements[key].node) { 88 | return; 89 | } 90 | 91 | if (props.getNode) { 92 | elements[key].node = props.getNode(props); 93 | } else { 94 | // this node will be animated 95 | elements[key].node = React.cloneElement(props.children); 96 | } 97 | }; 98 | const setSourcePosition = (props, position) => { 99 | if (!position) { 100 | return; 101 | } 102 | 103 | set(props, 'waitingForSource', false); 104 | 105 | const key = getKey(props); 106 | elements[key].source.position = position; 107 | }; 108 | const setDestinationPosition = (props, position) => { 109 | if (!position) { 110 | return; 111 | } 112 | 113 | set(props, 'waitingForDestination', false); 114 | 115 | const key = getKey(props); 116 | elements[key].destination.position = position; 117 | }; 118 | const set = (props, propName, value) => { 119 | const key = getKey(props); 120 | elements[key][propName] = value; 121 | }; 122 | const get = (props, propName) => { 123 | const key = getKey(props); 124 | return elements[key][propName]; 125 | }; 126 | const getElement = props => { 127 | const key = getKey(props); 128 | return elements[key]; 129 | }; 130 | const getAnimationConfig = props => { 131 | // get only animation config 132 | const { 133 | id, 134 | sourceId, 135 | children, 136 | onMoveToSourceWillStart, 137 | onMoveToSourceDidFinish, 138 | onMoveToDestinationWillStart, 139 | onMoveToDestinationDidFinish, 140 | startOnDestinationDidMount, 141 | startOnDestinationWillUnmount, 142 | // should contains only animation config 143 | ...animationConfig 144 | } = props; 145 | 146 | return animationConfig; 147 | }; 148 | const getAnimationConfigOfSource = props => { 149 | const element = getElement(props); 150 | return getAnimationConfig(element.source.props); 151 | }; 152 | const getAnimationConfigOfDestination = props => { 153 | const element = getElement(props); 154 | return getAnimationConfig(element.destination.props); 155 | }; 156 | const fireEvent = (props, name) => { 157 | if (props[name]) { 158 | props[name](); 159 | } 160 | }; 161 | 162 | class SharedElement extends PureComponent { 163 | constructor(props) { 164 | super(props); 165 | // just create an object for this shared element 166 | initElement(props); 167 | } 168 | componentDidMount() { 169 | setProps(this.props); 170 | } 171 | componentDidUpdate() { 172 | setProps(this.props); 173 | } 174 | componentWillUnmount() { 175 | const { startOnDestinationWillUnmount } = this.props; 176 | 177 | if (startOnDestinationWillUnmount && isDestination(this.props)) { 178 | this.moveToSource(); 179 | } 180 | } 181 | setRef = node => { 182 | setRef(this.props, node); 183 | 184 | const { children } = this.props; 185 | // Call the original ref, if there is any 186 | const { ref } = children; 187 | if (typeof ref === 'function') { 188 | ref(node); 189 | } 190 | }; 191 | measure = (ref, callback) => { 192 | if (!ref) { 193 | callback(null); 194 | } 195 | 196 | ref.measure((x, y, width, height, pageX, pageY) => { 197 | const position = { x, y, width, height, pageX, pageY }; 198 | callback(position); 199 | }); 200 | }; 201 | moveToDestination = () => { 202 | const { moveSharedElement } = this.context; 203 | 204 | setNode(this.props); 205 | const element = getElement(this.props); 206 | 207 | if (!element.destination.position) { 208 | set(this.props, 'waitingForDestination', true); 209 | } else { 210 | moveSharedElement({ 211 | animationConfig: getAnimationConfigOfSource(this.props), 212 | element: getElement(this.props), 213 | onMoveWillStart: this.onMoveToDestinationWillStart, 214 | onMoveDidComplete: this.onMoveToDestinationDidFinish, 215 | }); 216 | } 217 | }; 218 | moveToSource = () => { 219 | const { moveSharedElement } = this.context; 220 | 221 | setNode(this.props); 222 | const element = getElement(this.props); 223 | 224 | if (!element.source.position) { 225 | set(this.props, 'waitingForSource', true); 226 | } else { 227 | moveSharedElement({ 228 | animationConfig: getAnimationConfigOfDestination(this.props), 229 | element: { 230 | ...element, 231 | destination: element.source, 232 | source: element.destination, 233 | }, 234 | onMoveWillStart: this.onMoveToSourceWillStart, 235 | onMoveDidComplete: this.onMoveToSourceDidFinish, 236 | }); 237 | } 238 | }; 239 | onMoveToDestinationWillStart = () => { 240 | const { source, destination } = getElement(this.props); 241 | 242 | fireEvent(source.props, 'onMoveToDestinationWillStart'); 243 | fireEvent(destination.props, 'onMoveToDestinationWillStart'); 244 | }; 245 | onMoveToDestinationDidFinish = () => { 246 | const { source, destination } = getElement(this.props); 247 | 248 | // will get the node again later when we need it - we need always current 249 | // node beucase it could be changed during 250 | set(this.props, 'node', null); 251 | 252 | fireEvent(source.props, 'onMoveToDestinationDidFinish'); 253 | fireEvent(destination.props, 'onMoveToDestinationDidFinish'); 254 | }; 255 | onMoveToSourceWillStart = () => { 256 | const { source, destination } = getElement(this.props); 257 | 258 | fireEvent(destination.props, 'onMoveToSourceWillStart'); 259 | fireEvent(source.props, 'onMoveToSourceWillStart'); 260 | }; 261 | onMoveToSourceDidFinish = () => { 262 | const { source, destination } = getElement(this.props); 263 | 264 | // will get the node again later when we need it - we need always current 265 | // node beucase it could be changed during 266 | set(this.props, 'node', null); 267 | 268 | fireEvent(destination.props, 'onMoveToSourceDidFinish'); 269 | fireEvent(source.props, 'onMoveToSourceDidFinish'); 270 | }; 271 | onSourceLayout = data => { 272 | const element = getElement(this.props); 273 | 274 | const { ref } = element.source; 275 | this.measure(ref, position => { 276 | const startAnimation = get(this.props, 'waitingForSource'); 277 | setSourcePosition(this.props, position); 278 | 279 | // if the user wanted to move to destination but there wasn't source yet 280 | if (startAnimation) { 281 | this.moveToSource(); 282 | } 283 | }); 284 | 285 | // Call original if any 286 | const { children } = this.props; 287 | const { onLayout } = children; 288 | if (typeof onLayout === 'function') { 289 | onLayout(data); 290 | } 291 | }; 292 | onDestinationLayout = data => { 293 | const { startOnDestinationDidMount } = this.props; 294 | const { source, destination } = getElement(this.props); 295 | 296 | this.measure(source.ref, sourcePosition => { 297 | setSourcePosition(this.props, sourcePosition); 298 | 299 | this.measure(destination.ref, destinationPosition => { 300 | const startAnimation = get(this.props, 'waitingForDestination'); 301 | setDestinationPosition(this.props, destinationPosition); 302 | 303 | if (startAnimation || startOnDestinationDidMount) { 304 | this.moveToDestination(); 305 | } 306 | }); 307 | }); 308 | 309 | // Call original if any 310 | const { children } = this.props; 311 | const { onLayout } = children; 312 | if (typeof onLayout === 'function') { 313 | onLayout(data); 314 | } 315 | }; 316 | renderSource() { 317 | const { children } = this.props; 318 | 319 | return React.cloneElement(children, { 320 | ref: this.setRef, 321 | onLayout: this.onSourceLayout, 322 | }); 323 | } 324 | renderDestination() { 325 | const { children } = this.props; 326 | 327 | return React.cloneElement(children, { 328 | ref: this.setRef, 329 | onLayout: this.onDestinationLayout, 330 | }); 331 | } 332 | render() { 333 | const { sourceId } = this.props; 334 | 335 | if (!sourceId) { 336 | return this.renderSource(); 337 | } 338 | 339 | return this.renderDestination(); 340 | } 341 | } 342 | 343 | SharedElement.propTypes = propTypes; 344 | SharedElement.defaultProps = defaultProps; 345 | SharedElement.contextTypes = contextTypes; 346 | 347 | export default SharedElement; 348 | -------------------------------------------------------------------------------- /src/SharedElementRenderer.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated, View, StyleSheet } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const childContextTypes = { 6 | moveSharedElement: PropTypes.func.isRequired, 7 | }; 8 | 9 | // We need shared element to be rendered after the whole application because it 10 | // be on the screen with position absolute and will cover everything on screen 11 | class SharedElementRenderer extends PureComponent { 12 | constructor(props) { 13 | super(props); 14 | 15 | this.isRunning = {}; 16 | this.state = { 17 | config: null, 18 | }; 19 | } 20 | getChildContext() { 21 | return { 22 | moveSharedElement: this.moveSharedElement, 23 | }; 24 | } 25 | onMoveWillStart = () => { 26 | const { config } = this.state; 27 | const { onMoveWillStart, element } = config; 28 | const { id } = element; 29 | 30 | this.isRunning[id] = true; 31 | 32 | if (onMoveWillStart) { 33 | onMoveWillStart(config); 34 | } 35 | }; 36 | onMoveDidComplete = () => { 37 | const { config } = this.state; 38 | const { onMoveDidComplete, element } = config; 39 | const { id } = element; 40 | 41 | this.isRunning[id] = false; 42 | 43 | if (onMoveDidComplete) { 44 | onMoveDidComplete(config); 45 | } 46 | 47 | this.reset(); 48 | }; 49 | reset = () => { 50 | this.setState({ config: null }); 51 | }; 52 | // This method will compute animations. Position and scale. 53 | getAnimations = config => { 54 | const { element, animationConfig } = config; 55 | const { source, destination } = element; 56 | 57 | const animations = []; 58 | let translateYValue = 0; 59 | 60 | if (!Number.isNaN(source.position.pageY)) { 61 | translateYValue = new Animated.Value(source.position.pageY); 62 | } 63 | 64 | if (source.position.pageY !== destination.position.pageY) { 65 | this.setState({ translateYValue }); 66 | 67 | animations.push( 68 | Animated.timing(translateYValue, { 69 | toValue: destination.position.pageY, 70 | useNativeDriver: true, 71 | ...animationConfig, 72 | }), 73 | ); 74 | } 75 | 76 | return animations; 77 | }; 78 | moveSharedElement = config => { 79 | const { id } = config.element; 80 | // animation was already started 81 | if (this.isRunning[id]) { 82 | return; 83 | } 84 | 85 | const animations = this.getAnimations(config); 86 | 87 | this.setState({ 88 | config, 89 | }); 90 | 91 | setTimeout(() => { 92 | this.onMoveWillStart(); 93 | Animated.parallel(animations).start(this.onMoveDidComplete); 94 | }, 0); 95 | }; 96 | renderSharedElement() { 97 | const { config, translateYValue } = this.state; 98 | const { element } = config || {}; 99 | const { source, node } = element || {}; 100 | const { position } = source || {}; 101 | const { height, width } = position || {}; 102 | 103 | if (!config) { 104 | return null; 105 | } 106 | 107 | const transform = []; 108 | 109 | if (translateYValue) { 110 | transform.push({ translateY: translateYValue }); 111 | } 112 | 113 | const animatedStyle = { 114 | height, 115 | width, 116 | transform, 117 | }; 118 | 119 | return ( 120 | 121 | 122 | {node} 123 | 124 | 125 | ); 126 | } 127 | render() { 128 | const { children } = this.props; 129 | 130 | return ( 131 | 132 | {children} 133 | {this.renderSharedElement()} 134 | 135 | ); 136 | } 137 | } 138 | 139 | const styles = StyleSheet.create({ 140 | flexContainer: { 141 | flex: 1, 142 | }, 143 | container: { 144 | ...StyleSheet.absoluteFillObject, 145 | backgroundColor: 'transparent', 146 | }, 147 | positionContainer: { 148 | position: 'absolute', 149 | }, 150 | }); 151 | 152 | SharedElementRenderer.childContextTypes = childContextTypes; 153 | 154 | export default SharedElementRenderer; 155 | -------------------------------------------------------------------------------- /src/TranslateX.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated, InteractionManager } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, 7 | value: PropTypes.number, 8 | initialValue: PropTypes.number, 9 | animateOnDidMount: PropTypes.bool, 10 | useNativeDriver: PropTypes.bool, 11 | }; 12 | const defaultProps = { 13 | type: 'timing', 14 | value: 0, 15 | initialValue: 0, 16 | animateOnDidMount: false, 17 | useNativeDriver: true, 18 | }; 19 | 20 | class TranslateX extends PureComponent { 21 | constructor(props) { 22 | super(props); 23 | 24 | const { value, initialValue } = props; 25 | 26 | this.state = { 27 | translateXValue: new Animated.Value(initialValue || value), 28 | }; 29 | } 30 | componentDidMount() { 31 | const { animateOnDidMount, value } = this.props; 32 | if (animateOnDidMount) { 33 | InteractionManager.runAfterInteractions().then(() => { 34 | this.move(value); 35 | }); 36 | } 37 | } 38 | componentDidUpdate(prevProps) { 39 | const { value } = this.props; 40 | 41 | if (prevProps.value !== value) { 42 | this.move(value); 43 | } 44 | } 45 | move = toValue => { 46 | const { style, onMoveDidFinish, type, ...rest } = this.props; 47 | const { translateXValue } = this.state; 48 | 49 | Animated[type](translateXValue, { 50 | toValue, 51 | ...rest, 52 | }).start(onMoveDidFinish); 53 | }; 54 | render() { 55 | const { style, children } = this.props; 56 | const { translateXValue } = this.state; 57 | 58 | const animatedStyle = { 59 | transform: [{ translateX: translateXValue }], 60 | }; 61 | 62 | return ( 63 | {children} 64 | ); 65 | } 66 | } 67 | 68 | TranslateX.propTypes = propTypes; 69 | TranslateX.defaultProps = defaultProps; 70 | 71 | export default TranslateX; 72 | -------------------------------------------------------------------------------- /src/TranslateXY.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, // eslint-disable-line react/no-unused-prop-types 7 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types 8 | x: PropTypes.number, 9 | y: PropTypes.number, 10 | }; 11 | const defaultProps = { 12 | type: 'timing', 13 | useNativeDriver: true, 14 | x: 0, 15 | y: 0, 16 | }; 17 | 18 | class TranslateXY extends PureComponent { 19 | constructor(props) { 20 | super(props); 21 | 22 | const { x, y } = props; 23 | 24 | this.state = { 25 | translateValue: new Animated.ValueXY({ x, y }), 26 | }; 27 | } 28 | componentDidUpdate(prevProps) { 29 | const { x, y } = this.props; 30 | 31 | if (prevProps.x !== x || prevProps.y !== y) { 32 | this.move(this.props); 33 | } 34 | } 35 | move = props => { 36 | const { translateValue } = this.state; 37 | const { style, type, x, y, ...rest } = props; 38 | 39 | Animated[type](translateValue, { 40 | toValue: { x, y }, 41 | ...rest, 42 | }).start(); 43 | }; 44 | render() { 45 | const { translateValue } = this.state; 46 | const { style, children } = this.props; 47 | 48 | const animatedStyle = { 49 | transform: translateValue.getTranslateTransform(), 50 | }; 51 | 52 | return ( 53 | {children} 54 | ); 55 | } 56 | } 57 | 58 | TranslateXY.propTypes = propTypes; 59 | TranslateXY.defaultProps = defaultProps; 60 | 61 | export default TranslateXY; 62 | -------------------------------------------------------------------------------- /src/TranslateY.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated, InteractionManager } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | type: PropTypes.string, 7 | value: PropTypes.number, 8 | initialValue: PropTypes.number, 9 | startOnDidMount: PropTypes.bool, 10 | useNativeDriver: PropTypes.bool, 11 | }; 12 | const defaultProps = { 13 | type: 'timing', 14 | value: 0, 15 | initialValue: null, 16 | startOnDidMount: false, 17 | useNativeDriver: true, 18 | }; 19 | 20 | class TranslateY extends PureComponent { 21 | constructor(props) { 22 | super(props); 23 | 24 | const { value, initialValue } = props; 25 | 26 | this.state = { 27 | translateYValue: new Animated.Value( 28 | initialValue !== null ? initialValue : value, 29 | ), 30 | }; 31 | } 32 | componentDidMount() { 33 | const { startOnDidMount, value } = this.props; 34 | if (startOnDidMount) { 35 | InteractionManager.runAfterInteractions().then(() => { 36 | this.move(value); 37 | }); 38 | } 39 | } 40 | componentDidUpdate(prevProps) { 41 | const { value } = this.props; 42 | 43 | if (prevProps.value !== value) { 44 | this.move(value); 45 | } 46 | } 47 | move = toValue => { 48 | const { style, type, pointerEvents, ...rest } = this.props; 49 | const { translateYValue } = this.state; 50 | 51 | Animated[type](translateYValue, { 52 | toValue, 53 | ...rest, 54 | }).start(); 55 | }; 56 | render() { 57 | const { style, children, pointerEvents } = this.props; 58 | const { translateYValue } = this.state; 59 | 60 | const animatedStyle = { 61 | transform: [{ translateY: translateYValue }], 62 | }; 63 | 64 | return ( 65 | 69 | {children} 70 | 71 | ); 72 | } 73 | } 74 | 75 | TranslateY.propTypes = propTypes; 76 | TranslateY.defaultProps = defaultProps; 77 | 78 | export default TranslateY; 79 | -------------------------------------------------------------------------------- /src/TranslateYAndOpacity.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Animated, InteractionManager } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const propTypes = { 6 | opacityMin: PropTypes.number, 7 | translateYMin: PropTypes.number, 8 | duration: PropTypes.number, // eslint-disable-line react/no-unused-prop-types 9 | animateOnDidMount: PropTypes.bool, 10 | delay: PropTypes.number, // eslint-disable-line react/no-unused-prop-types 11 | useNativeDriver: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types 12 | }; 13 | const defaultProps = { 14 | opacityMin: 0, 15 | translateYMin: -4, 16 | duration: 500, 17 | animateOnDidMount: false, 18 | delay: 0, 19 | useNativeDriver: false, 20 | }; 21 | 22 | class TranslateYAndOpacity extends PureComponent { 23 | constructor(props) { 24 | super(props); 25 | 26 | const { opacityMin, translateYMin } = props; 27 | 28 | this.state = { 29 | opacityValue: new Animated.Value(opacityMin), 30 | translateYValue: new Animated.Value(translateYMin), 31 | }; 32 | } 33 | componentDidMount() { 34 | const { animateOnDidMount } = this.props; 35 | 36 | if (animateOnDidMount) { 37 | InteractionManager.runAfterInteractions().then(() => { 38 | this.show(this.props); 39 | }); 40 | } 41 | } 42 | componentDidUpdate(prevProps) { 43 | const { isHidden } = this.props; 44 | 45 | if (!prevProps.isHidden && isHidden) { 46 | this.hide(this.props); 47 | } 48 | if (prevProps.isHidden && !isHidden) { 49 | this.show(this.props); 50 | } 51 | } 52 | show(props) { 53 | const { opacityValue, translateYValue } = this.state; 54 | const { onShowDidFinish, ...rest } = props; 55 | 56 | Animated.parallel([ 57 | Animated.timing(opacityValue, { 58 | toValue: 1, 59 | ...rest, 60 | }), 61 | Animated.timing(translateYValue, { 62 | toValue: 0, 63 | ...rest, 64 | }), 65 | ]).start(() => { 66 | if (onShowDidFinish) { 67 | onShowDidFinish(props); 68 | } 69 | }); 70 | } 71 | hide(props) { 72 | const { translateYValue, opacityValue } = this.state; 73 | const { opacityMin, translateYMin, onHideDidFinish, ...rest } = props; 74 | 75 | Animated.parallel([ 76 | Animated.timing(opacityValue, { 77 | toValue: opacityMin, 78 | ...rest, 79 | }), 80 | Animated.timing(translateYValue, { 81 | toValue: translateYMin, 82 | ...rest, 83 | }), 84 | ]).start(() => { 85 | if (onHideDidFinish) { 86 | onHideDidFinish(props); 87 | } 88 | }); 89 | } 90 | render() { 91 | const { children } = this.props; 92 | const { opacityValue, translateYValue } = this.state; 93 | 94 | const animatedStyle = { 95 | opacity: opacityValue, 96 | transform: [{ translateY: translateYValue }], 97 | }; 98 | 99 | return {children}; 100 | } 101 | } 102 | 103 | TranslateYAndOpacity.propTypes = propTypes; 104 | TranslateYAndOpacity.defaultProps = defaultProps; 105 | 106 | export default TranslateYAndOpacity; 107 | --------------------------------------------------------------------------------