├── .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 | ![emoticons](docs/demo.gif) 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; --------------------------------------------------------------------------------