Apple's Scroll Feels "Meaty" Because of Physics: Rubber Bands, Springs, and Why Easing Feels Fake
Core conclusion: Apple's scrolling feels "meaty" not because the animation is fancy, but because it moves according to real physics. Breaking it apart, there are four layers: ① 1:1 direct manipulation (content follows the finger with zero latency, like grabbing something); ② overscroll resistance (at the boundary you can pull a bit more, but it gets heavier and asymptotically saturates, like stretching a rubber band); ③ spring rebound (on release, it returns to target via spring physics, with acceleration and slight overshoot, not linear deceleration); ④ momentum inertia (the faster you flick, the farther it slides). Most web pages use
linear/easeeasing, which is essentially "movement by timetable"—no mass, no acceleration, no boundary elasticity—so it feels "brittle" and weightless. To recreate the meaty feel, you only need to replace "time-driven easing" with "force-driven spring + damping functions."
Table of Contents
- One-sentence conclusion
- What "meaty" really is: not visual, but physical
- Layer one: 1:1 direct manipulation, "grab" first, then talk about feel
- Layer two: overscroll resistance (Rubber Banding)
- Layer three: spring rebound on release (Spring Animation)
- Layer four: momentum inertia (Velocity Projection)
- Why
linear/easeare "fake": timetable vs. physical force
- Why
- Animation curve comparison (measured screenshots)
- Complete runnable code: rubber band + spring solver
- Comparison table: symptom → cause → solution
- Judgment wrap-up
1. One-sentence conclusion
First, build an intuition 🎭: Apple's scrolling is like "pushing a ball attached to a spring"—you push it, it follows; push to the end, the spring stretches, the farther the harder; release, the spring pulls the ball back, with a slight wobble. A normal web scroll is like "dragging a piece of paper taped to a wall"—it stops at the wall, no bounce, no weight.
The four layers of physics map to four kinds of feel:
- Direct manipulation: makes you feel "I grabbed it."
- Overscroll resistance: makes you feel "it has a boundary, but the boundary is soft."
- Spring rebound: makes you feel "it has mass and wobbles."
- Momentum inertia: makes you feel "it remembers how fast I flicked."
One sentence: meaty feel = replacing "time-based animation" with "physics-based motion."
2. What "meaty" really is: not visual, but physical
Many people assume "meaty" is a visual style (colors, shadows, rounded corners). Actually, it's entirely kinesthetic:
- The same interface, sliding with
ease→ "hard, brittle, plastic." - Switching to spring physics → "soft, heavy, meaty."
- The difference isn't in appearance, but in whether the motion follows physical laws.
WWDC18's Designing Fluid Interfaces explains it best: think of the scrolling content as a ball attached to a spring—one end of the spring is the "currently displayed position," the other end is the "position the content wants to go to due to elasticity," and the spring's tension determines how the content moves.
| Dimension | Normal easing | Apple meaty feel |
|---|---|---|
| Drive method | Timetable (0.3s to complete) | Physical force (spring tension) |
| Has mass | No | Yes (inertia) |
| Boundary behavior | Hard stop | Soft boundary (overscroll resistance) |
| Ending | Stops exactly at the point | Converges with slight overshoot |
| Feel | Brittle, plastic | Heavy, meaty |
References:
- Designing Fluid Interfaces -- WWDC18 ⭐ Worth reading
3. Layer one: 1:1 direct manipulation, "grab" first, then talk about feel
The first prerequisite of meaty feel is direct manipulation:
- When the finger moves 1px, the content moves 1px—perfectly 1:1 within the boundary.
- No latency allowed: any "smooth following" (like
transition: 0.1s) makes it feel "slippery, unresponsive." - No easing either: adding easing during the drag phase = hand and content are "disconnected."
Analogy 🎭: This is like a "claw machine"—the claw follows the joystick, so you feel in control; if the claw lags half a second, you immediately think "this machine is broken."
⚠️ Common mistake: adding
transitionto the drag itself. Correct approach: during the drag, displacement equals the finger displacement directly (no transition); only after release do you hand it to the spring animation.
4. Layer two: overscroll resistance (Rubber Banding)
Once direct manipulation is correct, the most recognizable layer is overscroll resistance (rubber banding).
- Within the boundary: content follows 1:1.
- Beyond the boundary: actual displacement = finger displacement compressed by a resistance function—the more you pull, the heavier it gets, asymptotically saturating (you can never pull it to infinity).
- Release: springs back to the boundary.
Apple-style resistance formula (common industry approximation):
$$ \text{offset} = \text{limit} + \frac{\text{overshoot} \times d \times c}{d + c \times |\text{overshoot}|} $$
Where overshoot is the distance the finger goes beyond the boundary, d is the container dimension, c is the resistance constant (commonly about 0.55).
- When
overshootis small, the expression approximates linear (direct manipulation). - When
overshootis large, the denominator is dominated by|overshoot|, and the result approaches a constant—the harder you pull, the less it moves.
Analogy 🎭: This is like "stretching a rubber band"—pulling it is easy at first, but gets increasingly hard, and eventually almost impossible. The "meaty" feel comes from this asymptotic saturation curve.
5. Layer three: spring rebound on release (Spring Animation)
After release, the content doesn't "return at a constant speed," but rebounds according to spring physics:
- Acceleration comes from spring force: the farther from the target, the stronger the pull (
F = -k × displacement). - Damping consumes energy: the higher the damping, the less it wobbles and the faster it stops.
- Critical damping: just enough to not overshoot, directly converge—Apple's default UI animations often use this (
damping 1.0,response 0.3–0.4s). - Underdamping: slight overshoot then settle back—used for "momentum" scenarios like flicks and page turns (
damping ≈ 0.8).
Two key parameters:
| Parameter | Meaning | Apple typical value |
|---|---|---|
| response | Approximate time to reach target | 0.3–0.4s |
| damping | Damping ratio (1 = critical, <1 causes overshoot) | default 1.0; flick 0.8 |
Analogy 🎭: This is like "pushing a door"—push gently, it slowly returns to closed (critical damping); push hard, it "thuds" against the frame and bounces back (underdamped overshoot). The meaty feel comes from "it wobbles, but it's controlled."
6. Layer four: momentum inertia (Velocity Projection)
The fourth layer is the most easily overlooked, yet it most affects the "fluid" feel—momentum:
- At the moment of release, the system reads the finger's velocity, predicts "where it would stop if you kept sliding at this speed."
- The faster the velocity, the farther the target (a flick can slide a long way).
- Then it hands this predicted target to the spring, which eases toward it.
Why it matters:
- Without momentum: release stops immediately, like being grabbed hard after the brakes fail.
- With momentum: it keeps sliding, gradually decelerating to a stop—like a real object with inertia.
Analogy 🎭: This is like "throwing a paper airplane"—the faster you throw, the farther it flies, then it gradually descends under drag. Part of the meaty feel is "the force I flicked with is remembered."
7. Why linear / ease are "fake": timetable vs. physical force
Now we can answer the core question: why do linear / ease have no meaty feel?
linear/easeare a "timetable": they specify "move from A to B in 0.3 seconds," then interpolate along a fixed curve—no mass, no acceleration, no boundary elasticity.- A spring is a "physical force": each frame computes "spring force → acceleration → velocity → new position"—it has mass, can overshoot, and is subject to damping.
| Comparison | CSS easing | Spring physics |
|---|---|---|
| Ending | Stops exactly at the point | Converges, may slightly overshoot |
| Interrupted mid-way | Recomputes from scratch | Preserves current velocity, continues smoothly |
| Boundary | No concept | Can do overscroll resistance |
| Feel | Mechanical, exact | Organic, heavy |
The key difference is "interruptibility": when the finger changes direction mid-way, a spring animation can preserve the current velocity and continue (hence smooth), while a CSS easing animation "restarts" (hence a stutter).
💡 This is also why Apple emphasizes "use springs instead of easing"—not because springs are "prettier," but because springs remain continuous through multiple interruptions.
8. Animation curve comparison (measured screenshots)
The image below compares two implementations of the same action: on the left, normal scrolling (hard boundary stop); on the right, Apple's meaty feel (overscroll resistance + rebound). The bottom half shows time-displacement curves for "linear easing" vs. "spring animation."

