forked from shoutem/animation
/
Parallax.js
135 lines (125 loc) · 3.37 KB
/
Parallax.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import React, { Component, } from 'react';
import NativeMethodsMixin from 'react/lib/NativeMethodsMixin';
import { Animated, View, Dimensions } from 'react-native';
import { DriverShape } from './DriverShape';
/*
* Parallax Component adds parallax effect to its children components.
* Connect it to driver to animate it. By default children will by
* translated dependent on scroll speed, but you can pass extrapolation options
* to limit translation.
* e.g.:
* ...
* const driver = new ScrollDriver();
*
* return (
* <ScrollView
* {...driver.scrollViewProps}
* >
* <Parallax
* driver={driver}
* scrollSpeed={2}
* >
* <Image />
* </Parallax>
* <Title>Title</Title>
* </ScrollView>
* );
*
* ...
* Above code will create scroll dependent parallax animation over Image component
* where image will be scrolled 2 times faster than Title
*/
export class Parallax extends Component {
static propTypes = {
/**
* An instance of animation driver, usually ScrollDriver
*/
driver: DriverShape.isRequired,
/**
* Components to which an effect will be applied
*/
children: React.PropTypes.node,
/**
* extrapolation options for parallax translation
* if not passed children would be translated by
* scrollVector * (scrollSpeed - 1) * driver.value
* where scroll vector is defined by scrolling direction
*/
extrapolation: React.PropTypes.object,
/**
* how fast passed children would scroll
*/
scrollSpeed: React.PropTypes.number,
/**
* Is Parallax placed in or outside the ScrollView
*/
insideScroll: React.PropTypes.bool,
/**
* Is parallax used as header
*/
header: React.PropTypes.bool,
style: React.PropTypes.object,
}
static defaultProps = {
insideScroll: true,
header: false,
}
constructor(props) {
super(props);
this.translation = new Animated.Value(0);
this.calculateTranslation = this.calculateTranslation.bind(this);
this.measure = this.measure.bind(this);
this.state = {
y: 0,
};
}
measure() {
NativeMethodsMixin.measure.call(this, (x, y, width, height, pageX, pageY) => {
this.setState({ x: pageX, y: pageY });
});
}
componentDidMount() {
requestAnimationFrame(this.measure);
}
calculateTranslation(scrollOffset) {
const { y } = this.state;
const { driver } = this.props;
const scrollHeight = driver.layout.height;
this.translation.setValue(scrollOffset.value - (y - scrollHeight / 2));
}
componentWillMount() {
const { driver } = this.props;
driver.value.addListener(this.calculateTranslation);
}
render() {
const {
scrollSpeed,
children,
extrapolation,
insideScroll,
style,
driver,
header,
} = this.props;
const scrollVector = insideScroll ? -1 : 1;
const scrollFactor = scrollVector * (scrollSpeed - 1);
const animatedValue = header ? driver.value : this.translation;
return (
<Animated.View
style={[style, {
transform: [
{
translateY: animatedValue.interpolate({
inputRange: [-100, 100],
outputRange: [-scrollFactor * 100, scrollFactor * 100],
...extrapolation,
}),
},
],
}]}
>
{children}
</Animated.View>
);
}
}