Skip to content

Elastic Overscroll List

Rubber-band overscroll with the real asymptotic curve, engaged only at the boundary.

DOM + CSS0 deps1 file · 5 KB TSX

Prominimalplayful
Preview

Props

0.55
460

The source is part of Pro

$27/month or $179 once. See Pro

No runtime dependencies
"use client";

import { useRef } from "react";

/**
 * Rubber-band overscroll for a scroll container, for the platforms and
 * containers that do not get it for free: Android, Windows, and anywhere you
 * have set overscroll-behavior to stop the gesture chaining to the page.
 */

/**
 * THE APPLE RUBBER-BAND FUNCTION.
 *
 *   f(x) = (x * d * c) / (d + c * |x|)
 *
 * where d is the container's size and c is roughly 0.55. It is asymptotic: as
 * the drag grows the travel approaches d and never exceeds it, so resistance
 * rises continuously and the limit is never actually reached.

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Details

About this animation

The bounce at the end of an iOS scroll is one curve, and substituting a linear multiplier with a clamp is why most reimplementations feel wrong rather than merely different. The real function divides the drag by a term that grows with the drag, which makes it asymptotic: travel approaches the container's own size and never passes it, resistance rises continuously, and the limit is never actually reached. A clamped linear version hits its maximum almost immediately, so the last two thirds of the gesture produce no response at all — the curve is the effect. Three implementation details decide whether it feels attached to the content. The rubber band engages only when the container is already at a boundary and the drag pushes further, because applying the transform on every move fights the container's own scrolling and the list stutters as the two disagree. The transform is applied to the inner content rather than to the scroller, since transforming a scroll container makes it a containing block for fixed descendants and interferes with its own scrolling. And a new drag reads the current translate off the computed style and reapplies it without a transition, or the content jumps from wherever the previous spring had reached.

Curator’s note

Linear travel with a clamp is the usual stand-in and it feels dead — you reach the clamp instantly and the rest of the gesture does nothing.

Pairs well with