Skip to content

Instantly share code, notes, and snippets.

@Matt54
Created June 13, 2024 12:21
Show Gist options
  • Save Matt54/b4c1357220a77bba34f1202a672f1166 to your computer and use it in GitHub Desktop.
Save Matt54/b4c1357220a77bba34f1202a672f1166 to your computer and use it in GitHub Desktop.
An animating mesh gradient view with an animating mask
import SwiftUI
struct AnimatingMaskedMeshView: View {
let referenceDate: Date = .now
@State private var mainPosition: CGPoint = .zero
@State private var positions: [CGPoint] = []
private let blurRadius = 20.0
private let alphaThreshold = 0.875
private let circleCount = 20
var body: some View {
TimelineView(.animation) { context in
let t = context.date.timeIntervalSince(referenceDate)*5
MeshGradient(width: 5, height: 4, points: [
[0, 0], [0.25, 0], [0.5, 0], [0.75, 0], [1, 0],
[0, 0.333],
[value(in: 0.0...0.2, offset: 0.1, timeScale: 0.2, t: t), value(in: 0.25...0.4, offset: 0.1, timeScale: 0.3, t: t)],
[value(in: 0.4...0.6, offset: 0.05, timeScale: 0.3, t: t), value(in: 0.2...0.4, offset: 0.15, timeScale: 0.3, t: t)],
[value(in: 0.8...1.0, offset: 0.1, timeScale: 0.3, t: t), value(in: 0.15...0.3, offset: 0.1, timeScale: 0.4, t: t)],
[1, 0.333],
[0, 0.667],
[value(in: 0.2...0.3, offset: 0.05, timeScale: 0.3, t: t), value(in: 0.6...0.95, offset: 0.1, timeScale: 0.4, t: t)],
[value(in: 0.4...0.6, offset: 0.07, timeScale: 0.25, t: t), value(in: 0.6...0.9, offset: 0.1, timeScale: 0.3, t: t)],
[value(in: 0.8...0.9, offset: 0.06, timeScale: 0.3, t: t), value(in: 0.6...0.8, offset: 0.12, timeScale: 0.3, t: t)],
[1, 0.667],
[0, 1], [0.25, 1], [0.5, 1], [0.75, 1], [1, 1],
], colors: [
.red, .red, .orange, .red, .red,
.red, .orange, .yellow, .orange, .red,
.red, .red, .orange, .red, .red,
.red, .red, .red, .red, .red
])
}
.blur(radius: 20)
.saturation(3.0)
.mask {
TimelineView(.animation(minimumInterval: 0.75)) { timeline in
let currentDate = timeline.date.timeIntervalSinceReferenceDate
let randomSeed = Int(currentDate / 0.75)
GeometryReader { geometry in
let size = geometry.size
Canvas { context, canvasSize in
let circles = (0...positions.count-1).map { tag in
context.resolveSymbol(id: tag)!
}
context.addFilter(.alphaThreshold(min: alphaThreshold))
context.addFilter(.blur(radius: blurRadius))
context.drawLayer { context2 in
circles.forEach { circle in
context2.draw(circle,
at: .init(x: canvasSize.width/2,
y: canvasSize.height/2)
)
}
}
} symbols: {
ForEach(positions.indices, id: \.self) { id in
Circle()
.frame(width: id == 0 ? size.width/2 - blurRadius/alphaThreshold : size.width/4)
.offset(x: id == 0 ? mainPosition.x : positions[id].x,
y: id == 0 ? mainPosition.y : positions[id].y)
.tag(id)
}
}
.onChange(of: randomSeed) { _, _ in
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) {
mainPosition = randomPosition(in: size, circleSize: .init(width: size.width/4, height: size.width/4))
positions = (0..<circleCount).map { _ in
randomPosition(in: size, circleSize: .init(width: size.width/2, height: size.width/2))
}
}
}
.onAppear {
positions = Array(repeating: .zero, count: circleCount)
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) {
mainPosition = randomPosition(in: size, circleSize: .init(width: size.width/4, height: size.width/4))
positions = positions.map { _ in
randomPosition(in: size, circleSize: .init(width: size.width/2, height: size.width/2))
}
}
}
}
}
}
.background(Color.init(red: 0.65, green: 0.25, blue: 0.75))
}
func value(in range: ClosedRange<Float>, offset: Float, timeScale: Float, t: TimeInterval) -> Float {
let amp = (range.upperBound - range.lowerBound) * 0.5
let midPoint = (range.lowerBound + range.upperBound) * 0.5
return midPoint + amp * sin(timeScale * Float(t) + offset)
}
func randomPosition(in bounds: CGSize, circleSize: CGSize) -> CGPoint {
let xRange = circleSize.width / 2 ... bounds.width - circleSize.width / 2
let yRange = circleSize.height / 2 ... bounds.height - circleSize.height / 2
let randomX = CGFloat.random(in: xRange)
let randomY = CGFloat.random(in: yRange)
let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
let offsetX = randomX - center.x
let offsetY = randomY - center.y
print("random position: x: \(offsetX) y: \(offsetY)")
return CGPoint(x: offsetX, y: offsetY)
}
}
#Preview {
AnimatingMaskedMeshView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment