├── .gitignore
├── README.md
├── docs
└── demo.gif
├── index.js
├── package.json
└── style.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 | .idea/
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-elastic-image-slider
2 |
3 | react native elastic image slider component
4 |
5 | 
6 |
7 | ## Install
8 |
9 | ```js
10 | npm install react-native-elastic-image-slider
11 | ```
12 |
13 | ## Usage
14 |
15 | ### UI Component
16 |
17 | - step 1
18 |
19 | Import the component package.
20 |
21 | ```js
22 | import ImageSlider from 'react-native-elastic-image-slider';
23 | ```
24 | - step 2
25 |
26 | Write the component code in the proper place of your page render.
27 |
28 | ```js
29 | let images = [
30 | {
31 | width: 150,
32 | height: 180,
33 | uri: 'http://chuantu.biz/t5/152/1501134247x2890173753.jpg'
34 | },
35 | {
36 | width: 200,
37 | height: 320,
38 | uri: 'http://chuantu.biz/t5/152/1501135055x3394041611.jpg'
39 | },
40 | {
41 | width: 200,
42 | height: 160,
43 | uri: 'http://chuantu.biz/t5/152/1501134194x2890173753.jpg'
44 | }
45 | ];
46 |
47 |
51 |
52 | ```
53 |
54 |
55 | ### props
56 |
57 | | Prop | Type | Description | Required | Default |
58 | |---|---|---|---|---|
59 | |**`images`**|`array`| the images to slide |`Yes`|None|
60 | |**`initialPosition`**|`number`| initial one of all images to show|`No`|`0`|
61 | |**`style`**|`style`| custom style|`No`| None |
62 |
63 | ## Thanks
64 |
65 | Inspired by [react-native-image-slider](https://github.com/PaulBGD/react-native-image-slider)
66 |
--------------------------------------------------------------------------------
/docs/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiewang/react-native-elastic-image-slider/cc444de82442633891c224415a6388b77f93ec53/docs/demo.gif
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component,PropTypes} from 'react';
2 | import {
3 | Image,
4 | Text,
5 | View,
6 | StyleSheet,
7 | Animated,
8 | PanResponder,
9 | TouchableHighlight,
10 | TouchableOpacity,
11 | Dimensions,
12 | LayoutAnimation,
13 | UIManager,
14 | Platform
15 | } from 'react-native';
16 |
17 | import styles from './style';
18 |
19 |
20 | class ImageSlider extends Component {
21 | constructor(props) {
22 | super(props);
23 |
24 | this.state = {
25 | position: this.props.initialPosition,
26 | height: new Animated.Value(this._scaleHeight(this.props.images[this.props.initialPosition])),
27 | left: new Animated.Value(0),
28 | scrolling: false,
29 | timeout: null
30 | };
31 |
32 | // Enable LayoutAnimation under Android
33 | if (Platform.OS === 'android') {
34 | UIManager.setLayoutAnimationEnabledExperimental(true)
35 | }
36 |
37 | }
38 |
39 | static defaultProps = {
40 | position: 0,
41 | initialPosition: 0
42 | };
43 |
44 |
45 | _move(index) {
46 | const width = Dimensions.get('window').width;
47 | const to = index * -width;
48 | const scaleH = this._scaleHeight(this.props.images[index]);
49 | if (!this.state.scrolling) {
50 | return;
51 | }
52 | Animated.timing(this.state.left, {toValue: to, friction: 10, tension: 10, velocity: 1, duration: 400}).start();
53 | Animated.timing(this.state.height, {
54 | toValue: scaleH,
55 | friction: 10,
56 | tension: 10,
57 | velocity: 1,
58 | duration: 400
59 | }).start();
60 |
61 | if (this.state.timeout) {
62 | clearTimeout(this.state.timeout);
63 | }
64 | this.setState({
65 | position: index,
66 | timeout: setTimeout(() => {
67 | this.setState({scrolling: false, timeout: null});
68 | if (this.props.onPositionChanged) {
69 | this.props.onPositionChanged(index);
70 | }
71 | }, 400)
72 | });
73 | }
74 |
75 | _scaleHeight(image) {
76 | const imageWidth = image.width;
77 | const imageHeight = image.height;
78 | return Dimensions.get('window').width * imageHeight / imageWidth;
79 | }
80 |
81 | _getPosition() {
82 | if (typeof this.props.position === 'number') {
83 | //return this.props.position;
84 | }
85 | return this.state.position;
86 | }
87 |
88 | componentWillReceiveProps(props) {
89 | if (props.position !== undefined) {
90 | this.setState({scrolling: true});
91 | this._move(props.position);
92 | }
93 | }
94 |
95 | componentWillMount() {
96 | const width = Dimensions.get('window').width;
97 |
98 | this.state.left.setValue(-(width * this.state.position));
99 |
100 | if (typeof this.props.position === 'number') {
101 | //this.state.left.setValue(-(width * this.props.position));
102 | }
103 |
104 | let release = (e, gestureState) => {
105 | const width = Dimensions.get('window').width;
106 | const relativeDistance = gestureState.dx / width;
107 | const vx = gestureState.vx;
108 | let change = 0;
109 |
110 | if (relativeDistance < -0.5 || (relativeDistance < 0 && vx <= 0.5)) {
111 | change = 1;
112 | } else if (relativeDistance > 0.5 || (relativeDistance > 0 && vx >= 0.5)) {
113 | change = -1;
114 | }
115 | const position = this._getPosition();
116 | if (position === 0 && change === -1) {
117 | change = 0;
118 | } else if (position + change >= this.props.images.length) {
119 | change = (this.props.images.length) - (position + change);
120 | }
121 | this._move(position + change);
122 | return true;
123 | };
124 |
125 | this._panResponder = PanResponder.create({
126 | onMoveShouldSetPanResponderCapture: (evt, gestureState) => Math.abs(gestureState.dx) > 5,
127 | onPanResponderRelease: release,
128 | onPanResponderTerminate: release,
129 | onPanResponderMove: (e, gestureState) => {
130 | const dx = gestureState.dx;
131 | const width = Dimensions.get('window').width;
132 | const position = this._getPosition();
133 | let left = -(position * width) + Math.round(dx);
134 | if (left > 0) {
135 | left = Math.sin(left / width) * (width / 2);
136 | } else if (left < -(width * (this.props.images.length - 1))) {
137 | const diff = left + (width * (this.props.images.length - 1));
138 | left = Math.sin(diff / width) * (width / 2) - (width * (this.props.images.length - 1));
139 | }
140 | this.state.left.setValue(left);
141 | if (!this.state.scrolling) {
142 | this.setState({scrolling: true});
143 | }
144 |
145 | //scale
146 | let change = 0;
147 |
148 | if (dx >= 0) {
149 | change = -1;
150 | } else if (dx < 0) {
151 | change = 1;
152 | }
153 | if (position === 0 && change === -1) {
154 | change = 0;
155 | } else if (position + change >= this.props.images.length) {
156 | change = (this.props.images.length) - (position + change);
157 | }
158 | const originH = this._scaleHeight(this.props.images[position]);
159 | const scaleH = this._scaleHeight(this.props.images[position + change]);
160 | Animated.timing(this.state.height, {
161 | toValue: (scaleH - originH)*Math.abs(dx/width) + originH,
162 | friction: 10,
163 | tension: 10,
164 | velocity: 1,
165 | duration: 0
166 | }).start();
167 | },
168 | onShouldBlockNativeResponder: () => true
169 | });
170 |
171 | }
172 |
173 | componentDidMount() {
174 |
175 | }
176 |
177 | componentWillUnmount() {
178 | if (this.state.timeout) {
179 | clearTimeout(this.state.timeout);
180 | }
181 | }
182 |
183 | componentWillUpdate() {
184 | const CustomLayoutAnimation = {
185 | duration: 100,
186 | //create: {
187 | // type: LayoutAnimation.Types.linear,
188 | // property: LayoutAnimation.Properties.opacity,
189 | //},
190 | update: {
191 | type: LayoutAnimation.Types.linear
192 | }
193 | };
194 | LayoutAnimation.configureNext(CustomLayoutAnimation);
195 | //LayoutAnimation.linear();
196 | }
197 |
198 | render() {
199 | const customStyles = this.props.style ? this.props.style : {};
200 | const width = Dimensions.get('window').width;
201 | const position = this._getPosition();
202 | return (
203 |
206 | {this.props.images.map((image, index) => {
207 |
208 | const imageWidth = image.width;
209 | const imageHeight = image.height;
210 | const scaleH = Dimensions.get('window').width * imageHeight / imageWidth;
211 | let imageComponent = ;
217 | if(typeof image.uri === 'number') {
218 | imageComponent = ;
224 | }
225 | if (this.props.onPress) {
226 | return (
227 | {
230 | this.props.onPress({ image, index })
231 | }}
232 | delayPressIn={200}
233 | >
234 | {imageComponent}
235 |
236 | );
237 | } else {
238 | return imageComponent;
239 | }
240 | })}
241 |
242 |
243 |
244 | {position+1}/{this.props.images.length}
245 |
246 |
247 | );
248 | }
249 | }
250 |
251 | ImageSlider.propTypes = {
252 | images: PropTypes.array,
253 | position: PropTypes.number,
254 | initialPosition: PropTypes.number,
255 | onPositionChanged: PropTypes.func,
256 | style: View.propTypes.style
257 | };
258 |
259 | export default ImageSlider;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-elastic-image-slider",
3 | "version": "1.0.0",
4 | "description": "react native elastic image slider",
5 | "main": "index.js",
6 | "directories": {
7 | "doc": "docs"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/xiewang/react-native-elastic-image-slider.git"
15 | },
16 | "keywords": [
17 | "react-native",
18 | "image",
19 | "slider",
20 | "elastic"
21 | ],
22 | "author": "xiewang",
23 | "license": "ISC",
24 | "bugs": {
25 | "url": "https://github.com/xiewang/react-native-elastic-image-slider/issues"
26 | },
27 | "homepage": "https://github.com/xiewang/react-native-elastic-image-slider"
28 | }
29 |
--------------------------------------------------------------------------------
/style.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react-native';
3 |
4 | var {
5 | StyleSheet,
6 | Dimensions,
7 | Platform
8 | } = React;
9 |
10 | const {height, width} = Dimensions.get('window');
11 |
12 | const styles = StyleSheet.create({
13 | container: {
14 | flexDirection: 'row',
15 | backgroundColor: '#ddd',
16 | overflow: 'hidden'
17 | },
18 | image: {
19 | width: Dimensions.get('window').width
20 | },
21 | sequences: {
22 | height: 20,
23 | width: 45,
24 | bottom: 20,
25 | position: 'absolute',
26 | right: 15,
27 | flex: 1,
28 | justifyContent: 'center',
29 | alignItems: 'center',
30 | flexDirection: 'row',
31 | backgroundColor: 'rgba(74,73,74,0.3)',
32 | borderRadius: 10
33 | },
34 | sequence: {
35 | color: '#fff',
36 | fontSize: 12,
37 | },
38 | count: {
39 | height: 36,
40 | width: 36,
41 | top: 40,
42 | right: 20,
43 | position: 'absolute',
44 | justifyContent: 'center',
45 | alignItems: 'center',
46 | backgroundColor: 'rgba(74,73,74,0.3)',
47 | borderRadius: 18
48 | },
49 | countText: {
50 | color: '#fff',
51 | fontSize: 18,
52 | }
53 | });
54 |
55 | export default styles;
--------------------------------------------------------------------------------