Spring Animations in Apple Music and Dynamic Island

Spring Animations in Apple Music and Dynamic Island

I love spring animation because it provides a natural continuity and velocity, creating interactions that feel intuitive and lifelike.

However, making the entire experience an extension of our physical world always requires some extra adjustment to a plain spring animation. I’d like to share some insights I gained while building JotJot and my approach to making spring animation more lifelike.

As someone who constantly has random thoughts — whether it’s while walking, waiting in line, or during a set of pushups — I wanted a place to keep them organized. So I developed JotJot, a place for all my dumb thoughts and uncool jokes. I designed it with a pinned notes-like card stack to present my entries.

The interaction is fairly simple:

Staged Spring Animation

Let's first compare staged spring animation with plain spring animation in this context.

Staged spring animation

Staged spring animation

Plain spring animation

Plain spring animation

Technical Deep-Dive

You might or might not notice the key differences. Don’t worry, I’ll walk through them one by one. In the staged animation version, three different springs are at work.

<aside> 1️⃣

Smooth Spring - Gentle Returns When a user swipes the card slightly and lets it go, the card returns to its original position without any bounciness, reflecting the softness of this light gesture.

Spring with no bounciness

Spring with no bounciness

// Use smooth spring for swipe gestures with low-momentum
withAnimation(.smooth(duration: duration)) {
  if 0 - modifier < predictedEndX, predictedEndX < screenWidth - modifier {
    xOffset = 0
    degrees = 0
    return
  }
	...
  }
}

</aside>

<aside> 2️⃣

Bouncy Spring - Reflecting Momentum When users swipe the card off the screen with momentum, I incorporated a slight bounciness to the animation. This rewards the speed and power of their gesture.

Spring with a little bit bounciness

Spring with a little bit bounciness

// Use snappy spring to add a bouncy effect 
// This applies when responding to a stronger swipe gesture.
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
    withAnimation(.snappy(duration: duration)) {
      xOffset = 0
      degrees = 0
    }
  }

</aside>

<aside> 3️⃣

Interactive Spring - Fast Response When users swipe the card left and right and hold it, the animation uses minimal damping, allowing for quick, responsive movement.

Spring with no friction

Spring with no friction

/// Use interactive spring to minimize latency
private func onDragChanged(_ value: _ChangedGesture<DragGesture>.Value) {
    withAnimation(.interactiveSpring) {
    xOffset = value.translation.width
    degrees = Double(value.translation.width / 25)
  }
}

</aside>


The Role of Physics

Initially, everything seemed smooth, but as I used it daily, I felt something was off—the static friction. In the real world, moving a card is harder at the beginning and easier afterward. This initial resistance is hard to blend into the interaction with an interactive spring.

$$ fs,max > fk​ $$