├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── note.md
├── package.json
└── parabola.js
/.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*
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
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*
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
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-smart-parabola
2 |
3 | [](https://www.npmjs.com/package/react-native-smart-parabola)
4 | [](https://www.npmjs.com/package/react-native-smart-parabola)
5 | [](https://www.npmjs.com/package/react-native-smart-parabola)
6 | [](https://github.com/react-native-component/react-native-smart-parabola/blob/master/LICENSE)
7 |
8 | A smart parabola for react-native apps. It is usually used for shopping cart. Written in JS for cross-platform support.
9 | It works on iOS and Android.
10 |
11 | This component is compatible with React Native 0.25 and newer.
12 |
13 | ## Preview
14 |
15 | ![react-native-smart-parabola-preview-ios][1]
16 | ![react-native-smart-parabola-preview-android][2]
17 |
18 | ## Installation
19 |
20 | ```
21 | npm install react-native-smart-parabola --save
22 | ```
23 |
24 | ## Full Demo
25 |
26 | see [ReactNativeComponentDemos][0]
27 |
28 | ## Usage
29 |
30 | Install the package from npm with `npm install react-native-smart-parabola --save`.
31 | Then, require it from your app's JavaScript files with `import Parabola from 'react-native-smart-parabola'`.
32 |
33 | ```js
34 | import React, {
35 | Component,
36 | } from 'react'
37 | import {
38 | StyleSheet,
39 | Alert,
40 | View,
41 | Text,
42 | Image,
43 | Dimensions,
44 | Platform,
45 | } from 'react-native'
46 |
47 | import Parabola from 'react-native-smart-parabola'
48 | import Button from 'react-native-smart-button'
49 | import image_cart from '../images/cart.png'
50 |
51 | let {width: deviceWidth, height: deviceHeight} = Dimensions.get('window')
52 | let contentTop = Platform.OS == 'ios' ? 64 : 56
53 |
54 | export default class ParabolaDemo extends Component {
55 |
56 | // 构造
57 | constructor (props) {
58 | super(props)
59 | // 初始状态
60 | this.state = {
61 | isTrigger: false,
62 | start: null,
63 | end: null,
64 | }
65 | this._startPositions = {}
66 | this._endPositions = {}
67 | }
68 |
69 | render () {
70 | return (
71 | //to get the page origin (0, 0), container need not to be set margin, padding and border
72 |
73 |
74 |
84 |
94 |
104 |
105 |
106 |
116 |
117 |
118 |
128 |
138 |
148 |
158 |
159 |
160 |
161 |
162 |
164 |
165 |
166 |
168 |
169 |
170 |
177 |
178 | //Parabola need to be contained by root container
179 | )
180 | }
181 |
182 | _onLayout1 = (key, e) => {
183 | let {x, y} = e.nativeEvent.layout
184 | console.log(`x: ${x}, y: ${y}, dw: ${deviceWidth}, dh: ${deviceHeight}, contentTop: ${contentTop}`)
185 | this._startPositions[ key ] = {
186 | start: {
187 | x,
188 | y: y + contentTop,
189 | },
190 | //end: {
191 | // x: deviceWidth - 5,
192 | // y: deviceHeight - 20 - 5
193 | //},
194 | }
195 | }
196 |
197 | _onLayout2 = (key, e) => {
198 | let {x, y} = e.nativeEvent.layout
199 | console.log(`x: ${x}, y: ${y}, dw: ${deviceWidth}, dh: ${deviceHeight}, contentTop: ${contentTop}`)
200 | this._startPositions[ key ] = {
201 | start: {
202 | x,
203 | y: y + contentTop,
204 | },
205 | //end: {
206 | // x: 5,
207 | // y: deviceHeight - 20 - 5
208 | //},
209 | }
210 | }
211 |
212 | _onLayoutCart1 = (e) => {
213 | let {x, y} = e.nativeEvent.layout
214 | this._endPositions[ 'cart-1' ] = {
215 | x: x + 5,
216 | y: y + 5,
217 | }
218 | }
219 |
220 | _onLayoutCart2 = (e) => {
221 | let {x, y} = e.nativeEvent.layout
222 | this._endPositions[ 'cart-2' ] = {
223 | x: x + 5,
224 | y: y + 5,
225 | }
226 | }
227 |
228 | _onPressHandler_1 (key, e) {
229 | let startPositions = this._startPositions[ key ]
230 |
231 | startPositions.end = this._endPositions[ 'cart-1' ]
232 |
233 | let {start, end} = startPositions
234 |
235 | this.setState({
236 | isTrigger: true,
237 | start,
238 | end,
239 | })
240 | }
241 |
242 | _onPressHandler_2 (key, e) {
243 | let startPositions = this._startPositions[ key ]
244 |
245 | startPositions.end = this._endPositions[ 'cart-2' ]
246 |
247 | let {start, end} = startPositions
248 |
249 | this.setState({
250 | isTrigger: true,
251 | start,
252 | end,
253 | })
254 | }
255 |
256 | _renderParabola = ({index, translateX, translateY}) => {
257 | return (
258 |
266 | )
267 | }
268 |
269 | }
270 | ```
271 |
272 | ## Props
273 |
274 | Prop | Type | Optional | Default | Description
275 | ---------------- | ------ | -------- | --------- | -----------
276 | isTrigger | bool | No | | If the value is true, a new parabola view will be rendered
277 | rate | number | Yes | 1 | determine the value which can change the parabola trajectory
278 | duration | number | Yes | 500 | determine the parabola animation duration
279 | top | number | Yes | 0 | determine the extra offset of axis y
280 | start | object | No | | determine the start coordinate (pageX, pageY)
281 | end | object | No | | determine the end coordinate (pageX, pageY)
282 | renderParabola | func | No | | determine the parabola view
283 |
284 | [0]: https://github.com/cyqresig/ReactNativeComponentDemos
285 | [1]: http://cyqresig.github.io/img/react-native-smart-parabola-preview-ios-v1.0.1.gif
286 | [2]: http://cyqresig.github.io/img/react-native-smart-parabola-preview-android-v1.0.1.gif
--------------------------------------------------------------------------------
/note.md:
--------------------------------------------------------------------------------
1 | * 要注意android上view根据onLayout得到layout.x, layout.y计算出的值,
2 | 与ios上有些许差异, 例如, android上根据原点定义一个view绝对对位, left:10, top: 10, 得到的x = 9.9, y = 9.9,
3 | ios上没有此问题, 总能得到x = 10, y = 10
4 |
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-native-component/react-native-smart-parabola",
3 | "version": "1.0.2",
4 | "description": "A smart parabola for react-native apps",
5 | "main": "parabola.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/react-native-component/react-native-smart-parabola.git"
12 | },
13 | "keywords": [
14 | "react-native",
15 | "smart",
16 | "cart",
17 | "parabola",
18 | "component"
19 | ],
20 | "author": "HISAME SHIZUMARU",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/react-native-component/react-native-smart-parabola/issues"
24 | },
25 | "homepage": "https://github.com/react-native-component/react-native-smart-parabola#readme"
26 | }
27 |
--------------------------------------------------------------------------------
/parabola.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A smart parabola for react-native apps
3 | * https://github.com/react-native-component/react-native-smart-parabola/
4 | * Released under the MIT license
5 | * Copyright (c) 2016 react-native-component
6 | */
7 |
8 | import React, {
9 | PropTypes,
10 | Component,
11 | } from 'react'
12 | import {
13 | StyleSheet,
14 | View,
15 | Text,
16 | Dimensions,
17 | } from 'react-native'
18 |
19 | export default class Parabola extends Component {
20 |
21 | static defaultProps = {
22 | rate: 1,
23 | duration: 500,
24 | top: 0,
25 | }
26 |
27 | static propTypes = {
28 | isTrigger: PropTypes.bool.isRequired, //true/false
29 | rate: PropTypes.number,
30 | start: PropTypes.shape({
31 | x: PropTypes.number.isRequired,
32 | y: PropTypes.number.isRequired,
33 | }).isRequired,
34 | end: PropTypes.shape({
35 | x: PropTypes.number.isRequired,
36 | y: PropTypes.number.isRequired,
37 | }).isRequired,
38 | duration: PropTypes.number,
39 | top: PropTypes.number,
40 | renderParabola: PropTypes.func.isRequired,
41 | }
42 |
43 | // 构造
44 | constructor (props) {
45 | super(props)
46 | // 初始状态
47 | this.state = {
48 | parabolas: [],
49 | }
50 | this._isAnimating = false
51 | }
52 |
53 | componentWillReceiveProps (nextProps) {
54 | let {start, end, isTrigger,} = nextProps
55 | if (isTrigger) {
56 | let parabola = {
57 | start,
58 | end,
59 | translateX: 0,
60 | translateY: 0,
61 | startTime: Date.now(),
62 | animationEnd: false,
63 | }
64 | this._addBall(parabola)
65 | }
66 | }
67 |
68 | render () {
69 | let parabolas = this.state.parabolas.map((parabola, index) => {
70 | let {translateX, translateY,} = parabola
71 | return this.props.renderParabola({
72 | index,
73 | translateX,
74 | translateY,
75 | })
76 | })
77 | return (
78 |
79 | {parabolas}
80 |
81 | )
82 | }
83 |
84 | _addBall (parabola) {
85 | this.state.parabolas.push(parabola)
86 |
87 | if(!this._isAnimating) {
88 | this._updateBalls()
89 | }
90 | }
91 |
92 | _updateBalls () {
93 | this._isAnimating = true
94 |
95 | if (this.state.parabolas.length == 0) {
96 | this._isAnimating = false
97 | return
98 | }
99 |
100 | let {duration, rate, top: rry1,} = this.props
101 |
102 | //let r_animationEnd //test code
103 |
104 | let parabolas = this.state.parabolas.map((parabola) => {
105 |
106 | let {start, end, startTime, animationEnd} = parabola
107 |
108 | let interval = Date.now() - startTime
109 |
110 | if (interval > duration) {
111 | if(animationEnd) {
112 | return null
113 | }
114 | else {
115 | interval = duration
116 | parabola.animationEnd = true
117 | //r_animationEnd = true //test code
118 | }
119 | }
120 |
121 | let percent = interval / duration
122 |
123 | let {x: rx1, y: ry1} = start
124 | let {x: rx2, y: ry2} = end
125 |
126 | let direction = rx2 > rx1 ? 1 : -1
127 |
128 | let lmy1 = ry2 - rry1
129 | let my1 = ry2 - ry1
130 | let mx2 = direction * (rx2 - rx1)
131 | let lmh = mx2 / 2
132 | //let mh = (1 - my1 / lmy1) * lmh
133 | let mh = (1 - my1 / lmy1) * lmh * ( rate ) * (1 - my1 / lmy1) + lmh * ( 1 - rate) * (1 + my1 / lmy1)
134 |
135 | let a = my1 / mx2 / (2 * mh - mx2)
136 | let b = -2 * a * mh
137 | let c = my1
138 |
139 | let mx = percent * mx2
140 | let my = a * Math.pow(mx, 2) + b * mx + c
141 |
142 | parabola.translateX = rx1 + direction * mx
143 | parabola.translateY = ry2 - my
144 |
145 | return parabola
146 | })
147 |
148 | parabolas = parabolas.filter((parabola) => {
149 | return parabola != null
150 | })
151 |
152 | this.setState({
153 | parabolas,
154 | })
155 |
156 | //if(!r_animationEnd) //test code
157 | requestAnimationFrame(this._updateBalls.bind(this))
158 | }
159 |
160 | }
161 |
162 |
--------------------------------------------------------------------------------