📱React Native Hero Image Blur Effect

How to Use React Native Skia and Animated to Create a Scrolling Blur Effect on Images

Mikael Ainalem
6 min readJan 4, 2025
An image showing heros and blur
Heros and blur

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:

GIF animation showing the complete effect
Blur effect when hero image is scrolled out of view

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:

An image showing the first iteration of building the effect. This image shows the image and the bottom sheet below
Screenshot of simulator

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:

GIF animation showing the first step of building the effect, animating the opacity of the image as it scrolls out of view
Opacity animation based on scroll position

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:

GIF animation of the second step of the effect, showing the scale up as well as the opacity change of the image scrolling out of view
GIF image of opacity and scale up effect

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:

  1. 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 the Animated.Value into a standard numeric value that Skia can understand.
  2. Using State for Reactivity:
    The blur value is stored in a useState hook rather than a useRef. 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! 🚀

--

--

Mikael Ainalem
Mikael Ainalem

Written by Mikael Ainalem

Enthusiastic about software & design, father of 3, freelancer and currently CTO at Norban | twitter: https://twitter.com/mikaelainalem

No responses yet