Skip to content

Commit f9d41c9

Browse files
committed
Animated emoji reaction
0 parents  commit f9d41c9

File tree

9 files changed

+369
-0
lines changed

9 files changed

+369
-0
lines changed

.DS_Store

6 KB
Binary file not shown.

.gitignore

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
6+
# Runtime data
7+
pids
8+
*.pid
9+
*.seed
10+
11+
# Directory for instrumented libs generated by jscoverage/JSCover
12+
lib-cov
13+
14+
# Coverage directory used by tools like istanbul
15+
coverage
16+
17+
# nyc test coverage
18+
.nyc_output
19+
20+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21+
.grunt
22+
23+
# node-waf configuration
24+
.lock-wscript
25+
26+
# Compiled binary addons (http://nodejs.org/api/addons.html)
27+
build/Release
28+
29+
# Dependency directories
30+
node_modules
31+
jspm_packages
32+
33+
# Optional npm cache directory
34+
.npm
35+
36+
# Optional REPL history
37+
.node_repl_history
38+
.idea/

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 David Bala
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

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# react-native-animated-emoji
2+
Animated Floating Reactions like Facebook
3+
4+
### Installation
5+
6+
```sh
7+
$ npm install --save react-native-animated-emoji
8+
```
9+
or
10+
11+
```sh
12+
yarn add react-native-animated-emoji
13+
```
14+
15+
### Usage
16+
17+
```javascript
18+
import { AnimatedEmoji } from 'react-native-animated-emoji';
19+
20+
export const App = () => (
21+
<AnimatedEmoji
22+
key={emoji.key}
23+
index={emoji.key}
24+
ref={ref => this._emojis[emoji.key] = ref}
25+
style={{ bottom: emoji.yPosition }}
26+
name={emoji.name}
27+
size={emoji.size}
28+
duration={emoji.duration}
29+
onAnimationCompleted={this.onAnimationCompleted}
30+
/>
31+
)
32+
```
33+
### Animated Floating Reactions
34+
![emoji]()

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AnimatedEmoji } from './lib';

