Skip to content

Instantly share code, notes, and snippets.

@hungtrn75
Created April 18, 2019 03:18
Show Gist options
  • Save hungtrn75/89048bc398be260f4ed20c78587db4f5 to your computer and use it in GitHub Desktop.
Save hungtrn75/89048bc398be260f4ed20c78587db4f5 to your computer and use it in GitHub Desktop.
import PropTypes from "prop-types";
import React, { PureComponent } from "react";
import { Animated, Easing, TouchableWithoutFeedback, View } from "react-native";
import { radius, styles } from "./styles.js";
export default class Ripple extends PureComponent {
static defaultProps = {
...TouchableWithoutFeedback.defaultProps,
rippleColor: "rgb(0, 0, 0)",
rippleOpacity: 0.3,
rippleDuration: 400,
rippleSize: 0,
rippleContainerBorderRadius: 0,
rippleCentered: false,
rippleSequential: false,
rippleFades: true,
disabled: false,
onRippleAnimation: (animation, callback) => animation.start(callback)
};
static propTypes = {
...Animated.View.propTypes,
...TouchableWithoutFeedback.propTypes,
rippleColor: PropTypes.string,
rippleOpacity: PropTypes.number,
rippleDuration: PropTypes.number,
rippleSize: PropTypes.number,
rippleContainerBorderRadius: PropTypes.number,
rippleCentered: PropTypes.bool,
rippleSequential: PropTypes.bool,
rippleFades: PropTypes.bool,
disabled: PropTypes.bool,
onRippleAnimation: PropTypes.func
};
constructor(props) {
super(props);
this.onLayout = this.onLayout.bind(this);
this.onPress = this.onPress.bind(this);
this.onPressIn = this.onPressIn.bind(this);
this.onPressOut = this.onPressOut.bind(this);
this.onLongPress = this.onLongPress.bind(this);
this.onAnimationEnd = this.onAnimationEnd.bind(this);
this.renderRipple = this.renderRipple.bind(this);
this.unique = 0;
this.mounted = false;
this.state = {
width: 0,
height: 0,
ripples: []
};
}
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}
onLayout(event) {
event.persist();
let { width, height } = event.nativeEvent.layout;
let { onLayout } = this.props;
if ("function" === typeof onLayout) {
onLayout(event);
}
this.setState({ width, height });
}
onPress(event) {
event.persist();
let { ripples } = this.state;
let { onPress, rippleSequential } = this.props;
if (!rippleSequential || !ripples.length) {
if ("function" === typeof onPress) {
requestAnimationFrame(() => onPress(event));
}
this.startRipple(event);
}
}
onLongPress(event) {
event.persist();
let { onLongPress } = this.props;
if ("function" === typeof onLongPress) {
requestAnimationFrame(() => onLongPress(event));
}
this.startRipple(event);
}
onPressIn(event) {
event.persist();
let { onPressIn } = this.props;
if ("function" === typeof onPressIn) {
onPressIn(event);
}
}
onPressOut(event) {
event.persist();
let { onPressOut } = this.props;
if ("function" === typeof onPressOut) {
onPressOut(event);
}
}
onAnimationEnd() {
if (this.mounted) {
this.setState(({ ripples }) => ({ ripples: ripples.slice(1) }));
}
}
startRipple(event) {
event.persist();
let { width, height } = this.state;
let {
rippleDuration,
rippleCentered,
rippleSize,
onRippleAnimation
} = this.props;
let w2 = 0.5 * width;
let h2 = 0.5 * height;
let { locationX, locationY } = rippleCentered
? { locationX: w2, locationY: h2 }
: event.nativeEvent;
let offsetX = Math.abs(w2 - locationX);
let offsetY = Math.abs(h2 - locationY);
let R =
rippleSize > 0
? 0.5 * rippleSize
: Math.sqrt(Math.pow(w2 + offsetX, 2) + Math.pow(h2 + offsetY, 2));
let ripple = {
unique: this.unique++,
progress: new Animated.Value(0),
locationX,
locationY,
R
};
let animation = Animated.timing(ripple.progress, {
toValue: 1,
easing: Easing.out(Easing.ease),
duration: rippleDuration,
useNativeDriver: true
});
onRippleAnimation(animation, this.onAnimationEnd);
this.setState(({ ripples }) => ({ ripples: ripples.concat(ripple) }));
}
renderRipple({ unique, progress, locationX, locationY, R }) {
let { rippleColor, rippleOpacity, rippleFades } = this.props;
let rippleStyle = {
top: locationY - radius,
left: locationX - radius,
backgroundColor: rippleColor,
transform: [
{
scale: progress.interpolate({
inputRange: [0, 1],
outputRange: [0.5 / radius, R / radius]
})
}
],
opacity: rippleFades
? progress.interpolate({
inputRange: [0, 1],
outputRange: [rippleOpacity, 0]
})
: rippleOpacity
};
return <Animated.View style={[styles.ripple, rippleStyle]} key={unique} />;
}
render() {
let { ripples } = this.state;
let { onPress, onPressIn, onPressOut, onLongPress, onLayout } = this;
let {
delayLongPress,
delayPressIn,
delayPressOut,
disabled,
hitSlop,
pressRetentionOffset,
children,
rippleContainerBorderRadius,
testID,
nativeID,
accessible,
accessibilityLabel,
onLayout: __ignored__,
...props
} = this.props;
let touchableProps = {
delayLongPress,
delayPressIn,
delayPressOut,
disabled,
hitSlop,
pressRetentionOffset,
onPress,
onPressIn,
testID,
nativeID,
accessible,
accessibilityLabel,
onPressOut,
onLongPress: props.onLongPress ? onLongPress : undefined,
onLayout
};
let containerStyle = {
borderRadius: rippleContainerBorderRadius
};
return (
<TouchableWithoutFeedback {...touchableProps}>
<Animated.View {...props} pointerEvents="box-only">
{children}
<View style={[styles.container, containerStyle]}>
{ripples.map(this.renderRipple)}
</View>
</Animated.View>
</TouchableWithoutFeedback>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment