Skip to content

Instantly share code, notes, and snippets.

@juliensagot
Created February 16, 2025 14:17
Show Gist options
  • Save juliensagot/ea5bd3d951b09cf6593d3885f2fbfb23 to your computer and use it in GitHub Desktop.
Save juliensagot/ea5bd3d951b09cf6593d3885f2fbfb23 to your computer and use it in GitHub Desktop.
Aqua scroll bar in SwiftUI
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)
}
@juliensagot
Copy link
Author

AquaScrollBar

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