Here I am

home

Implementing elastic overscroll in JavaScript [Part 1]

28 Oct 2013

When scrolling (or panning) normally, the distance the page is moved relative to the viewport is directly proportional to the dragged distance.

`f(x) = x` agraph plot( x ) endagraph

That is until the edge of the page meets an edge of the viewport. To report to the user that this has occured, the page continues to move but it "resists" the force that the user applies.

When I was thinking of what kind of function I could use to achieve this, I immediately thought "exponential decay". A few equations later, I realised that I was wrong but close. Then I thought "LOGARITHMS!"

if `x` is the horizontal overscroll distance, then the resisted value fed back to the user would be

`ln(x)` agraph plot( ln(x) ) endgraph

... almost. The root of `ln(x)` is not `0` We need a function that meets `f(x) = x` at `(0, 0)` tangentially so that the page's movement is continuous and smooth.

`d/dx ln(x) = 1 / x`

`1 / x = 1 => x = 1`

By adding the solution of `d/dx ln(x) = 1` to the parameter passed to `ln`, we effectively shift the curve to the left so that the slope of the curve is 1 at `x = 0`.

`ln(x + 1)` agraph plot( ln(x + 1) ) plot( x ) endagraph But still, the result of `ln(1 + x)` would make for too great a resistance. The obvious solution was to simply multiply by some scalar. That sort of works but the compound curve is no longer continuous.

`4 * ln(x + 1)` agraph xmin=-20; xmax=20; plot( 4 * ln(x + 1) ); plot( f(x) ) endgraph

The "resisted" overscroll would initially be ahead of the actual overscroll for a while before slowing down at the right-most point of intersection in the graph above.

To fix this, we shift the origin to the right so that at `x = 0`, `dy/dx = 1`.

`g(x) = a * ln(x)`

`g'(x) = a / x`

`a / x = 1 => x = a`

`=> g(x + a)`

`=> a * ln(x + a)`


`4 * ln(x + 4)` agraph xmin=-20; xmax=20; a = 4; plot( a * ln(x + a) ); plot( x ) endgraph

Almost there. What's left now is to move the curve downwards by `a * ln(a)`.

`h(x) = a * ln(x + a) - a * ln(a)`

`=> h(x) = a * (ln(x + a) - ln(a))`

agraph xmin=-20; xmax=20; a = 4; plot( a * (ln(x + a) - ln(a)) ); plot( x ) endgraph

Logarithmic resistance with interact.js path snapping

interact.modifiers.snap({
  targets: [function (x, y) {
    var scale = 50;

    return {
      x: scale * Math.log(x + scale) - scale * Math.log(scale),
      y: 75,
      range: Infinity
    };
  }]
}