lib/AnimatedEmoji.js

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/**
2+
* Sample React Native App
3+
* https://github.com/facebook/react-native
4+
* @flow
5+
*/
6+
7+
import React, { Component } from 'react';
8+
import PropTypes from 'prop-types';
9+
import {
10+
Animated,
11+
StyleSheet,
12+
Dimensions,
13+
Text
14+
} from 'react-native';
15+
16+
import Emoji from './Emoji';
17+
18+
const ANIMATION_END_X = Dimensions.get('window').width;
19+
const NEGATIVE_END_X = ANIMATION_END_X * -1;
20+
21+
export class AnimatedEmoji extends Component<{}> {
22+
23+
/**
24+
* PropTypes Definition
25+
*/
26+
static propTypes = {
27+
style: PropTypes.any,
28+
name: PropTypes.string.isRequired,
29+
onAnimationCompleted: PropTypes.func,
30+
index: PropTypes.number.isRequired,
31+
};
32+
33+
static defaultProps = {
34+
style: [],
35+
onAnimationCompleted: () => {}
36+
};
37+
38+
constructor(props) {
39+
super(props);
40+
41+
this.state = {
42+
position: new Animated.Value(ANIMATION_END_X),
43+
isAnimationStarted: false
44+
};
45+
}
46+
47+
getRandomInt = (max) => {
48+
return Math.floor(Math.random() * Math.floor(max));
49+
};
50+
51+
/**
52+
* Function to generate input range
53+
* @param start
54+
* @param end
55+
* @param count
56+
* @returns {Array}
57+
*/
58+
generateInputRanges = (start, end, count) => {
59+
const length = start - end;
60+
const segment = length / (count - 1);
61+
const results = [];
62+
results.push(start);
63+
for (let i = count - 2; i > 0; i--) {
64+
results.push(end + segment * i);
65+
}
66+
results.push(end);
67+
return results;
68+
};
69+
70+
/**
71+
* Function to generate scale output range
72+
* @param count
73+
* @returns {Array}
74+
*/
75+
generateYOutputRange = (count) => {
76+
const results = [];
77+
for (let i = 0; i < count; i++) {
78+
results.push(Math.random() * 50);
79+
}
80+
return results;
81+
};
82+
83+
/**
84+
* Function to generate scale output range
85+
* @param count
86+
* @returns {Array}
87+
*/
88+
generateScaleOutputRange = (count) => {
89+
const results = [];
90+
for (let i = 0; i < count; i++) {
91+
results.push(Math.random() + 1);
92+
}
93+
return results;
94+
};
95+
96+
/**
97+
* Function to generate a path
98+
*/
99+
generateAnimation = () => {
100+
/**
101+
* Position X
102+
*/
103+
this._xAnimation = this.state.position.interpolate({
104+
inputRange: [NEGATIVE_END_X, 0],
105+
outputRange: [ANIMATION_END_X, 0]
106+
});
107+
108+
/**
109+
* Position Y
110+
*/
111+
let randomSize = this.getRandomInt(3) + 3;
112+
const inputRangeY = this.generateInputRanges(NEGATIVE_END_X, 0, randomSize);
113+
114+
this._yAnimation = this._xAnimation.interpolate({
115+
inputRange: inputRangeY,
116+
outputRange: this.generateYOutputRange(randomSize)
117+
});
118+
119+
/**
120+
* Scale
121+
*/
122+
randomSize = this.getRandomInt(2) + 2;
123+
const inputRangeScale = this.generateInputRanges(NEGATIVE_END_X, 0, randomSize);
124+
125+
this._scaleAnimation = this._xAnimation.interpolate({
126+
inputRange: inputRangeScale,
127+
outputRange: this.generateScaleOutputRange(randomSize),
128+
extrapolate: 'clamp'
129+
});
130+
131+
/**
132+
* Opacity
133+
*/
134+
this._opacityAnimation = this._xAnimation.interpolate({
135+
inputRange: [NEGATIVE_END_X, NEGATIVE_END_X/4*3, NEGATIVE_END_X/4*2, NEGATIVE_END_X/4, 0],
136+
outputRange: [1, 1, 1, 0.8, 0.0]
137+
});
138+
139+
/**
140+
* Rotate
141+
*/
142+
this._rotateAnimation = this._xAnimation.interpolate({
143+
inputRange: [NEGATIVE_END_X, NEGATIVE_END_X/4*3, NEGATIVE_END_X/4*2, NEGATIVE_END_X/4, 0],
144+
outputRange: ['0deg', '-20deg', '0deg', '20deg', '0deg']
145+
});
146+
};
147+
148+
componentDidMount() {
149+
this.startAnimating();
150+
}
151+
152+
startAnimating = () => {
153+
const { onAnimationCompleted, index } = this.props;
154+
155+
/**
156+
* Generate animation paths
157+
*/
158+
this.generateAnimation();
159+
160+
/**
161+
* Start animating
162+
* Once animating is done, reset values
163+
*/
164+
this.setState({
165+
isAnimationStarted: true
166+
}, () => {
167+
Animated.timing(this.state.position, {
168+
duration: this.props.duration,
169+
toValue: -100,
170+
useNativeDriver: true
171+
}).start(() => {
172+
this.setState({
173+
isAnimationStarted: false,
174+
position: new Animated.Value(ANIMATION_END_X),
175+
});
176+
onAnimationCompleted(index);
177+
});
178+
});
179+
};
180+
181+
getAnimationStyle = () => {
182+
if (this.state.isAnimationStarted) {
183+
return {
184+
transform: [
185+
{ translateY: this._yAnimation },
186+
{ translateX: this.state.position },
187+
{ scale: this._scaleAnimation },
188+
{ rotate: this._rotateAnimation }
189+
],
190+
opacity: this._opacityAnimation
191+
}
192+
}
193+
194+
return {};
195+
};
196+
197+
render() {
198+
const { style, name, size } = this.props;
199+
200+
return (
201+
<Animated.View style={[styles.container, this.getAnimationStyle(), style]}>
202+
<Text style={{ fontSize: size }}>
203+
<Emoji name={name}/>
204+
</Text>
205+
</Animated.View>
206+
);
207+
}
208+
}
209+
210+
const styles = StyleSheet.create({
211+
container: {
212+
position: 'absolute',
213+
bottom: 30,
214+
transform: [
215+
{ translateX: ANIMATION_END_X }
216+
],
217+
}
218+
});

lib/Emoji.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react';
2+
import { Text } from 'react-native';
3+
import PropTypes from 'prop-types';
4+
import nodeEmoji from 'node-emoji';
5+
6+
export default Emoji({
7+
name
8+
}) {
9+
return (
10+
<Text>
11+
{nodeEmoji.get(name)}
12+
</Text>
13+
);
14+
}
15+
16+
Emoji.propTypes = {
17+
name: PropTypes.string.isRequired
18+
};

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AnimatedEmoji } from './AnimatedEmoji';

package.json

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "react-native-animated-emoji",
3+
"version": "0.1.0",
4+
"description": "Animated Floating Reactions like Facebook",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [
10+
"react",
11+
"react-native",
12+
"animation",
13+
"animated-emoji",
14+
"emoji",
15+
"simple",
16+
"emoticons",
17+
"emoticon",
18+
"emojis",
19+
"smiley",
20+
"smileys",
21+
"smilies",
22+
"ideogram",
23+
"ideograms"
24+
],
25+
"license": "MIT",
26+
"repository": {
27+
"type": "git",
28+
"url": "git@github.com:bdavid68/react-native-animated-emoji.git"
29+
},
30+
"author": "David Bala <b.david6008@gmail.com>",
31+
"peerDependencies": {
32+
"react-native": ">=0.29"
33+
},
34+
"dependencies": {
35+
"prop-types": "^15.6.0",
36+
"node-emoji": "^1.3.1"
37+
}
38+
}

0 commit comments

Comments
 (0)