Skip to content

Instantly share code, notes, and snippets.

@Koshimizu-Takehito
Created April 12, 2025 10:59
Show Gist options
  • Save Koshimizu-Takehito/0ca2b9bd7fa1d68057d459952043f87f to your computer and use it in GitHub Desktop.
Save Koshimizu-Takehito/0ca2b9bd7fa1d68057d459952043f87f to your computer and use it in GitHub Desktop.
visualEffectモディファイア
import SwiftUI
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
let scrollViewFrame = geometry.frame(in: .local)
ScrollView {
ForEach(0..<1000) { offset in
RowContent(offset: offset, scrollViewFrame: scrollViewFrame)
}
.padding(.horizontal)
}
}
}
}
private struct RowContent: View {
let offset: Int
let scrollViewFrame: CGRect
@State var zIndex: Double = 0
var body: some View {
RoundedRectangle(cornerRadius: 24)
.fill(.blue)
.frame(height: 100.0)
.onGeometryChange(for: CGRect.self) { $0.frame(in: .scrollView) } action: { newValue in
zIndex = min(newValue.minY, min(scrollViewFrame.midY - newValue.midY, 0))
}
.zIndex(zIndex * Double(offset))
.visualEffect { content, proxy in
let frame = proxy.frame(in: .scrollView(axis: .vertical))
let distance1 = frame.minY
let distance2 = scrollViewFrame.maxY - frame.maxY
let distance = min(distance1, min(distance2, 0))
return content
.hueRotation(.degrees(frame.origin.y / 5))
.scaleEffect(max(1 + distance / 1000, 0))
.offset(y: distance1 < 0 ? -distance : distance)
.brightness(distance1 < 0 ? -distance / 500 : -distance / 200)
}
}
}
#Preview {
ContentView()
}
@Codelaby
Copy link

Thanks for share,
a couple of additional examples, just for the top edge and bottom edge

struct ScrollingStackDemoTop: View {
    var body: some View {
        GeometryReader { geometry in
            let scrollViewFrame = geometry.frame(in: .local)
            ScrollView {
                ForEach(0..<100) { offset in
                    RowContentTop(offset: offset, scrollViewFrame: scrollViewFrame)
                }
                .padding(.horizontal)
            }
        }
    }
    
    private struct RowContentTop: View {
        let offset: Int
        let scrollViewFrame: CGRect
        @State var zIndex: Double = 0
        
        var body: some View {
            RoundedRectangle(cornerRadius: 24)
                .fill(.blue)
                .frame(height: 100.0)
                .onGeometryChange(for: CGRect.self) { $0.frame(in: .scrollView) } action: { newValue in
                    zIndex = min(newValue.minY, min(scrollViewFrame.midY - newValue.midY, 0))
                }
                .zIndex(zIndex * Double(offset))
                .visualEffect { content, proxy in
                    let frame = proxy.frame(in: .scrollView(axis: .vertical))
                    let distance1 = frame.minY
                    let distance2 = scrollViewFrame.maxY - frame.maxY
                    let distance = min(distance1, min(distance2, 0))
                    return content
                        .hueRotation(.degrees(frame.origin.y / 5))
                        .scaleEffect(distance1 < 0 ? max(1 + distance / 1000, 0) : 1, anchor: .top)
                        .offset(y: distance1 < 0 ? -distance : 0)
                        .brightness(distance1 < 0 ? -distance / 500 : 0)
                }
        }
    }
}

#Preview("Top edge") {
    ScrollingStackDemoTop()
}


struct ScrollingStackDemoBottom: View {
    var body: some View {
        GeometryReader { geometry in
            let scrollViewFrame = geometry.frame(in: .local)
            ScrollView {
                ForEach(0..<100) { offset in
                    RowContentBottom(offset: offset, scrollViewFrame: scrollViewFrame)
                }
                .padding(.horizontal)
            }
        }
    }
    
    private struct RowContentBottom: View {
        let offset: Int
        let scrollViewFrame: CGRect
        @State var zIndex: Double = 0
        
        var body: some View {
            RoundedRectangle(cornerRadius: 24)
                .fill(.blue)
                .frame(height: 100.0)
                .onGeometryChange(for: CGRect.self) { $0.frame(in: .scrollView) } action: { newValue in
                    zIndex = min(newValue.minY, min(scrollViewFrame.midY - newValue.midY, 0))
                }
                .zIndex(zIndex * Double(offset))
                .visualEffect { content, proxy in
                    let frame = proxy.frame(in: .scrollView(axis: .vertical))
                    let distance1 = frame.minY
                    let distance2 = scrollViewFrame.maxY - frame.maxY
                    let distance = min(distance1, min(distance2, 0))
                    return content
                        .hueRotation(.degrees(frame.origin.y / 5))
                        .scaleEffect(distance1 < 0 ? 1 : max(1 + distance / 1000, 0))
                        .offset(y: distance1 < 0 ? 0 : distance)
                        .brightness(distance1 < 0 ? 0 : -distance / 200)
                }
        }
    }
}

#Preview("Bottom edge") {
    ScrollingStackDemoBottom()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment