I am making an attempt to create a type of liquid animation as seen right here (static picture). A video of the impact will be seen on this youtube video from round 35s mark. Dots spawn on the outermost circle and transfer inwards. As they strategy the innermost circle displaying charging info, the purpose of contact of the dot with the circle type of animates upwards regularly till it makes contact with the transferring dot after which flatlines again to the circumference of the circle. This is my code however the animation shouldn’t be fairly there, the circumference type of abruptly scales up and again down and isn’t fluid.
struct MovingDot: Identifiable {
let id = UUID()
var startAngle: Double
var progress: CGFloat
var scale: CGFloat = 1.0
}
struct BulgeEffect: Form {
var targetAngle: Double
var bulgeHeight: CGFloat
var bulgeWidth: Double
var animatableData: AnimatablePair {
get { AnimatablePair(targetAngle, bulgeHeight) }
set {
targetAngle = newValue.first
bulgeHeight = newValue.second
}
}
func path(in rect: CGRect) -> Path {
let radius = rect.width / 2
var path = Path()
path.transfer(to: CGPoint(x: rect.midX + radius, y: rect.midY))
stride(from: 0, to: 2 * .pi, by: 0.01).forEach { angle in
let distanceFromTarget = abs(angle - targetAngle)
let bulgeEffect = distanceFromTarget < bulgeWidth
? bulgeHeight * (cos(distanceFromTarget / bulgeWidth * .pi) + 1) / 2
: 0
let x = rect.midX + (radius + bulgeEffect) * cos(angle)
let y = rect.midY + (radius + bulgeEffect) * sin(angle)
path.addLine(to: CGPoint(x: x, y: y))
}
path.closeSubpath()
return path
}
}
struct LiquidAnimation: View {
let outerDiameter: CGFloat
let innerDiameter: CGFloat
let dotSize: CGFloat
@State non-public var movingDots: [MovingDot] = []
@State non-public var bulgeHeight: CGFloat = 0
@State non-public var targetAngle: Double = 0
var physique: some View {
ZStack {
ForEach(movingDots) { dot in
Circle()
.body(width: dotSize * 2, top: dotSize * 2)
.scaleEffect(dot.scale)
.place(
x: outerDiameter/2 + cos(dot.startAngle) * (outerDiameter/2 - dot.progress * (outerDiameter/2 - innerDiameter/2)),
y: outerDiameter/2 + sin(dot.startAngle) * (outerDiameter/2 - dot.progress * (outerDiameter/2 - innerDiameter/2))
)
}
BulgeEffect(targetAngle: targetAngle, bulgeHeight: bulgeHeight, bulgeWidth: 0.8)
.fill()
.body(width: innerDiameter, top: innerDiameter)
.animation(.spring(response: 0.3, dampingFraction: 0.7), worth: bulgeHeight)
}
.body(width: outerDiameter, top: outerDiameter)
.onAppear(carry out: startSpawningDots)
}
non-public func startSpawningDots() {
Timer.scheduledTimer(withTimeInterval: Double.random(in: 2...5), repeats: true) { _ in
let startAngle = Double.random(in: 0...(2 * .pi))
let newDot = MovingDot(startAngle: startAngle, progress: 0)
movingDots.append(newDot)
withAnimation(.easeIn(period: 1.5)) {
movingDots[movingDots.count - 1].progress = 0.8
}
// Begin bulge animation when dot is shut
DispatchQueue.essential.asyncAfter(deadline: .now() + 1.2) {
targetAngle = startAngle
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
bulgeHeight = dotSize * 3
}
// Scale up the dot barely
withAnimation(.easeOut(period: 0.3)) {
movingDots[movingDots.count - 1].scale = 1.2
}
}
// Full dot motion and absorption
DispatchQueue.essential.asyncAfter(deadline: .now() + 1.5) {
withAnimation(.easeOut(period: 0.3)) {
movingDots[movingDots.count - 1].progress = 1
movingDots[movingDots.count - 1].scale = 0.1
}
// Collapse bulge
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
bulgeHeight = 0
}
}
// Take away dot
DispatchQueue.essential.asyncAfter(deadline: .now() + 2.0) {
movingDots.removeAll { $0.id == newDot.id }
}
}
}
}
struct ContentView: View {
var physique: some View {
ZStack {
LiquidAnimation(
outerDiameter: 350,
innerDiameter: 150,
dotSize: 4
)
}
}
}
How can I obtain the identical impact as within the video ?