- Left curve: hits the boundary and cuts off abruptly, forming a hard corner—the feel is "brittle."
- Right curve: after overscroll, it smoothly converges with a small rebound—the feel is "meaty."
- Bottom curves: red is a uniform straight line (no physics); green overshoots the target and then settles back (spring).
9. Complete runnable code: rubber band + spring solver
The following is a copy-paste Python code implementing two core functions: rubber_band() for overscroll resistance and spring_step() for spring physics integration. No third-party libraries needed; it directly prints displacement sequences so you can see the "heavier the pull" and "overshoot and settle."
# The two core pieces of Apple's meaty feel: overscroll resistance + spring solver
def rubber_band(overshoot, dimension, constant=0.55):
"""
Overscroll resistance: compresses the overscroll displacement into an asymptotically saturating real displacement
Args:
overshoot: the distance the finger goes beyond the boundary (>0)
dimension: container dimension (e.g., screen height)
constant: resistance constant, smaller means "harder"
Returns: compressed real overscroll displacement
"""
return (overshoot * dimension * constant) / (dimension + constant * abs(overshoot))
def spring_step(x, v, target, stiffness=180.0, damping=20.0, dt=1/60):
"""
One step of spring physics integration: returns new position and velocity
Args:
x: current position, v: current velocity, target: target position
stiffness: stiffness (higher = faster return)
damping: damping (higher = less wobble)
dt: time step (frame interval)
Returns: (new position, new velocity)
"""
force = -stiffness * (x - target) # Spring force: farther from target means stronger pull
accel = force - damping * v # Acceleration = spring force - damping
v = v + accel * dt # Velocity integration
x = x + v * dt # Position integration
return x, v
if __name__ == "__main__":
# ① Overscroll resistance: the harder you pull, the slower real displacement grows, asymptotically saturating (never reaching infinity)
print("Overscroll resistance: the farther you pull, the slower real displacement grows")
for pull in [0, 50, 100, 200, 400, 800]:
real = rubber_band(pull, 800)
print(f" Finger overshoot {pull:>3}px → actual displacement {real:6.1f}px")
# ② Spring rebound: from an offset position back to 0, with slight overshoot
print("\nSpring rebound (target 0, initial offset 100)")
x, v = 100.0, 0.0
for frame in range(40):
x, v = spring_step(x, v, 0.0)
if frame % 4 == 0: # print every 4 frames
print(f" Frame {frame:>2}: position {x:7.2f}, velocity {v:8.2f}")
How to read this code: rubber_band() is the resistance formula from section 4—you can see that when you pull 400px the actual displacement is only about 150px, and even at 800px it doesn't reach 200px, never reaching infinity; spring_step() is the spring integration from section 5—force = -stiffness * (x - target) is Hooke's law, subtracting damping * v dissipates energy, and the position will overshoot past 0 then rebound (you'll see negative values in the printed output). Hook these functions into a browser (call spring_step each frame, use rubber_band for overscroll displacement), and you have a minimal viable "Apple meaty feel."
10. Comparison table: symptom → cause → solution
| Symptom | Cause | Solution |
|---|---|---|
| Scrolling feels "brittle," no weight | Using linear / ease easing |
Switch to spring physics (stiffness + damping) |
| Drag feels "slippery, unresponsive" | Added transition during drag |
During drag, displacement is 1:1; only animate on release |
| Hitting the boundary feels like "hitting a wall" | No overscroll resistance | Add rubber band damping function |
| Stops immediately on release, no inertia | Didn't read velocity for momentum prediction | Read release velocity → project target position |
| Stutter when changing direction mid-way | Easing recomputes when interrupted | Use spring animation that preserves velocity |
| Rebound wobbles too much, causes dizziness | Damping ratio too low (overshoot too large) | Increase damping (closer to 1.0) |
11. Judgment wrap-up
When to apply this physics: your product has drag, scroll, page turn, drawer such direct operations, and you want a "premium, heavy" feel—in that case spring + overscroll resistance is almost a standard, with a high return on investment. When you can skip it: purely forms, low-frequency clicks in admin pages, or very low-performance devices—a simple ease is enough there, and adding physics animation only adds complexity and performance burden. The single judgment criterion: does the user "drag" the interface with their hand? If yes, it's worth doing the meaty feel; if they only click without dragging, don't add complexity to the animation.
This column Full-Stack Development covers frontend engineering, UI & interaction design, and developer toolchains. Each article includes runnable code and real-world comparisons, continuously updated. To build interfaces with "feel," you can subscribe on the column page.
References (combined):