forked from uber/react-vis
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathanimation.js
148 lines (132 loc) · 4.25 KB
/
animation.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
136
137
138
139
140
141
142
143
144
145
146
147
148
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {interpolate} from 'd3-interpolate';
import {spring, Motion, presets} from 'react-motion';
const ANIMATION_PROPTYPES = PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
stiffness: PropTypes.number,
nonAnimatedProps: PropTypes.arrayOf(PropTypes.string),
damping: PropTypes.number
}),
PropTypes.bool
]);
const propTypes = {
animatedProps: PropTypes.arrayOf(PropTypes.string).isRequired,
animation: ANIMATION_PROPTYPES,
onStart: PropTypes.func,
onEnd: PropTypes.func
};
/**
* Format the animation style object
* @param {Object|String} animationStyle - The animation style property, either the name of a
* presets are one of noWobble, gentle, wobbly, stiff
*/
function getAnimationStyle(animationStyle = presets.noWobble) {
if (typeof animationStyle === 'string') {
return presets[animationStyle] || presets.noWobble;
}
const {damping, stiffness} = animationStyle;
return {
damping: damping || presets.noWobble.damping,
stiffness: stiffness || presets.noWobble.stiffness,
...animationStyle
};
}
/**
* Extract the animated props from the entire props object.
* @param {Object} props Props.
* @returns {Object} Object of animated props.
*/
export function extractAnimatedPropValues(props) {
const {animatedProps, ...otherProps} = props;
return animatedProps.reduce((result, animatedPropName) => {
if (otherProps.hasOwnProperty(animatedPropName)) {
result[animatedPropName] = otherProps[animatedPropName];
}
return result;
}, {});
}
class Animation extends PureComponent {
constructor(props) {
super(props);
this._updateInterpolator(props);
this._renderChildren = this._renderChildren.bind(this);
this._motionEndHandler = this._motionEndHandler.bind(this);
}
componentWillUpdate(props) {
this._updateInterpolator(this.props, props);
if (props.onStart) {
props.onStart();
}
}
_motionEndHandler() {
if (this.props.onEnd) {
this.props.onEnd();
}
}
/**
* Render the child into the parent.
* @param {Number} i Number generated by the spring.
* @returns {React.Component} Rendered react element.
* @private
*/
_renderChildren({i}) {
const {children} = this.props;
const interpolator = this._interpolator;
const child = React.Children.only(children);
const interpolatedProps = interpolator ? interpolator(i) : interpolator;
// interpolator doesnt play nice with deeply nested objected
// so we expose an additional prop for situations like these, soit _data,
// which stores the full tree and can be recombined with the sanitized version
// after interpolation
let data = (interpolatedProps && interpolatedProps.data) || null;
if (data && child.props._data) {
data = data.map((row, index) => {
const correspondingCell = child.props._data[index];
return {
...row,
parent: correspondingCell.parent,
children: correspondingCell.children
};
});
}
return React.cloneElement(child, {
...child.props,
...interpolatedProps,
data: data || child.props.data || null,
// enforce re-rendering
_animation: Math.random()
});
}
/**
* Update the interpolator function and assign it to this._interpolator.
* @param {Object} oldProps Old props.
* @param {Object} newProps New props.
* @private
*/
_updateInterpolator(oldProps, newProps) {
this._interpolator = interpolate(
extractAnimatedPropValues(oldProps),
newProps ? extractAnimatedPropValues(newProps) : null
);
}
render() {
const animationStyle = getAnimationStyle(this.props.animation);
const defaultStyle = {i: 0};
const style = {i: spring(1, animationStyle)};
// In order to make Motion re-run animations each time, the random key is
// always passed.
// TODO: find a better solution for the spring.
const key = Math.random();
return (
<Motion {...{defaultStyle, style, key}} onRest={this._motionEndHandler}>
{this._renderChildren}
</Motion>
);
}
}
Animation.propTypes = propTypes;
Animation.displayName = 'Animation';
export default Animation;
export const AnimationPropType = ANIMATION_PROPTYPES;