@pod-point/react-native-animatable
Version:
Easy to use declarative transitions and a standard set of animations for React Native
1,068 lines (939 loc) • 32.8 kB
JavaScript
import React, {
Component,
} from 'react';
import ReactNative, {
Animated,
Easing,
Dimensions,
StyleSheet,
} from 'react-native';
import PropTypes from 'prop-types';
// Transform an object to an array the way react native wants it for transform styles
// { a: x, b: y } => [{ a: x }, { b: y }]
function createKeyedArray(obj) {
return Object.keys(obj).map(key => {
let keyed = {};
keyed[key] = obj[key];
return keyed;
});
}
// Helper function to calculate transform values, args:
// direction: in|out
// originOrDestination: up|down|left|right
// verticalValue: amplitude for up/down animations
// horizontalValue: amplitude for left/right animations
function getAnimationValueForDirection(direction, originOrDestination, verticalValue, horizontalValue) {
const isVertical = originOrDestination === 'up' || originOrDestination === 'down';
const modifier = (isVertical && direction === 'out' ? -1 : 1) * (originOrDestination === 'down' || originOrDestination === 'left' ? -1 : 1);
return modifier * (isVertical ? verticalValue : horizontalValue);
}
// Animations starting with these keywords use element dimensions
// thus, any animation needs to be deferred until the element is measured
const LAYOUT_DEPENDENT_ANIMATIONS = [
'slide',
'fade',
'wobble',
'lightSpeed',
];
// These styles need to be nested in a transform array
const TRANSFORM_STYLE_PROPERTIES = [
'rotate',
'rotateX',
'rotateY',
'rotateZ',
'scale',
'scaleX',
'scaleY',
'translateX',
'translateY',
'skewX',
'skewY',
];
// These styles are not number based and thus needs to be interpolated
const INTERPOLATION_STYLE_PROPERTIES = [
// Transform styles
'rotate',
'rotateX',
'rotateY',
'rotateZ',
'skewX',
'skewY',
'transformMatrix',
// View styles
'backgroundColor',
'borderColor',
'borderTopColor',
'borderRightColor',
'borderBottomColor',
'borderLeftColor',
'shadowColor',
// Text styles
'color',
'textDecorationColor',
];
const EASING_FUNCTIONS = {
'linear': Easing.linear,
'ease': Easing.ease,
'ease-in': Easing.in(Easing.ease),
'ease-out': Easing.out(Easing.ease),
'ease-in-out': Easing.inOut(Easing.ease),
};
// Transforms { translateX: 1 } to { transform: [{ translateX: 1 }]}
function wrapStyleTransforms(style) {
let wrapped = {};
Object.keys(style).forEach(key => {
if (TRANSFORM_STYLE_PROPERTIES.indexOf(key) !== -1) {
if (!wrapped.transform) {
wrapped.transform = [];
}
wrapped.transform.push({
[key]: style[key],
});
} else {
wrapped[key] = style[key];
}
});
return wrapped;
}
// Determine to what value the animation should tween to
function getAnimationTarget(iteration, direction) {
switch (direction) {
case 'reverse': return 0;
case 'alternate': return (iteration % 2) ? 0 : 1;
case 'alternate-reverse': return (iteration % 2) ? 1 : 0;
case 'normal':
default: return 1;
}
}
// Like getAnimationTarget but opposite
function getAnimationOrigin(iteration, direction) {
return getAnimationTarget(iteration, direction) ? 0 : 1;
}
function getDefaultStyleValue(key) {
if (key === 'backgroundColor') {
return 'rgba(0,0,0,0)';
}
if (key === 'color' || key.indexOf('Color') !== -1) {
return 'rgba(0,0,0,1)';
}
if (key.indexOf('rotate') !== -1 || key.indexOf('skew') !== -1) {
return '0deg';
}
if (key === 'fontSize') {
return 14;
}
if (key === 'opacity') {
return 1;
}
return 0;
}
// Returns a flattened version of style with only `keys` values.
function getStyleValues(keys, style) {
if (!StyleSheet.flatten) {
throw new Error('StyleSheet.flatten not available, upgrade React Native or polyfill with StyleSheet.flatten = require(\'flattenStyle\');');
}
let values = {};
let flatStyle = Object.assign({}, StyleSheet.flatten(style));
if (flatStyle.transform) {
flatStyle.transform.forEach(transform => {
const key = Object.keys(transform)[0];
flatStyle[key] = transform[key];
});
delete flatStyle.transform;
}
(typeof keys === 'string' ? [keys] : keys).forEach(key => {
values[key] = (key in flatStyle ? flatStyle[key] : getDefaultStyleValue(key));
});
return values;
}
// Make (almost) any component animatable, similar to Animated.createAnimatedComponent
export function createAnimatableComponent(component) {
const Animatable = Animated.createAnimatedComponent(component);
return class AnimatableComponent extends Component {
static propTypes = {
animation: PropTypes.string,
duration: PropTypes.number,
direction: PropTypes.oneOf(['normal', 'reverse', 'alternate', 'alternate-reverse']),
delay: PropTypes.number,
easing: PropTypes.oneOf(Object.keys(EASING_FUNCTIONS)),
iterationCount(props, propName, componentName) {
const val = props[propName];
if (val !== 'infinite' && !(typeof val === 'number' && val >= 1)) {
return new Error('iterationCount must be a positive number or "infinite"');
}
},
onAnimationBegin: PropTypes.func,
onAnimationEnd: PropTypes.func,
transition: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
]),
};
static defaultProps = {
iterationCount: 1,
onAnimationBegin() {},
onAnimationEnd() {},
};
constructor(props) {
super(props);
this.state = {
animationValue: new Animated.Value(getAnimationOrigin(0, this.props.direction)),
animationStyle: {},
transitionStyle: {},
transitionValues: {},
currentTransitionValues: {},
};
if (props.transition) {
this.state = {
...this.state,
...this.initializeTransitionState(props.transition),
};
}
}
initializeTransitionState(transitionKeys) {
let transitionValues = {};
let styleValues = {};
const currentTransitionValues = getStyleValues(transitionKeys, this.props.style);
Object.keys(currentTransitionValues).forEach(key => {
const value = currentTransitionValues[key];
if (INTERPOLATION_STYLE_PROPERTIES.indexOf(key) !== -1) {
transitionValues[key] = new Animated.Value(0);
styleValues[key] = value;
} else {
transitionValues[key] = styleValues[key] = new Animated.Value(value);
}
});
return {
transitionStyle: styleValues,
transitionValues: transitionValues,
currentTransitionValues: currentTransitionValues,
};
}
getTransitionState(keys) {
const transitionKeys = (typeof transitionKeys === 'string' ? [keys] : keys);
let { transitionValues, currentTransitionValues, transitionStyle } = this.state;
const missingKeys = transitionKeys.filter(key => !this.state.transitionValues[key]);
if (missingKeys.length) {
const transitionState = this.initializeTransitionState(missingKeys);
transitionValues = { ...transitionValues, ...transitionState.transitionValues };
currentTransitionValues = { ...currentTransitionValues, ...transitionState.currentTransitionValues };
transitionStyle = { ...transitionStyle, ...transitionState.transitionStyle };
}
return { transitionValues, currentTransitionValues, transitionStyle };
}
setNativeProps(nativeProps) {
if (this._root) {
this._root.setNativeProps(nativeProps);
}
}
componentDidMount() {
const { animation, duration, delay, onAnimationBegin, onAnimationEnd } = this.props;
if (animation) {
if (delay && animation !== 'none') {
this.setState({ scheduledAnimation: animation });
this._timer = setTimeout(() =>{
onAnimationBegin();
this.setState({ scheduledAnimation: false }, () => this[animation](duration).then(onAnimationEnd));
this._timer = false;
}, delay);
return;
}
if (!this._layout) {
for (let i = LAYOUT_DEPENDENT_ANIMATIONS.length - 1; i >= 0; i--) {
if (animation.indexOf(LAYOUT_DEPENDENT_ANIMATIONS[i]) === 0) {
this.setState({ scheduledAnimation: animation });
return;
}
}
}
onAnimationBegin();
if (animation !== 'none') {
this[animation](duration).then(onAnimationEnd);
} else {
onAnimationEnd();
}
}
}
componentWillUnmount() {
if (this._timer) {
clearTimeout(this._timer);
}
}
componentWillReceiveProps(props) {
const { animation, duration, delay, easing, transition, onAnimationBegin, onAnimationEnd } = props;
if (transition) {
const values = getStyleValues(transition, props.style);
this.transitionTo(values, duration, easing);
} else if (animation !== this.props.animation) {
if (animation && animation != 'none') {
if (delay) {
this.setState({ scheduledAnimation: animation });
this._timer = setTimeout(() =>{
onAnimationBegin();
this.setState({ scheduledAnimation: false }, () => this[animation](duration).then(onAnimationEnd));
this._timer = false;
}, delay);
} else {
onAnimationBegin();
this[animation](duration).then(onAnimationEnd);
}
} else {
this.stopAnimation();
}
}
}
_handleLayout(event) {
const { duration, onLayout, onAnimationBegin, onAnimationEnd } = this.props;
const { scheduledAnimation } = this.state;
this._layout = event.nativeEvent.layout;
if (onLayout) {
onLayout(event);
}
if (scheduledAnimation && !this._timer) {
onAnimationBegin();
this.setState({ scheduledAnimation: false }, () => {
this[scheduledAnimation](duration).then(onAnimationEnd);
});
}
}
animate(duration, animationStyle) {
return new Promise((resolve, reject) => {
this.setState({ animationStyle }, () => {
this._startAnimation(duration, 0, resolve);
});
});
}
stopAnimation() {
this.setState({
scheduledAnimation: false,
animationStyle: {},
});
this.state.animationValue.stopAnimation();
if (this._timer) {
clearTimeout(this._timer);
this._timer = false;
}
}
_startAnimation(duration, iteration, callback) {
const { animationValue } = this.state;
const { direction, iterationCount } = this.props;
let easing = this.props.easing || 'ease-in-out';
let currentIteration = iteration || 0;
const fromValue = getAnimationOrigin(currentIteration, direction);
const toValue = getAnimationTarget(currentIteration, direction);
animationValue.setValue(fromValue);
// This is on the way back reverse
if ((
(direction === 'reverse') ||
(direction === 'alternate' && !toValue) ||
(direction === 'alternate-reverse' && !toValue)
) && easing.match(/^ease\-(in|out)$/)) {
if (easing.indexOf('-in') !== -1) {
easing = easing.replace('-in', '-out');
} else {
easing = easing.replace('-out', '-in');
}
}
Animated.timing(animationValue, {
toValue: toValue,
easing: EASING_FUNCTIONS[easing],
isInteraction: !iterationCount,
duration: duration || this.props.duration || 1000,
}).start(endState => {
currentIteration++;
if (endState.finished && this.props.animation && (iterationCount === 'infinite' || currentIteration < iterationCount)) {
this._startAnimation(duration, currentIteration, callback);
} else if (callback) {
callback(endState);
}
});
}
transition(fromValues, toValues, duration, easing) {
const transitionKeys = Object.keys(toValues);
let { transitionValues, currentTransitionValues, transitionStyle } = this.getTransitionState(transitionKeys);
transitionKeys.forEach(property => {
const fromValue = fromValues[property];
const toValue = toValues[property];
let transitionValue = transitionValues[property];
if (!transitionValue) {
transitionValue = new Animated.Value(0);
}
transitionStyle[property] = transitionValue;
if (INTERPOLATION_STYLE_PROPERTIES.indexOf(property) !== -1) {
transitionValue.setValue(0);
transitionStyle[property] = transitionValue.interpolate({
inputRange: [0, 1],
outputRange: [fromValue, toValue],
});
currentTransitionValues[property] = toValue;
toValues[property] = 1;
} else {
transitionValue.setValue(fromValue);
}
});
this.setState({ transitionValues, transitionStyle, currentTransitionValues }, () => {
this._transitionToValues(toValues, duration || this.props.duration, easing);
});
}
transitionTo(toValues, duration, easing) {
const { currentTransitionValues } = this.state;
let transitions = {
from: {},
to: {},
};
Object.keys(toValues).forEach(property => {
const toValue = toValues[property];
if (INTERPOLATION_STYLE_PROPERTIES.indexOf(property) === -1 && this.state.transitionStyle[property] && this.state.transitionStyle[property] === this.state.transitionValues[property]) {
return this._transitionToValue(this.state.transitionValues[property], toValue, duration, easing);
}
let currentTransitionValue = currentTransitionValues[property];
if (typeof currentTransitionValue === 'undefined' && this.props.style) {
const style = getStyleValues(property, this.props.style);
currentTransitionValue = style[property];
}
transitions.from[property] = currentTransitionValue;
transitions.to[property] = toValue;
});
if (Object.keys(transitions.from).length) {
this.transition(transitions.from, transitions.to, duration, easing);
}
}
_transitionToValues(toValues, duration, easing) {
Object.keys(toValues).forEach(property => {
const transitionValue = this.state.transitionValues[property];
const toValue = toValues[property];
this._transitionToValue(transitionValue, toValue, duration, easing);
});
}
_transitionToValue(transitionValue, toValue, duration, easing) {
if (duration || easing) {
Animated.timing(transitionValue, {
toValue: toValue,
duration: duration || 1000,
easing: EASING_FUNCTIONS[easing || 'ease-in-out'],
}).start();
} else {
Animated.spring(transitionValue, {
toValue: toValue,
}).start();
}
}
bounce(duration) {
return this.animate(duration, {
transform: [{
translateY: this.state.animationValue.interpolate({
inputRange: [0, 0.2, 0.4, 0.43, 0.53, 0.7, 0.8, 0.9, 1],
outputRange: [0, 0, -30, -30, 0, -15, 0, -4, 0],
}),
}],
});
}
flash(duration, times = 2) {
let inputRange = [0];
let outputRange = [1];
const totalTimes = times * 2;
for (let i = 1; i <= totalTimes; i++) {
inputRange.push(i / totalTimes);
outputRange.push(i % 2 ? 0 : 1);
}
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange,
outputRange,
}),
});
}
jello(duration, skew = 12.5, times = 4) {
let inputRange = [0];
let outputRange = ['0 deg'];
const totalTimes = times * 2;
for (let i = 1; i < totalTimes; i++) {
inputRange.push(i / totalTimes);
outputRange.push(skew / i * (i % 2 ? -1 : 1) + ' deg');
}
inputRange.push(1);
outputRange.push('0 deg');
return this.animate(duration, {
transform: [{
skewX: this.state.animationValue.interpolate({ inputRange, outputRange }),
}, {
skewY: this.state.animationValue.interpolate({ inputRange, outputRange }),
}],
});
}
pulse(duration) {
return this.animate(duration, {
transform: [{
scale: this.state.animationValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [1, 1.05, 1],
}),
}],
});
}
rotate(duration) {
return this.animate(duration, {
transform: [{
rotate: this.state.animationValue.interpolate({
inputRange: [0, 0.25, 0.5, 0.75, 1],
outputRange: ['0 deg', '90 deg', '180 deg', '270 deg', '360 deg'],
}),
}],
});
}
rubberBand(duration) {
return this.animate(duration, {
transform: [{
scaleX: this.state.animationValue.interpolate({
inputRange: [0, 0.3, 0.4, 0.5, 0.65, 0.75, 1],
outputRange: [1, 1.25, 0.75, 1.15, 0.95, 1.05, 1],
}),
}, {
scaleY: this.state.animationValue.interpolate({
inputRange: [0, 0.3, 0.4, 0.5, 0.65, 0.75, 1],
outputRange: [1, 0.75, 1.25, 0.85, 1.05, 0.95, 1],
}),
}],
});
}
shake(duration, distance = 10, times = 5) {
let inputRange = [0];
let outputRange = [0];
for (let i = 1; i <= times; i++) {
inputRange.push(i / times);
outputRange.push(i === times ? 0 : (i % 2 ? 1 : -1) * distance);
}
return this.animate(duration, {
transform: [{
translateX: this.state.animationValue.interpolate({
inputRange,
outputRange,
}),
}],
});
}
swing(duration) {
return this.animate(duration, {
transform: [{
rotateZ: this.state.animationValue.interpolate({
inputRange: [0, 0.2, 0.4, 0.6, 0.8, 1],
outputRange: ['0 deg', '15 deg', '-10 deg', '5 deg', '-5 deg', '0 deg'],
}),
}],
});
}
tada(duration) {
return this.animate(duration, {
transform: [{
scale: this.state.animationValue.interpolate({
inputRange: [0, 0.1, 0.2, 0.3, 0.9, 1],
outputRange: [1, 0.9, 0.9, 1.1, 1.1, 1],
}),
}, {
rotateZ: this.state.animationValue.interpolate({
inputRange: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
outputRange: ['0 deg', '-3 deg', '-3 deg', '3 deg', '-3 deg', '3 deg', '-3 deg', '3 deg', '-3 deg', '3 deg', '0 deg'],
}),
}],
});
}
wobble(duration) {
const width = (this._layout || Dimensions.get('window')).width;
return this.animate(duration, {
transform: [{
translateX: this.state.animationValue.interpolate({
inputRange: [0, 0.15, 0.3, 0.45, 0.6, 0.75, 1],
outputRange: [0, -0.25 * width, 0.2 * width, -0.15 * width, 0.1 * width, -0.05 * width, 1],
}),
}, {
rotateZ: this.state.animationValue.interpolate({
inputRange: [0, 0.15, 0.3, 0.45, 0.6, 0.75, 1],
outputRange: ['0 deg', '-5 deg', '3 deg', '-3 deg', '2 deg', '-1 deg', '0 deg'],
}),
}],
});
}
_bounce(duration, direction, originOrDestination) {
let style = {
opacity: this.state.animationValue.interpolate({
inputRange: (direction === 'in' ? [0, 0.6, 1] : [0, 0.55, 1]),
outputRange: (direction === 'in' ? [0, 1, 1] : [1, 1, 0]),
}),
};
if (originOrDestination) {
style.transform = createKeyedArray(this._getBounceTransformation(direction, originOrDestination));
}
return this.animate(duration, style);
}
_getBounceTransformation(direction, originOrDestination) {
const windowSize = Dimensions.get('window');
const animationValue = getAnimationValueForDirection(direction, originOrDestination, windowSize.height, windowSize.width);
const translateKey = (originOrDestination === 'up' || originOrDestination === 'down' ? 'translateY' : 'translateX');
const modifier = animationValue > 0 ? 1 : -1;
return {
[translateKey]: this.state.animationValue.interpolate({
inputRange: (direction === 'in' ? [0, 0.6, 0.75, 0.9, 1] : [0, 0.2, 0.4, 0.45, 1]),
outputRange: (direction === 'in' ? [animationValue, 25 * modifier, -10 * modifier, 5 * modifier, 0] : [0, 10 * modifier, -20 * modifier, -20 * modifier, animationValue]),
}),
};
}
bounceIn(duration) {
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.6, 1],
outputRange: [0, 1, 1],
}),
transform: [{
scale: this.state.animationValue.interpolate({
inputRange: [0, 0.2, 0.4, 0.6, 0.8, 1],
outputRange: [0.3, 1.1, 0.9, 1.03, 0.97, 1],
}),
}],
});
}
bounceOut(duration) {
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.55, 1],
outputRange: [1, 1, 0],
}),
transform: [{
scale: this.state.animationValue.interpolate({
inputRange: [0, 0.2, 0.5, 0.55, 1],
outputRange: [1, 0.9, 1.1, 1.1, 0.3],
}),
}],
});
}
bounceInDown(duration) {
return this._bounce(duration, 'in', 'down');
}
bounceInUp(duration) {
return this._bounce(duration, 'in', 'up');
}
bounceInLeft(duration) {
return this._bounce(duration, 'in', 'left');
}
bounceInRight(duration) {
return this._bounce(duration, 'in', 'right');
}
bounceOutDown(duration) {
return this._bounce(duration, 'out', 'down');
}
bounceOutUp(duration) {
return this._bounce(duration, 'out', 'up');
}
bounceOutLeft(duration) {
return this._bounce(duration, 'out', 'left');
}
bounceOutRight(duration) {
return this._bounce(duration, 'out', 'right');
}
flipInX(duration) {
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.6, 1],
outputRange: [0, 1, 1],
}),
transform: [{
rotateX: this.state.animationValue.interpolate({
inputRange: [0, 0.4, 0.6, 0.8, 1],
outputRange: ['90 deg', '-20 deg', '10 deg', '-5 deg', '0 deg'],
}),
}],
});
}
flipInY(duration) {
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.6, 1],
outputRange: [0, 1, 1],
}),
transform: [{
rotateY: this.state.animationValue.interpolate({
inputRange: [0, 0.4, 0.6, 0.8, 1],
outputRange: ['90 deg', '-20 deg', '10 deg', '-5 deg', '0 deg'],
}),
}],
});
}
flipOutX(duration) {
return this.animate(duration || 750, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.3, 1],
outputRange: [1, 1, 0],
}),
transform: [{
rotateX: this.state.animationValue.interpolate({
inputRange: [0, 0.3, 1],
outputRange: ['0 deg', '-20 deg', '90 deg'],
}),
}],
});
}
flipOutY(duration) {
return this.animate(duration || 750, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.3, 1],
outputRange: [1, 1, 0],
}),
transform: [{
rotateY: this.state.animationValue.interpolate({
inputRange: [0, 0.3, 1],
outputRange: ['0 deg', '-20 deg', '90 deg'],
}),
}],
});
}
lightSpeedIn(duration) {
const width = (this._layout || Dimensions.get('window')).width;
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.6, 1],
outputRange: [0, 1, 1],
}),
transform: [{
translateX: this.state.animationValue.interpolate({
inputRange: [0, 0.6, 1],
outputRange: [width, 0, 0],
}),
}, {
skewX: this.state.animationValue.interpolate({
inputRange: [0, 0.6, 0.8, 1],
outputRange: ['-30 deg', '20 deg', '-5 deg', '0 deg'],
}),
}],
});
}
lightSpeedOut(duration) {
const width = (this._layout || Dimensions.get('window')).width;
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
}),
transform: [{
translateX: this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: [0, width],
}),
}, {
skewX: this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: ['0 deg', '30 deg'],
}),
}],
});
}
_fade(duration, direction, originOrDestination, isBig) {
let style = {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: (direction === 'in' ? [0, 1] : [1, 0]),
}),
};
if (originOrDestination) {
style.transform = createKeyedArray(this._getSlideTransformation(direction, originOrDestination, isBig));
}
return this.animate(duration, style);
}
fadeIn(duration) {
return this._fade(duration, 'in');
}
fadeInDown(duration) {
return this._fade(duration, 'in', 'down');
}
fadeInUp(duration) {
return this._fade(duration, 'in', 'up');
}
fadeInLeft(duration) {
return this._fade(duration, 'in', 'left');
}
fadeInRight(duration) {
return this._fade(duration, 'in', 'right');
}
fadeOut(duration) {
return this._fade(duration, 'out');
}
fadeOutDown(duration) {
return this._fade(duration, 'out', 'down');
}
fadeOutUp(duration) {
return this._fade(duration, 'out', 'up');
}
fadeOutLeft(duration) {
return this._fade(duration, 'out', 'left');
}
fadeOutRight(duration) {
return this._fade(duration, 'out', 'right');
}
fadeInDownBig(duration) {
return this._fade(duration, 'in', 'down', true);
}
fadeInUpBig(duration) {
return this._fade(duration, 'in', 'up', true);
}
fadeInLeftBig(duration) {
return this._fade(duration, 'in', 'left', true);
}
fadeInRightBig(duration) {
return this._fade(duration, 'in', 'right', true);
}
fadeOutDownBig(duration) {
return this._fade(duration, 'out', 'down', true);
}
fadeOutUpBig(duration) {
return this._fade(duration, 'out', 'up', true);
}
fadeOutLeftBig(duration) {
return this._fade(duration, 'out', 'left', true);
}
fadeOutRightBig(duration) {
return this._fade(duration, 'out', 'right', true);
}
_getSlideTransformation(direction, originOrDestination, isBig) {
const size = (isBig || !this._layout ? Dimensions.get('window') : this._layout);
const animationValue = getAnimationValueForDirection(direction, originOrDestination, size.height, size.width);
const translateKey = (originOrDestination === 'up' || originOrDestination === 'down' ? 'translateY' : 'translateX');
return {
[translateKey]: this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: (direction === 'in' ? [animationValue, 0] : [0, animationValue]),
}),
};
}
_slide(duration, direction, originOrDestination) {
return this.animate(duration, {
transform: createKeyedArray(this._getSlideTransformation(direction, originOrDestination)),
});
}
slideInDown(duration) {
return this._slide(duration, 'in', 'down');
}
slideInUp(duration) {
return this._slide(duration, 'in', 'up');
}
slideInLeft(duration) {
return this._slide(duration, 'in', 'left');
}
slideInRight(duration) {
return this._slide(duration, 'in', 'right');
}
slideOutDown(duration) {
return this._slide(duration, 'out', 'down');
}
slideOutUp(duration) {
return this._slide(duration, 'out', 'up');
}
slideOutLeft(duration) {
return this._slide(duration, 'out', 'left');
}
slideOutRight(duration) {
return this._slide(duration, 'out', 'right');
}
_zoom(duration, direction, originOrDestination) {
let style = {
opacity: this.state.animationValue.interpolate({
inputRange: (direction === 'in' ? [0, 0.6, 1] : [0, 0.4, 1]),
outputRange: (direction === 'in' ? [0, 1, 1] : [1, 1, 0]),
}),
};
if (originOrDestination) {
style.transform = createKeyedArray(this._getZoomTransformation(direction, originOrDestination));
}
return this.animate(duration, style);
}
_getZoomTransformation(direction, originOrDestination) {
const windowSize = Dimensions.get('window');
const animationValue = getAnimationValueForDirection(direction, originOrDestination, windowSize.height, windowSize.width);
const translateKey = (originOrDestination === 'up' || originOrDestination === 'down' ? 'translateY' : 'translateX');
const modifier = animationValue > 0 ? 1 : -1;
return {
scale: this.state.animationValue.interpolate({
inputRange: (direction === 'in' ? [0, 0.6, 1] : [0, 0.4, 1]),
outputRange: (direction === 'in' ? [0.1, 0.457, 1] : [1, 0.457, 0.1]),
}),
[translateKey]: this.state.animationValue.interpolate({
inputRange: (direction === 'in' ? [0, 0.6, 1] : [0, 0.4, 1]),
outputRange: (direction === 'in' ? [animationValue, -60 * modifier, 0] : [0, -60 * modifier, animationValue]),
}),
};
}
zoomIn(duration) {
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 1, 1],
}),
transform: [{
scale: this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: [0.3, 1],
}),
}],
});
}
zoomOut(duration) {
return this.animate(duration, {
opacity: this.state.animationValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [1, 1, 0],
}),
transform: [{
scale: this.state.animationValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [1, 0.3, 0],
}),
}],
});
}
zoomInDown(duration) {
return this._zoom(duration, 'in', 'down');
}
zoomInUp(duration) {
return this._zoom(duration, 'in', 'up');
}
zoomInLeft(duration) {
return this._zoom(duration, 'in', 'left');
}
zoomInRight(duration) {
return this._zoom(duration, 'in', 'right');
}
zoomOutDown(duration) {
return this._zoom(duration, 'out', 'down');
}
zoomOutUp(duration) {
return this._zoom(duration, 'out', 'up');
}
zoomOutLeft(duration) {
return this._zoom(duration, 'out', 'left');
}
zoomOutRight(duration) {
return this._zoom(duration, 'out', 'right');
}
render() {
const { style, children, onLayout, animation, duration, delay, transition, ...props } = this.props;
if (animation && transition) {
throw new Error('You cannot combine animation and transition props');
}
const { scheduledAnimation } = this.state;
const hideStyle = (scheduledAnimation && scheduledAnimation.indexOf('In') !== -1 ? { opacity: 0 } : false);
return (
<Animatable
{...props}
ref={element => this._root = element}
onLayout={event => this._handleLayout(event)}
style={[
style,
this.state.animationStyle,
wrapStyleTransforms(this.state.transitionStyle),
hideStyle,
]}
>{children}</Animatable>
);
}
};
}
export const View = createAnimatableComponent(ReactNative.View);
export const Text = createAnimatableComponent(ReactNative.Text);
export const Image = createAnimatableComponent(ReactNative.Image);