After the MIUI 12 upgrade, I just loved playing the new smooth animations ๐ and as a developer, was excited to know how to implement it ๐ค.
The first thing I did was google "Wave Animation Can it be done in react-native? " ๐ฌ and got a close result to what I was looking for but as I wanted to play with the wave. That wasn't enough and hence I tried further on my own.
Here are all my learnings compiled as an article.
1. Make A Smooth Curve
To draw a curve, we will use SVG Path which supports the cubic bezier curve.
A cubic bezier curve is a curve line formed using 4 points, a starting point, an ending point, and control points ( c1 & c2 ).
If you are familiar with making curves using SVG, you can jump ๐๐ปโโ๏ธ to point 2.
1.1 Let's Move To The Start Point ๐ฏ
To move to a particular point in SVG, we use the "Move to" command i.e., M x y
.
M 0 0.5
1.2 Make The ๐ง Cubic Curve
In the above example, we plot a curve that starts at (0, 0.5) and ends at (1, 0.5) with control points at (0.5, 0) and (0.5, 1).
๐ด Red dots are curve endpoints and ๐ข green dots are the control points.
1.3 Form A Closed Figure To Fill Color
To form a closed figure, we will draw lines connecting curve endpoints to (1, 1) and (0, 1).
Hence, the complete path command is โฌ๏ธ
M 0 0.5 C 0.5 0 0.5 1 1 0.5 L 1 1 L 0 1 Z
const curveDimension = {
start: { x: 0, y: 0.5 },
c1: { x: 0.5, y: 0 },
c2: { x: 0.5, y: 1 },
end: { x: 1, y: 0.5 },
};
React Native code to make curve using SVG โฌ๏ธ .
To understand how the bezier curve works check out this video. To learn more about SVG path format read this.
2. Get Device Orientation Values ๐ฑ
We will use expo-sensors's
accelerometer to get the values.
Let's get the accelerometer (x,y,z) values and store them in the state variable orientation
.
import { Accelerometer } from 'expo-sensors';
const Wave = () => {
const [orientation, setOrientation] = useState({
x: 0,
y: 0,
z: 0,
});
useEffect(() => {
subscription.current = Accelerometer.addListener(({ x, y, z }) => {
setOrientation(() => ({
x: x,
y: y,
z: z,
}));
});
return () => subscription.current.remove();
}, []);
}
return <View><Text>{orientation.x}</Text></View>
}
Note: On tilting the phone left, the
orientation.x
value becomes positive and vice versa when tilting right.
3. Moving the Curve on Phone Tilt
When a phone is tilted from the extreme left to the extreme right, the orientation.x
value moves from a positive maximum to a negative maximum.
Accordingly, curve points coordinate move as shown below
When you go through the above 5 steps, you can notice
The
start
point will move from (0,0) to (0,1).The
end
point will move from (1,1) to (1,0).Control points
c1
moves from (0.25, 0.25) to (0.25, 0.75) andc2
from (0.75,0.75) to (0.75, 0.25).
X coordinates for all the curve points are kept constant.
Let's find the relation of the Y coordinates of the curve points and the orientation.x
value.
3.1 Start Point
We will find the relation between orientation.x
and the start point. For that, we need to remember the below points.
When the phone is not tilted, the starting point stays at 0.5.
When titled right (negative
orientation.x
value)start
point increases from 0.5 till 1 and similarly 0.5 to 0 when titled left (positiveorientation.x
value). Hence, we negate theorientation.x
value.Initial position of the
start
point is 0.5 when theorientation.x
value is 0, so we will need to add 0.5 to theorientation.x
value.
const adjustAxis = (value) => 0.5 + (-value / 1.5);
adjustAxis is a helper function to map orientation.x
to the starting point's y value.
Hence, start.y = adjustAxis(orientation.x)
3.2 End Point
Since the end
point is always opposite to the start
point.
end.y = 1 - start.y
3.3 Start Control Point c1
After scratching the brain for a long ๐คฏ, I came up with a linear equation that works fine for control points.
c1 = (start.y / 2) + 0.25
3.4 Ending Control Point c2
c2 = 1 - c1
๐คซ Enough Talking, Let's Code It Down.
// start
const startY = adjustAxis(orientation.x);
// initial control point c1
const startControlPointY = (adjustAxis(orientation.x) / 2) + 0.25;
// curvePoints
const curveDimension = {
start: { x: 0, y: startY},
c1: { x: 0.4, y: startControlPointY},
c2: { x: 0.6, y: 1 - startControlPointY},
end: { x: 1, y: 1 - startY},
};
4. Let's Get Ready To Animate ๐๐ปโโ๏ธ
As our curve points are derived from the startY
and startControlPointY
. We will make startY
and startControlPointY
as ** shared values ** and curve point as **derived value **.
Shared values can be understood as a replacement of
Animated.Value
with more benefits.Derived values are similar to shared values whose value is derived from other shared values.
const startY = useSharedValue(0);
const startControlPointY = useSharedValue(0);
useSharedValue
returns a reference to shared value initialized with the provided data. A reference is an object with .value
property, that can be accessed and modified from worklets, but also updated directly from the main JS thread.
const curveDimension = useDerivedValue(() => {
return {
start: { x: 0, y: startY.value },
c1: { x: 0.4, y: startControlPointY.value },
c2: { x: 0.6, y: 1 - startControlPointY.value },
end: { x: 1, y: 1 - startY.value },
};
});
useDerivedValue
allows for creating shared value reference that can change in response to updating of one or more other shared values.
Create AnimatedPath
As we will animate values supplied in the SVGpath, let's first create an <AnimatedPath />
Component and replace it with <Path/>
.
const AnimatedPath = Animated.createAnimatedComponent(Path);
animatedProps
returns an object with key d
and value as SVG path
.
+ const animatedProps = useAnimatedProps(() => {
+ const { start, c1, c2, end } = curveDimension.value;
+ return {
+ d: `M ${start.x} ${start.y} C ${c1.x} ${c1.y} ${c2.x} ${c2.y} ${end.x} ${end.y} L 1 1 L 0 + 1 Z`,
+ };
+ });
- const staticPath = `M ${curveDimension.start.x} ${curveDimension.start.y}
- C ${curveDimension.c1.x} ${curveDimension.c1.y}
- ${curveDimension.c2.x} ${curveDimension.c2.y}
- ${curveDimension.end.x} ${curveDimension.end.y}
- L 1 1 L 0 1 Z`;
Supply animatedProps
instead of the d
.
- <Path
+ <AnimatedPath
fill={'#0099ff'}
- d={d}
+ animatedProps={animatedProps}
/>
5. Add Spring Animation
We are using spring animation because it gives control over velocity, stiffness, friction which can be used to give a fluid property to the curve.
startY.value = withSpring(adjustAxis(orientation.x), {
damping: 80,
mass: 1,
stiffness: 10,
restDisplacementThreshold: 0.0001,
restSpeedThreshold: 0.001,
});
const value = (adjustAxis(orientation.x) / 2) + 0.25;
startControlPointY.value = withSpring(value, {
easing: Easing.out(Easing.bounce),
});
withSpring
is a reanimated hook to implement string physics-based animation.
And finally, we are done. ๐ฅณ ๐
This is how it looks โฌ๏ธ .
To play with it in the expo app click here
and if you are interested in improving it, check out the complete code.
Hope you enjoyed this article and learned something new. ๐โโ๏ธ
Also, check out William Candillon's Wave Animation Video if you haven't.