跪拜 Guibai
← Back to the summary

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 / ease easing, 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

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:

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:

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:

3. Layer one: 1:1 direct manipulation, "grab" first, then talk about feel

The first prerequisite of meaty feel is direct manipulation:

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 transition to 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).

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).

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:

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:

Why it matters:

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?

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."

Linear easing vs. spring animation: scroll meaty feel comparison

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):