Parallax Effect: A Step-by-Step Guide to Parallax Scrolling

Parallax Effect: A Step-by-Step Guide to Parallax Scrolling

You can get the github project here.

Parallax is an optical or visual phenomenon where objects appear to change their relative positions when viewed from different angles. In the context of design and user interfaces, the parallax effect is often utilized to create depth and dimension by moving foreground and background elements at different speeds, thereby giving a sense of motion or a 3D effect.


Implementing parallax scrolling

Creating the parallax scrolling effect is fairly simple. It involves moving the background of a scrolling screen at a different speed compared to the foreground.


Here's the code for a custom ScrollView with a parallax effect:

struct ParallaxScrollView<Content: View>: View {
    let content: Content
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            ZStack {
                GeometryReader { proxy in
                    Image("background")
                        .resizable()
                        .scaledToFill()
                        .scaleEffect(2)
                        .blur(radius: 1)
                        .opacity(0.6)
                        .offset(y: -proxy.frame(in: .global).origin.y / 2)
                }
                
                content
            }
            .ignoresSafeArea()
        }
    }
}

This view wraps all the content of the ScrollView  in a ZStack because we want the content to overlay the background image.

To achieve the parallax effect, we need to offset the background based on the scroll position, but less than the content. Otherwise, the background image will scroll as fast as the content.

To do this, we wrap the background image inside a GeometryReader to obtain the coordinates of the current scroll position.

Then, we divide it by 2 (or any desired number) to control the parallax scrolling effect.

If you want the background to move more slowly than the foreground add the minus sign, otherwise, remove it.

In the provided example, the background image is blurred and slightly transparent to avoid distracting the user from the main content of the view. It's better to use an image larger than the screen to ensure you can scroll through the content while still seeing the background scrolling slowly.

You can use a regular ScrollView

struct ParallaxView: View {
    var body: some View {
        ParallaxScrollView {
            VStack {
                Text("Parallax Example")
                    .font(.system(size: 40))
                    .padding(.bottom, 20)
                
                Text(loremIpsum)
                    .font(.system(size: 17))
            }
            .padding(.horizontal, 20)
            .padding(.bottom, 20)
        }.edgesIgnoringSafeArea(.bottom)
    }
}

And here is the result:



Taking the parallax effect a step further

Parallax scrolling can be applied with more than two layers, resulting in a greater feeling of depth. To achieve this, ensure that all layers move at different speeds. In real life, elements further away in the background should move more slowly than those at the front.

Building upon the previous implementation, we can create a horizontal scrolling view with a parallax effect:



struct ParallaxScrolling<Content: View>: View {
    let content: Content
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            ZStack(alignment: .bottomLeading) {
                GeometryReader { proxy in
                    cloudBackground
                        .frame(maxWidth: UIScreen.main.bounds.width * 5)
                        .offset(x: -proxy.frame(in: .global).origin.x / 1.5)
                    
                    treeBackground
                        .frame(maxWidth: UIScreen.main.bounds.width * 5)
                        .offset(x: -proxy.frame(in: .global).origin.x / 2, y: -50)
                }
                
                content
                
            }
            .edgesIgnoringSafeArea(.bottom)
        }
    }
}

Instead of having only one background with trees, we introduce an additional layer that moves even more slowly, enhancing the parallax effect. The clouds, positioned further away from the trees, accentuates the sense of depth.

struct ParallaxScrolling<Content: View>: View {
    //... 
     var treeBackground: some View {
        VStack {
            Spacer()
            ZStack {
                HStack {
                    ForEach(0 ..< 100) { _ in
                    //Tree
                        Rectangle()
                            .fill(.brown)
                            .frame(width: 60, height: 350)
                            .overlay(
                                Image(systemName: "triangle.fill")
                                    .resizable()
                                    .foregroundColor(.green)
                                    .frame(width: 100, height: 350)
                                    .padding(.bottom, 300)
                            )
                            .padding(.trailing, 40)
                    }
                }
                .padding(.bottom, 40)
            }
        }
    }
    
    var cloudBackground: some View {
        VStack {
            HStack {
                ForEach(0 ..< 100) { _ in
                //Cloud
                    ZStack {
                        Circle()
                            .fill(.white)
                            .frame(width: 50, height: 50)
                            .padding(.leading, 30)
                            .padding(.top, 40)
                        
                        Circle()
                            .fill(.white)
                            .frame(width: 50, height: 50)
                            .padding(.leading, 80)
                            .padding(.top, 50)
                        
                        Circle()
                            .fill(.white)
                            .frame(width: 80, height: 80)
                            .padding(.leading, 70)

                        Circle()
                            .fill(.white)
                            .frame(width: 60, height: 60)
                    }
                }
            }
            .padding(.top, 80)
            Spacer()
        }
    }
}

Once we have the horizontal parallax scrolling view with the two backgrounds setup, we can add the foreground, the gray road and the red car.

struct ParallaxGraphicExample: View {
    var body: some View {
        ZStack {
            ParallaxScrolling {
                ZStack(alignment: .bottom) {
                //Road
                    Color.gray
                        .frame(width: UIScreen.main.bounds.width * 5, height: 150)
                        .overlay {
                            HStack {
                                Image(systemName: "chevron.left")
                                    .font(.system(size: 50))
                                    .foregroundColor(.white)
                                
                                Text("Scroll Left")
                                    .font(.system(size: 50))
                                    .foregroundColor(.white)

                                Spacer()
                            }
                            .padding(.leading, 10)
                        }
                    //Car
                    VStack {
                        Spacer()
                        ZStack(alignment: .leading) {
                            Image("2cv")
                                .resizable()
                                .rotationEffect(Angle(degrees: 90))
                                .frame(width: 300, height: 380)
                            
                        }
                    }
                }
            }
            .background(.blue.opacity(0.4))
        }
        .edgesIgnoringSafeArea(.all)
    }
}

Conclusion

The parallax effect is a visually appealing technique that adds depth and dimension to user interfaces. By moving foreground and background elements at different speeds, it creates a sense of motion and a 3D-like experience.

With SwiftUI, implementing a horizontal parallax scrolling effect is straightforward. By using a custom ScrollView with a ZStack and a GeometryReader, we can achieve the desired parallax motion. Furthermore, we can extend the effect by incorporating multiple background layers moving at different speeds.

Adding parallax scrolling to your user interfaces can enhance the user experience, making it more engaging and immersive. It is a powerful tool to add dynamism and excitement to your app's visual design.

Subscribe to Studio Bytes, your weekly blast of all things tech.

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Knowledge.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.