├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── screencasts └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | *.[aod] 2 | *.DS_Store 3 | .DS_Store 4 | *Thumbs.db 5 | *.iml 6 | .gradle 7 | .idea 8 | node_modules 9 | npm-debug.log 10 | /android/build 11 | /ios/**/*xcuserdata* 12 | /ios/**/*xcshareddata* 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | .DS_Store 3 | *Thumbs.db 4 | .gradle 5 | .idea 6 | *.iml 7 | screencasts 8 | npm-debug.log 9 | node_modules 10 | /android/build 11 | /ios/**/*xcuserdata* 12 | /ios/**/*xcshareddata* 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015-2016 YunJiang.Fang <42550564@qq.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Marquee (remobile) 2 | A react-native marquee list write in js 3 | 4 | ## Installation 5 | ```sh 6 | npm install @remobile/react-native-marquee --save 7 | ``` 8 | 9 | ## Usage 10 | 11 | ### Example 12 | ```js 13 | 'use strict'; 14 | 15 | var React = require('react'); 16 | var ReactNative = require('react-native'); 17 | var { 18 | StyleSheet, 19 | View, 20 | Text, 21 | } = ReactNative; 22 | 23 | var Button = require('@remobile/react-native-simple-button'); 24 | var Marquee = require('@remobile/react-native-marquee'); 25 | 26 | module.exports = React.createClass({ 27 | getInitialState() { 28 | return { 29 | text: '暗示健康等会拉时间段ksajdfkasdjkfasjdkfasldfjasdlf暗示健康等会拉', 30 | fontSize: 18, 31 | width: 200, 32 | lineHeight: 50, 33 | color: 'red', 34 | }; 35 | }, 36 | changeContent() { 37 | if (!this.contentFlag) { 38 | this.contentFlag = true; 39 | this.setState({ 40 | text: '1231238123981273981273981273912873912873129837129837', 41 | }); 42 | } else { 43 | this.contentFlag = false; 44 | this.setState({ 45 | text: '暗示健康等会拉时间段ksajdfkasdjkfasjdkfasldfjasdlf暗示健康等会拉', 46 | }); 47 | } 48 | }, 49 | changeStyle() { 50 | if (!this.styleFlag) { 51 | this.styleFlag = true; 52 | this.setState({ 53 | fontSize: 50, 54 | width: 100, 55 | lineHeight: 150, 56 | color: 'blue', 57 | }); 58 | } else { 59 | this.styleFlag = false; 60 | this.setState({ 61 | fontSize: 18, 62 | width: 200, 63 | lineHeight: 50, 64 | color: 'red', 65 | }); 66 | } 67 | }, 68 | render() { 69 | const {text, fontSize, color, width, lineHeight} = this.state; 70 | return ( 71 | 72 | 73 | {text} 74 | 75 | 76 | 77 | 78 | ); 79 | } 80 | }); 81 | 82 | 83 | var styles = StyleSheet.create({ 84 | container: { 85 | flex: 1, 86 | paddingTop: 100, 87 | }, 88 | label: { 89 | color: 'red', 90 | fontSize: 18, 91 | fontWeight: '800', 92 | letterSpacing: 10, 93 | fontStyle: 'italic', 94 | lineHeight: 50, 95 | backgroundColor: 'green', 96 | paddingHorizontal: 20, 97 | width: 200, 98 | left: 100, 99 | overflow: 'hidden', 100 | }, 101 | }); 102 | ``` 103 | 104 | ## Screencasts 105 | 106 | ![demo](https://github.com/remobile/react-native-marquee/blob/master/screencasts/demo.gif) 107 | 108 | #### Props 109 | - `children: React.PropTypes.string.isRequired` show text 110 | - `speed: React.PropTypes.number` letter move speed, unit is ms, default is 10 111 | - `spaceRatio: React.PropTypes.number` the space ratio of container width 112 | - `style: PropTypes.style` view style and text style 113 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const React = require('react'); 4 | const ReactNative = require('react-native'); 5 | const { 6 | StyleSheet, 7 | View, 8 | Text, 9 | Animated, 10 | Easing, 11 | } = ReactNative; 12 | const _ = require('lodash'); 13 | 14 | function until (test, iterator, callback) { 15 | if (!test()) { 16 | iterator((err) => { 17 | if (err) { 18 | return callback(err); 19 | } 20 | until(test, iterator, callback); 21 | }); 22 | } else { 23 | callback(); 24 | } 25 | } 26 | 27 | module.exports = React.createClass({ 28 | propTypes: { 29 | children: React.PropTypes.string.isRequired, 30 | speed: React.PropTypes.number, 31 | spaceRatio: React.PropTypes.number, 32 | }, 33 | getDefaultProps () { 34 | return { 35 | speed: 30, 36 | spaceRatio: 0.5, 37 | }; 38 | }, 39 | getInitialState () { 40 | this.alpha = {}; 41 | return { 42 | left1: new Animated.Value(0), 43 | left2: new Animated.Value(0), 44 | list: this.props.children.split(''), 45 | }; 46 | }, 47 | componentWillReceiveProps (nextProps) { 48 | if (this.props.children != nextProps.children) { 49 | this.animateEnable = false; 50 | this.width = 0; 51 | this.state.left1.stopAnimation(() => { 52 | this.state.left2.stopAnimation(() => { 53 | Animated.timing(this.state.left1, { 54 | toValue: 0, 55 | duration: 0, 56 | }).start(() => { 57 | Animated.timing(this.state.left2, { 58 | toValue: this.width, 59 | duration: 0, 60 | }).start(() => { 61 | this.setState({ list: nextProps.children.split('') }); 62 | }); 63 | }); 64 | }); 65 | }); 66 | } 67 | }, 68 | onLayout (i, e) { 69 | this.alpha[i] = e.nativeEvent.layout.width; 70 | if (_.size(this.alpha) === this.state.list.length) { 71 | this.twidth = _.sum(_.values(this.alpha)); 72 | this.alpha = {}; 73 | if (!this.animateEnable) { 74 | this.animateEnable = true; 75 | until( 76 | () => this.width > 0, 77 | (cb) => setTimeout(cb, 100), 78 | () => this.startMoveFirstLabelHead() 79 | ); 80 | } 81 | } 82 | }, 83 | onLayoutContainer (e) { 84 | if (!this.width) { 85 | this.width = e.nativeEvent.layout.width; 86 | this.spaceWidth = this.props.spaceRatio * this.width; 87 | this.setState({ left1: new Animated.Value(0) }); 88 | this.setState({ left2: new Animated.Value(this.width) }); 89 | } 90 | }, 91 | startMoveFirstLabelHead () { 92 | const { width, twidth, props } = this; 93 | const { speed } = props; 94 | Animated.timing(this.state.left1, { 95 | toValue: -twidth + this.spaceWidth, 96 | duration: (twidth - this.spaceWidth) * speed, 97 | easing: Easing.linear, 98 | delay: 500, 99 | }).start(() => { 100 | this.animateEnable && Animated.parallel( 101 | this.moveFirstLabelTail(), 102 | this.moveSecondLabelHead(), 103 | ); 104 | }); 105 | }, 106 | moveFirstLabelHead () { 107 | const { width, twidth, props } = this; 108 | const { speed } = props; 109 | Animated.timing(this.state.left1, { 110 | toValue: -twidth + this.spaceWidth, 111 | duration: (twidth + this.spaceWidth) * speed, 112 | easing: Easing.linear, 113 | }).start(() => { 114 | this.animateEnable && Animated.parallel( 115 | this.moveFirstLabelTail(), 116 | this.moveSecondLabelHead(), 117 | ); 118 | }); 119 | }, 120 | moveFirstLabelTail () { 121 | const { width, twidth, props } = this; 122 | const { speed } = props; 123 | Animated.timing(this.state.left1, { 124 | toValue: -twidth, 125 | duration: this.spaceWidth * speed, 126 | easing: Easing.linear, 127 | }).start(() => { 128 | this.animateEnable && this.setState({ left1: new Animated.Value(width) }); 129 | }); 130 | }, 131 | moveSecondLabelHead () { 132 | const { width, twidth, props } = this; 133 | const { speed } = props; 134 | Animated.timing(this.state.left2, { 135 | toValue: -twidth + this.spaceWidth, 136 | duration: (twidth + this.spaceWidth) * speed, 137 | easing: Easing.linear, 138 | }).start(() => { 139 | this.animateEnable && Animated.parallel( 140 | this.moveFirstLabelHead(), 141 | this.moveSecondLabelTail(), 142 | ); 143 | }); 144 | }, 145 | moveSecondLabelTail () { 146 | const { width, twidth, props } = this; 147 | const { speed } = props; 148 | Animated.timing(this.state.left2, { 149 | toValue: -twidth, 150 | duration: this.spaceWidth * speed, 151 | easing: Easing.linear, 152 | }).start(() => { 153 | this.animateEnable && this.setState({ left2: new Animated.Value(twidth) }); 154 | }); 155 | }, 156 | render () { 157 | const { left1, left2, list } = this.state; 158 | const s = StyleSheet.flatten(this.props.style); 159 | const textStyleKeys = ['color', 'fontSize', 'fontWeight', 'letterSpacing', 'fontStyle', 'lineHeight', 'fontFamily', 'textDecorationLine']; 160 | const textStyle = _.pick(s, textStyleKeys); 161 | const containerStyle = _.omit(s, textStyleKeys); 162 | return ( 163 | 164 | 165 | {list.map((o, i) => ({o}))} 166 | 167 | 168 | {list.map((o, i) => ({o}))} 169 | 170 | 171 | ); 172 | }, 173 | }); 174 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@remobile/react-native-marquee", 3 | "version": "1.0.1", 4 | "description": "A react-native card list write in js", 5 | "main": "index.js", 6 | "author": { 7 | "name": "YunJiang.Fang", 8 | "email": "42550564@qq.com" 9 | }, 10 | "license": "MIT", 11 | "keywords": [ 12 | "react-native", 13 | "react-component", 14 | "ios", 15 | "android", 16 | "marquee", 17 | "list", 18 | "remobile", 19 | "mobile" 20 | ], 21 | "homepage": "https://github.com/remobile/react-native-marquee", 22 | "bugs": { 23 | "url": "https://github.com/remobile/react-native-marquee/issues" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git://github.com/remobile/react-native-marquee.git" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /screencasts/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remobile/react-native-marquee/41941f86784391e7a7c92f441c3ac322d3faf7a3/screencasts/demo.gif --------------------------------------------------------------------------------