I'm trying to create a component using Animated on React Native where a circle expands from the point of the press to encompass the entire screen.
The idea is that there's essentially a carousel of colors, say:
const colors = ['white', 'black', 'green', 'blue', 'red', 'pink'];
You start with color 0 as the background, and then, when you press the screen, a circle of color 1 expands (from nothing) from the point of press, and takes up the whole screen. At that point, with the animation complete, the colorIndex
is incremented, and the animated circle simultaneously erased, so that, seamlessly, we've moved onto the next color, which is now the background. (EG, we're on 'black' as the background color now).
Then, when you press again, the process repeats, expanding a circle of "green" to fill the screen, then updating green to be the BG color, etc.
(To clarify what I'm looking for in the animation, check this GIF out (minus the icons, etc)).
I'm trying this with Animated, and code like the following, called on press of the main background component (a TouchableHighlight):
triggerSwipe(event) {
this.setState({ location: { x: event.nativeEvent.locationX, y: event.nativeEvent.locationY } });
Animated.timing(
this.state.diameter,
{ toValue: Dimensions.get('window').height * 2, duration: 500 },
).start(() => {
this.state.diameter.setValue(0);
this.setState({ colorIndex: this.state.colorIndex + 1 });
});
}
Then the idea is, in the render
function, I set left
and top
of my absolute
ly positioned circle component to equal the location, and I set width
and height
to equal diameter
, as well as borderRadius
(diameter / 2.0
would not work, because diameter
is an Animated object, not a number).
There are two issues going on here that I can't seem to figure out (both visible in the GIF included at the bottom of this question):
I'm assuming #1 is really hard to reason about without all the code. So I'll paste the full component code below. The 2nd one I'm hoping is a little more conceptual/easy to figure out without playing with it.
Can anyone think of a good way to assert the location of the center of the circle? I'm worried it requires constantly updating the left/top location of the circle as a function of its diameter (left: location.x - diameter._value / 2
), which will get rid of all the native performance benefits of Animated, as I understand it. Any better ideas?
Here's the full component code:
import React from 'react';
import { TouchableHighlight, Animated } from 'react-native';
import { Dimensions } from 'react-native';
const colors = ['white', 'black', 'green', 'blue', 'red', 'pink'];
const color = index => colors[index % colors.length];
class ColorScape extends React.Component {
constructor(props) {
super(props);
this.state = {
colorIndex: 0,
location: { x: 0, y: 0 },
diameter: new Animated.Value(0)
};
}
triggerSwipe(event) {
this.setState({ location: { x: event.nativeEvent.locationX, y: event.nativeEvent.locationY } });
Animated.timing(
this.state.diameter,
{ toValue: Dimensions.get('window').height * 2, duration: 500 },
).start(() => {
this.state.diameter.setValue(0);
this.setState({ colorIndex: this.state.colorIndex + 1 });
});
}
render() {
const { colorIndex, diameter, location } = this.state;
const circleStyles = {
backgroundColor: color(colorIndex + 1),
width: diameter,
height: diameter,
borderRadius: diameter,
position: 'absolute',
zIndex: 2,
left: location.x,
top: location.y
};
return (
<TouchableHighlight
style={ {
flex: 1,
width: '100%',
height: '100%',
zIndex: 1,
backgroundColor: color(colorIndex)
} }
onPress={ (event) => this.triggerSwipe(event) }
>
<Animated.View
style={ circleStyles }
/>
</TouchableHighlight>
);
}
}
export default ColorScape;
Here's the current functionality (in the iPhone X simulator):
If you want your circle to expand from the point of click, I would recommend using transform: scale()
instead of animating the width/height properties. This way you only have to animate one property.
You will still need to center your circle. Let's say you set the width/height to 100px. Now you set transform: scale(0)
as your initial size and you can scale your circle up past 1 if you want, so it will take the full screen. You can play with the math to get the timing/feel right.
With this approach you will STILL have the same issue of the circle enlarging from the top left. That is because you have positioned it there. top/left are relative to the parent container. You'll now need to center your circle relative to itself. This means you will need to add another transform
property. transform: translateX(-50%)translateY(-50%)
. This is going to always center your circle relative to it's center position.
Starting transform: transform: translateX(-50%)translateY(-50%)scale(0)
Enlarged transform: transform: translateX(-50%)translateY(-50%)scale(x)
where x
is however large you want the circle to get.