📱React Native Hero Image Blur Effect
How to Use React Native Skia and Animated to Create a Scrolling Blur Effect on Images
A Hero blur effect is not just visually appealing but also enhances UX by drawing attention to the main content as users scroll. By combining blur, scaling, and opacity effects, you can create an interactive and polished transition that makes your app stand out.
In this article, we’ll walk you through how to implement this effect in React Native to enhance your app’s design. Let’s get started!
Here’s what we’re creating:
First up, let’s create a fresh React Native installation.
# Create the project
npx react-native init HeroBlurEffect
# Enter the directory
cd HeroBlurEffect
Secondly, let’s get the project up and running in your development environment. Android or iOS, pick your choice. I usually run iOS as it is slightly more convenient on a Mac. Ready? Let’s dive in:
# Install Cocoapods
npx pod-install
# Cross-compile and run it!
npx react-native run-ios
This will launch the iOS Simulator and start Metro, the JavaScript bundler for React Native. If everything is set up correctly, you’ll see the default “Welcome to React Native” app running in the Simulator. Hot reloading should also be enabled, letting you make code changes and see updates instantly.
Next, let’s update the App.tsx
to set the stage for the project. The goal is to have a photo at the top with a bottom sheet or card below it. While the photo is essential for the effect, the rest of the layout—whether it’s cards, text, or other components—can be customized to whatever fits your needs. For the image, I’m using a photo from Pexels, specifically this one.
Let’s fire up the IDE and have a look at the code:
code .
Below is the JSX markup for the modified App.tsx
. It includes the hero image at the top and some text in a bottom sheet, built using View
and Text
components. I’ve placed the hero image in a component of its own for convenience. I’ll be updating HeroImage incrementally as the project evolves.
<View style={styles.container}>
<ScrollView>
<HeroImage />
<View style={styles.element}>
<Text style={styles.text}>Loreena Ipsu</Text>
<Text style={styles.text2}>@lowrealipsu_</Text>
</View>
<View style={styles.body}>
<Text style={styles.heading}>Lorem Ipsum Fashionis</Text>
<Text style={styles.paragraph}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut
perspiciatis unde omnis
{/* ... */}
</Text>
<Text style={styles.paragraph}>
{/* ... */}
</Text>
{/* ... */}
</View>
</ScrollView>
</View>
Now, let’s look at the HeroImage component. The first version, HeroImage1.tsx
, is a simple component that renders a plain image.
<Image source={require('./assets/hero2.jpg')} style={[styles.image]} />
Here’s what we got thus far:
Next, we need an animated value for the scroll position to create the scroll effect. This can be done by replacing the ScrollView
with Animated.ScrollView
and by adding an onScroll
handler. With the scroll position in place, we can start building animations based on how far the user has scrolled down the page.
const scrollY = useRef(new Animated.Value(0)).current;
return (
<View style={styles.container}>
<Animated.ScrollView
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: scrollY}}}],
{useNativeDriver: false},
)}
scrollEventThrottle={16}
/>
{/* ... */}
</Animated.ScrollView>
</View>
);
Now, let’s create an updated version of the HeroImage
component (HeroImage2.tsx), which takes the vertical scroll position as a prop. To start, we’ll animate the opacity based on the position, making the image gradually more translucent as it scrolls out of view. Since the image is positioned at the top of the screen, we can assume an animation range of 0
to the image’s height for simplicity. In a more complex project it would be wise to decouple the height of the image and the scroll position. E.g. by passing it as a prop.
type Props = {
animated: Animated.Value;
};
const {width} = Dimensions.get('window');
// Set the height retaining the aspect ratio of 4/3
const IMAGE_HEIGHT = (width * 4) / 3;
const HeroImage = ({animated}: Props) => {
const opacity = animated.interpolate({
inputRange: [0, IMAGE_HEIGHT],
outputRange: [1, 0.4],
});
const imageStyle = {
opacity,
};
return (
<Animated.Image
source={require('./assets/hero2.jpg')}
style={[styles.image, imageStyle]}
/>
);
};
Here’s what it looks like in action:
This effect on its own is quite neat, and if you’re going for a more subtle look, it might be all you need. But let’s take it a step further by adding some flair — scaling the image as it scrolls out of view. In this step, we’ll scale the image up to 1.3 times its original size while it transitions off-screen.
const HeroImage = ({animated}: Props) => {
// ...
const scale = animated.interpolate({
inputRange: [0, IMAGE_HEIGHT],
outputRange: [1, 1.3],
extrapolate: 'clamp',
});
const imageStyle = {
opacity,
transform: [{scale}],
};
// ..
}
And here’s the result:
This effect is already looking great, but since this article is all about blur, let’s dive into the blurry stuff! The first step is to find a convenient way to implement a blur effect in React Native. Since React Native doesn’t natively support blur effects, we’ll need to use a third-party library. One great option is React Native Skia by Shopify, which offers excellent support for blur effects with an easy-to-use API. Let’s start by installing the Skia libraries:
yarn add @shopify/react-native-skia@latest
And let’s rebuild the project.
npx pod-install
npx react-native run-ios
Now, we’re ready to create the blur effect using the react-native-skia
<Blur />
component. Below is the updated implementation, but there are a couple of important things to keep in mind:
- Connecting React Native Animations to Skia:
Since the Skia<Blur />
component doesn’t directly work with React Native’s animated system, we use an animated listener to convert theAnimated.Value
into a standard numeric value that Skia can understand. - Using State for Reactivity:
The blur value is stored in auseState
hook rather than auseRef
. This ensures the component re-renders when the blur value changes, allowing the updated blur effect to reflect in the UI.
Here’s the code:
const HeroImage = ({animated}: Props) => {
// Load the image using useImage from react-native-skia
const image = useImage(require('./assets/hero2.jpg'));
// State to store the blur radius
const [blurRadius, setBlurRadius] = useState(0);
// Add a listener to the Animated.Value to update the blur radius
useEffect(() => {
const listenerId = animated.addListener(({value}: {value: number}) => {
// Update the blur radius dynamically based on scroll position if in range
if (value >= 0 && value <= IMAGE_HEIGHT) {
setBlurRadius(value / (IMAGE_HEIGHT / 10));
}
});
return () => {
animated.removeListener(listenerId); // Cleanup listener on unmount
};
}, [animated]);
// Early return if the image is not yet loaded
if (!image) {
return null;
}
return (
<Canvas style={styles.canvas}>
{/* Render the Image and apply the Blur effect */}
<Image image={image} x={0} y={0} width={width} height={IMAGE_HEIGHT}>
<Blur blur={blurRadius} />
</Image>
</Canvas>
);
};
The last part is combining all three effects together in one animation. This is now rather straightforward as we now have all separate parts.
const HeroImage = ({animated}: Props) => {
// ...
const opacity = ...;
const scale = ...;
const imageStyle = {
opacity,
transform: [{scale}],
};
return (
<Animated.View style={[styles.container, imageStyle]}>
<Canvas style={styles.canvas}>
{/* Render the Image and apply the Blur effect */}
<Image image={image} x={0} y={0} width={width} height={IMAGE_HEIGHT}>
<Blur blur={blurRadius} />
</Image>
</Canvas>
</Animated.View>
);
};
Now we have the effect fully in place, with all three animations (opacity, scaling, and blur) working seamlessly together to create a polished and dynamic hero image. 🎉
And that’s a wrap! If you’ve made it this far, thank you for following along. I hope this guide has been helpful and inspired you to explore creative animations in your React Native projects.
You can find the complete code here if you’d like to dig deeper or customize the effect for your own app.
Good luck with your blurred scroll animations — your users are sure to love the polished look and feel! If you have any questions or would like to share your results, feel free to leave a comment below. Happy coding! 🚀