Skip to content

Instantly share code, notes, and snippets.

@juliensagot
Created February 16, 2025 14:17
Show Gist options
  • Select an option

  • Save juliensagot/ea5bd3d951b09cf6593d3885f2fbfb23 to your computer and use it in GitHub Desktop.

Select an option

Save juliensagot/ea5bd3d951b09cf6593d3885f2fbfb23 to your computer and use it in GitHub Desktop.

Revisions

  1. juliensagot created this gist Feb 16, 2025.
    196 changes: 196 additions & 0 deletions AquaScrollBar.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,196 @@
    import SwiftUI

    struct AquaScrollBar: View {
    @ScaledMetric private var height = 35.0

    var body: some View {
    Track()
    .frame(height: height)
    .overlay(alignment: .leading) {
    Thumb()
    .containerRelativeFrame(.horizontal) { length, axis in
    length / 3
    }
    .padding(1)
    .foregroundStyle(.tint)
    .padding(.leading, 32)
    }
    .mask {
    Capsule()
    }
    }
    }

    private struct Thumb: View {
    @State private var size: CGSize?

    var body: some View {
    Rectangle()
    .fill(.tint)
    .overlay(
    Rectangle()
    .fill(
    .linearGradient(
    stops: [
    .init(color: .black.opacity(0.4), location: 0.0),
    .init(color: .black.opacity(0), location: 0.9)
    ],
    startPoint: .top,
    endPoint: .bottom
    )
    )
    .blendMode(.plusDarker)
    )
    .overlay(
    Rectangle()
    .fill(
    .linearGradient(
    stops: [
    .init(color: .white.opacity(0.0), location: 0.0),
    .init(color: .white.opacity(0.86), location: 1.0)
    ],
    startPoint: .top,
    endPoint: .bottom
    )
    )
    .blendMode(.overlay)
    )
    .drawingGroup()
    .overlay(
    HStack(spacing: 14) {
    ForEach(0...3, id: \.self) { i in
    Rectangle()
    .fill(
    .linearGradient(
    stops: [
    .init(color: .white.opacity(0.2), location: 0.0),
    .init(color: .white, location: 1.0)
    ],
    startPoint: .top,
    endPoint: .bottom
    )
    )
    }
    }
    .padding(.horizontal, 6)
    .opacity(0.06)
    .blur(radius: 4)
    .blendMode(.plusLighter)
    )
    .overlay(
    LinearGradient(
    stops: [
    .init(color: .white.opacity(0), location: 0.8),
    .init(color: .white.opacity(0.3), location: 0.9),
    .init(color: .white.opacity(1.0), location: 1.0)
    ],
    startPoint: .top,
    endPoint: .bottom
    )
    .opacity(0.8)
    )
    .overlay(alignment: .top) {
    Capsule()
    .fill(
    .linearGradient(
    stops: [
    .init(color: .white.opacity(0.8), location: 0.0),
    .init(color: .white.opacity(0.1), location: 1.0)
    ],
    startPoint: .top,
    endPoint: .bottom
    )
    )
    .blendMode(.plusLighter)
    .overlay(
    Capsule()
    .strokeBorder(
    .linearGradient(
    stops: [
    .init(color: .white.opacity(0.3), location: 0.0),
    .init(color: .white.opacity(0), location: 0.2)
    ],
    startPoint: .top,
    endPoint: .bottom
    ),
    lineWidth: 1
    )
    )
    .frame(height: (size?.height).flatMap({ $0 / 2.3 }))
    .padding(.horizontal, 8)
    .padding(.top, 2)
    }
    .overlay(
    ContainerRelativeShape()
    .stroke(
    .linearGradient(
    colors: [.black.opacity(0.1), .black.opacity(0.4)],
    startPoint: .top,
    endPoint: .bottom
    ),
    lineWidth: 1.5
    )
    .blendMode(.multiply)
    )
    .clipShape(ContainerRelativeShape())
    .containerShape(Capsule())
    .brightness(0.03)
    .shadow(
    color: .black.opacity(0.26),
    radius: 4,
    x: 0,
    y: 2
    )
    .onGeometryChange(for: CGSize.self) { geometry in
    geometry.size
    } action: { newValue in
    size = newValue
    }
    }
    }

    private struct Track: View {
    var body: some View {
    ContainerRelativeShape()
    .fill(
    .linearGradient(
    stops: [
    .init(color: .black.mix(with: .white, by: 0.86), location: 0.0),
    .init(color: .white, location: 0.8)
    ],
    startPoint: .top,
    endPoint: .bottom
    )
    .shadow(
    .inner(
    color: .black.opacity(0.2),
    radius: 4,
    x: 0,
    y: 4
    )
    )
    )
    .overlay(
    ContainerRelativeShape()
    .stroke(.black.opacity(0.1), lineWidth: 1.5)
    )
    .mask {
    ContainerRelativeShape()
    }
    .containerShape(Capsule())
    }
    }

    private extension Color {
    static var aqua: Self {
    return .init(red: 62/255, green: 99/255, blue: 165/255)
    }
    }

    #Preview {
    VStack(spacing: 16) {
    AquaScrollBar()
    .tint(Color.aqua)
    }
    .padding(.horizontal, 64)
    }