skip to Main Content

I’m trying to reproduce Apple’s parallax effect code from their WWDC24 video here but it isn’t quite working well.
I have the following code:

struct AnimalView: View {
    let imageName: String
    var body: some View {
        Image(imageName)
            .resizable()
            .frame(width: 300, height: 450)
            .clipShape(RoundedRectangle(cornerRadius: 32))
    }
}

struct ContentView: View {
    private let animals = ["1", "2", "3", "4", "5", "6"]
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            LazyHStack(spacing: 16) {
                ForEach(animals, id: .self) { name in
                    AnimalView(imageName: name)
                        .scrollTransition(.interactive, axis: .horizontal) { content, phase in
                            content.offset(x: phase.value * -250)
                        }
                        .containerRelativeFrame(.horizontal)
                        .clipShape(RoundedRectangle(cornerRadius: 32))
                }
            }
            .scrollTargetLayout()
        }
        .contentMargins(.horizontal, 32)
        .scrollTargetBehavior(.paging)
    }
}

The parallax effect is somewhat there but is jarring and not smooth at all as in Apple’s video. I understand the video doesn’t show the entire code but it seems to me that the AnimalView needs additional offset to be applied for the transitions to be smoother but I can’t quite nail it down. Any help is appreciated.

2

Answers


  1. First, remove the clipShape from AnimalView:

    struct AnimalView: View {
        let imageName: String
        var body: some View {
            Image(imageName)
                .resizable()
                .scaledToFill()
                .frame(width: 300, height: 450)
        }
    }
    

    You only need the clipShape after the scrollTransition. The image should be clipped after the scroll transition is applied, not before.

    Second, adjust the x offset based on the size of your image. It should be at most half of the image width.

    .scrollTransition(.interactive, axis: .horizontal) { content, phase in
        // 150, because the image is 300pt wide
        content.offset(x: phase.value * -150)
    }
    

    You can use a smaller offset to achieve a "less" parallax effect.

    Login or Signup to reply.
  2. Took a while. Hope this helps.

    struct paging: View {
        var body: some View {
            GeometryReader { geometry in
                let width = geometry.size.width
    
                ScrollView(.horizontal) {
                    LazyHStack(spacing: 22) {
                        ForEach(1...10, id: .self) { index in
                            ZStack {
                                Image(((index % 2) != 0) ? .t : .h)
                                    .resizable()
                                    .aspectRatio(contentMode: .fill)
                                    .frame(width: width, height: 450)                                    .scrollTransition(
                                        axis: .horizontal
                                    ) { content, phase in
                                        content.offset(x: phase.value * -(width/2))
                                }
                            }
                             .containerRelativeFrame(.horizontal)
                             .clipShape(RoundedRectangle(cornerRadius: 32))
                        }
                    }
                    .scrollTargetLayout()
                }
                .contentMargins(.horizontal, 44)
                .scrollTargetBehavior(.paging)
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search