├── .gitignore
├── Example.gif
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
└── src
├── index.js
└── utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 |
--------------------------------------------------------------------------------
/Example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EricPKerr/react-native-animated-styles/ecc1d84522bd4022701de135a7a2b4833501c709/Example.gif
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Eric Kerr
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-animated-styles
2 |
3 | Easily animate react-native components between two styles. Similar concept to adding and removing a CSS class to animate HTML Elements for the web.
4 |
5 | # Install
6 |
7 | `npm install --save react-native-animated-styles`
8 |
9 | # Properties
10 |
11 | ```
12 |
20 | ```
21 |
22 | # Example Usage
23 |
24 | ```
25 | import AnimatedStyles from 'react-native-animated-styles';
26 | import { useEffect, useState } from 'react';
27 |
28 | const MyComponent = () => {
29 | const [ active, setActive ] = useState(true);
30 |
31 | useEffect(() => {
32 | setInterval(() => {
33 | setActive((active) => !active);
34 | }, 3000);
35 | }, []);
36 |
37 | return (
38 |
39 |
44 |
45 | )
46 | };
47 |
48 | const styles = StyleSheet.create({
49 | wrap: {
50 | flex: 1,
51 | alignItems: 'center',
52 | justifyContent: 'center'
53 | },
54 |
55 | box: {
56 | backgroundColor: 'rgb(73, 0, 9)', // normalizes colors
57 | width: 80,
58 | height: 80
59 | },
60 |
61 | boxActive: {
62 | width: 100,
63 | height: 100,
64 | backgroundColor: '#AC0E28',
65 | transform: [
66 | { rotate: '45deg' } // uses default values for transform properties
67 | ]
68 | }
69 | });
70 | ```
71 |
72 | 
73 |
74 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-animated-styles",
3 | "version": "0.9.0",
4 | "description": "Easily animate components between two styles",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/ericpkerr/react-native-animated-styles.git"
12 | },
13 | "keywords": [
14 | "react-native",
15 | "animation",
16 | "animate-styles",
17 | "react",
18 | "mobile",
19 | "react-component",
20 | "react-native-component"
21 | ],
22 | "author": {
23 | "name": "Eric Kerr",
24 | "email": "EricPKerr@gmail.com"
25 | },
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/ericpkerr/react-native-animated-styles/issues"
29 | },
30 | "homepage": "https://github.com/ericpkerr/react-native-animated-styles#readme",
31 | "devDependencies": {
32 | "babel": "^6.23.0",
33 | "babel-eslint": "^10.1.0",
34 | "babel-jest": "^26.6.3",
35 | "babel-preset-react-native": "^4.0.1",
36 | "jest": "26.6.3",
37 | "jest-cli": "26.6.3",
38 | "jest-react-native": "18.0.0",
39 | "react": "*",
40 | "react-native": "^0.64.1",
41 | "react-test-renderer": "17.0.1"
42 | },
43 | "dependencies": {
44 | "prop-types": "^15.7.2",
45 | "tinycolor2": "^1.4.2"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, { PureComponent } from 'react';
4 | import PropTypes from 'prop-types';
5 |
6 | import {
7 | Animated,
8 | ViewPropTypes
9 | } from 'react-native';
10 |
11 | import {
12 | flattenStyle,
13 | getDefaultStyleValue,
14 | wrapTransforms
15 | } from './utils';
16 |
17 | import tinycolor from 'tinycolor2';
18 |
19 | function createComponent(WrappedComponent) {
20 | class AnimatedStylesComponent extends PureComponent {
21 | static propTypes = {
22 | style: ViewPropTypes.style,
23 | animatedStyle: ViewPropTypes.style,
24 | duration: PropTypes.number,
25 | animateInitial: PropTypes.bool,
26 | active: PropTypes.bool,
27 | useNativeDriver: PropTypes.bool
28 | };
29 |
30 | static defaultProps = {
31 | duration: 500,
32 | animateInitial: false,
33 | active: false,
34 | useNativeDriver: false
35 | };
36 |
37 | constructor(props) {
38 | super(props);
39 |
40 | this.state = {
41 | defaultStyle: this.getDefaultStyle(),
42 | active: props.active,
43 | animatedValue: new Animated.Value(props.active && !props.animateInitial ? 1 : 0)
44 | }
45 |
46 | this.state.animatedStyle = this.getAnimatedStyle();
47 | }
48 |
49 | componentDidMount() {
50 | if(this.props.animateInitial) this.animate();
51 | }
52 |
53 | componentDidUpdate(previousProps) {
54 | if ((previousProps.style !== this.props.style) ||
55 | (previousProps.animatedStyle !== this.props.animatedStyle) ||
56 | (previousProps.animateInitial !== this.props.animateInitial)
57 | ){
58 | this.setState({ defaultStyle: this.getDefaultStyle() })
59 | }
60 |
61 | if ((previousProps.style !== this.props.style) || (previousProps.animatedStyle !== this.props.animatedStyle) ){
62 | this.setState({ animatedStyle: this.getAnimatedStyle() })
63 | }
64 |
65 | if (this.props.active != this.state.active) {
66 | this.setState({
67 | active: this.props.active
68 | }, () => {
69 | this.animate();
70 | })
71 | }
72 |
73 | }
74 |
75 | getDefaultStyle() {
76 | const { style, active, animatedStyle, animateInitial } = this.props;
77 |
78 | let styles = [style];
79 |
80 | if(active && !animateInitial) styles.push(animatedStyle);
81 |
82 | return Object.assign({}, flattenStyle(styles));
83 | }
84 |
85 | getAnimatedStyle() {
86 | const { style, animatedStyle } = this.props;
87 |
88 | const start = flattenStyle(style),
89 | finish = flattenStyle(animatedStyle);
90 |
91 | let styles = {};
92 |
93 | Object.keys(finish).forEach(key => {
94 | styles[key] = this.state.animatedValue.interpolate({
95 | inputRange: [0, 1],
96 | outputRange: this.getOutputRange(key, start, finish)
97 | })
98 | });
99 |
100 | return styles;
101 | }
102 |
103 | getOutputRange(key, start, finish) {
104 | start = start.hasOwnProperty(key) ? start[key] : getDefaultStyleValue(key, start);
105 | finish = finish[key];
106 |
107 | return [
108 | this.formatValue(key, start),
109 | this.formatValue(key, finish)
110 | ]
111 | }
112 |
113 | formatValue(key, value) {
114 | if(key.toLowerCase().indexOf('color') < 0) return value;
115 | const color = tinycolor(value);
116 | return color.toRgbString();
117 | }
118 |
119 | animate() {
120 | const { active, useNativeDriver, duration } = this.props;
121 |
122 | this.state.animatedValue.stopAnimation(() => {
123 | Animated.timing(this.state.animatedValue, {
124 | toValue: active ? 1 : 0,
125 | useNativeDriver,
126 | duration
127 | }).start();
128 | });
129 | }
130 |
131 | render() {
132 | const { style, ref, children, ...props } = this.props;
133 | const { defaultStyle, animatedStyle } = this.state;
134 |
135 | return (
136 |
140 | {children}
141 |
142 | );
143 | }
144 | }
145 |
146 | return AnimatedStylesComponent;
147 | }
148 |
149 | export default {
150 | View: createComponent(Animated.View),
151 | Text: createComponent(Animated.Text),
152 | Image: createComponent(Animated.Image)
153 | }
154 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export const flattenStyle = (style) => {
4 | const flatStyle = Object.assign({}, StyleSheet.flatten(style));
5 | if (flatStyle.transform) {
6 | flatStyle.transform.forEach(transform => {
7 | const key = Object.keys(transform)[0];
8 | flatStyle[key] = transform[key];
9 | });
10 | delete flatStyle.transform;
11 | }
12 | return flatStyle;
13 | }
14 |
15 | const DIRECTIONAL_FALLBACKS = {
16 | Top: ['Vertical', ''],
17 | Bottom: ['Vertical', ''],
18 | Vertical: [''],
19 | Left: ['Horizontal', ''],
20 | Right: ['Horizontal', ''],
21 | Horizontal: [''],
22 | };
23 |
24 | const DIRECTIONAL_SUFFICES = Object.keys(DIRECTIONAL_FALLBACKS);
25 |
26 | export const getDefaultStyleValue = (key, flatStyle) => {
27 | if (key === 'backgroundColor') {
28 | return 'rgba(0,0,0,0)';
29 | }
30 | if (key === 'color' || key.indexOf('Color') !== -1) {
31 | return 'rgba(0,0,0,1)';
32 | }
33 | if (key.indexOf('rotate') === 0 || key.indexOf('skew') === 0) {
34 | return '0deg';
35 | }
36 | if (key === 'opacity' || key.indexOf('scale') === 0) {
37 | return 1;
38 | }
39 | if (key === 'fontSize') {
40 | return 14;
41 | }
42 | if (key.indexOf('margin') === 0 || key.indexOf('padding') === 0) {
43 | for (let suffix, i = 0; i < DIRECTIONAL_SUFFICES.length; i++) {
44 | suffix = DIRECTIONAL_SUFFICES[i];
45 | if (key.substr(-suffix.length) === suffix) {
46 | const prefix = key.substr(0, key.length - suffix.length);
47 | const fallbacks = DIRECTIONAL_FALLBACKS[suffix];
48 | for (let fallback, j = 0; j < fallbacks.length; j++) {
49 | fallback = prefix + fallbacks[j];
50 | if (fallback in flatStyle) {
51 | return flatStyle[fallback];
52 | }
53 | }
54 | break;
55 | }
56 | }
57 | }
58 | return 0;
59 | }
60 |
61 | // These styles need to be nested in a transform array
62 | const TRANSFORM_STYLE_PROPERTIES = [
63 | 'perspective',
64 | 'rotate',
65 | 'rotateX',
66 | 'rotateY',
67 | 'rotateZ',
68 | 'scale',
69 | 'scaleX',
70 | 'scaleY',
71 | 'skewX',
72 | 'skewY',
73 | 'translateX',
74 | 'translateY',
75 | ];
76 |
77 | // Transforms { translateX: 1 } to { transform: [{ translateX: 1 }]}
78 | export const wrapTransforms = (style) => {
79 | let wrapped = {};
80 | Object.keys(style).forEach(key => {
81 | if (TRANSFORM_STYLE_PROPERTIES.indexOf(key) !== -1) {
82 | if (!wrapped.transform) {
83 | wrapped.transform = [];
84 | }
85 | wrapped.transform.push({
86 | [key]: style[key],
87 | });
88 | } else {
89 | wrapped[key] = style[key];
90 | }
91 | });
92 | return wrapped;
93 | }
--------------------------------------------------------------------------------