React Native: How To Implement Animated Telegram and WhatsApp Background
In my mind, the popular messenger Telegram introduces us to a good example.
It has a very nice option you can find by opening Telegram then going to Settings > Appearance > Chat Background, then select a background from the gallery and choose the “Motion” option to check it out. Today, we will implement the same alive background in our own apps.
The main properties a good UI should have:
- It should look alive.
- It should not make any discomfort for the user.
- It has to have an attractive design and restrained element placements.
Installation
To make our background movable, depending on the movements of our hands with the phone, we need a library that will provide us with sensor data (e.g gyroscope, accelerometer). The easiest library I have found to use is “expo-sensors”. Let’s install it:
yarn add expo-sensors or npm install expo-sensors
Now you can start your app:
react-native run-ios or react-native run-android
Preparing
Find the image you want to use as background and save it to a convenient place. In my case, it’s located in assets/images/background.jpg. When it’s done let’s import the necessary modules we will use. First thing we need to do is:
import React, { useState, useEffect, useRef } from 'react';
import { Animated, StyleSheet } from 'react-native';
After that, we need to import the background image to our App.js
import BackgroundImage from './assets/images/background.jpg';
Also, we need to import the wanted sensor from “expo-sensors”. I’ll use gyroscope:
import { Gyroscope } from 'expo-sensors';
Code
First let’s create the StyleSheet for our background and put it in the bottom of our App.js:
const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
transform: [{scale: 1.1}],
resizeMode: 'cover'
},
});
The next step is to set up an update interval. The update interval is the value in milliseconds that sets how often we have to ask our sensor about the phone moving data. The higher the interval the lower the performance. The lower the interval the worse the look of animation in accordance. After a long selection of different values, I found the golden mean: 50. You can set any value that is suitable for you but try to choose not less than 20.
Let’s set our interval
const interval = 50;
Gyroscope.setUpdateInterval(interval);
Now we need to contain a subscription to our sensors somewhere. A good choice for that is creating the state with *null*
initial value:
const [subscription, setSubscription] = useState(null);
To decrease the influence of our background movements on the app performance, we will use React Native Animated implemented together with a useRef hook. Also, we have to initialize the *position*
value that will provide us with ready placement of the animated element:
const animation = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
const position = animation.getLayout();
When it’s done we need to create a function that is able to subscribe us to the sensor. Inside of this function, we will also manage our animation:
const subscribeSensor = (sensor) => {
setSubscription(
sensor.addListener(sensorData => {
Animated.timing(animation, {
toValue: {
x: -sensorData.y,
y: -sensorData.x,
},
duration: interval,
useNativeDriver: false,
}).start();
})
);
};
Short explanation. When we are adding a subscription to our sensor by referring to sensor.addListener, the app starts synchronization with the sensor directly. Every time the app asks the sensor about phone moving data, it returns back an object with x, y, z number values inside. Using that object, we put these values directly into the callback function.
To reduce influence on the app performance, we need to create the function that will unsubscribe us from the sensor:
const unsubscribeSensor = () => {
subscription && subscription.remove();
setSubscription(null);
};
And throw that all to the *useEffect*
:
useEffect(() => {
subscribeSensor(Gyroscope);
return () => unsubscribeSensor();
}, []);
Eventually, we are adding the JSX:
return (
<Animated.Image style={[styles.container, position]} source={BackgroundImage} />
);
Entire code
import React, { useState, useEffect, useRef } from 'react';
import { Animated, StyleSheet } from 'react-native';
import BackgroundImage from './assets/images/background.jpg'
import { Gyroscope } from 'expo-sensors';
export default function App() {
const interval = 50;
Gyroscope.setUpdateInterval(interval);
const [subscription, setSubscription] = useState(null);
const animation = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
const position = animation.getLayout();
const subscribeSensor = (sensor) => {
setSubscription(
sensor.addListener(sensorData => {
Animated.timing(animation, {
toValue: {
x: -sensorData.y,
y: -sensorData.x,
},
duration: interval,
useNativeDriver: false,
}).start();
})
);
};
const unsubscribeSensor = () => {
subscription && subscription.remove();
setSubscription(null);
};
useEffect(() => {
subscribeSensor(Gyroscope); // you can set any other sensor instead of Gyroscope
return () => unsubscribeSensor();
}, []);
return (
<Animated.Image style={[styles.container, position]} source={BackgroundImage} />
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
transform: [{scale: 1.1}],
resizeMode: 'cover'
},
});
Result
So here is our movable background. I hope you like it and you will find a purpose for this feature in your application. It is one good way to revive the UI of your app.
Check out how it looks on the Expo Snack (you can do it using a physical device only): https://snack.expo.dev/@nazarsydyaha/movable-background