Jessica
Jessica

Reputation: 9830

Understanding why the scroll animation works

I am making a scrollTo functionality in my website, and basing it on this answer. Because I don't want to just copy and paste the code, I'm trying to understand it. I was able to understand everything (took me 2 days!) besides for the math part in the scrollToX function.

element.scrollTop = xFrom - (xFrom - xTo) * motion(t01);
t01 += speed * step;

I understand the actual math, but I don't understand why it works. Why does that math make the scroll animation?

JSFiddle

document.getElementsByTagName('button')[0].onclick = function() {
  scrollTo(0, 1000);
}

// Element to move, time in ms to animate
function scrollTo(element, duration) {
  var e = document.documentElement;
  if (e.scrollTop === 0) {
    var t = e.scrollTop;
    ++e.scrollTop;
    e = t + 1 === e.scrollTop-- ? e : document.body;
  }
  scrollToC(e, e.scrollTop, element, duration);
}

// Element to move, element or px from, element or px to, time in ms to animate
function scrollToC(element, from, to, duration) {
  if (duration <= 0) return;
  if (typeof from === "object") from = from.offsetTop;
  if (typeof to === "object") to = to.offsetTop;

  scrollToX(element, from, to, 0, 1 / duration, 20, easeOutCuaic);
}

function scrollToX(element, xFrom, xTo, t01, speed, step, motion) {
  if (t01 < 0 || t01 > 1 || speed <= 0) {
    element.scrollTop = xTo;
    return;
  }
  element.scrollTop = xFrom - (xFrom - xTo) * motion(t01);
  t01 += speed * step;

  setTimeout(function() {
    scrollToX(element, xFrom, xTo, t01, speed, step, motion);
  }, step);
}


function linearTween(t) {
  return t;
}

function easeInQuad(t) {
  return t * t;
}

function easeOutQuad(t) {
  return -t * (t - 2);
}

function easeInOutQuad(t) {
  t /= 0.5;
  if (t < 1) return t * t / 2;
  t--;
  return (t * (t - 2) - 1) / 2;
}

function easeInCuaic(t) {
  return t * t * t;
}

function easeOutCuaic(t) {
  t--;
  return t * t * t + 1;
}

function easeInOutCuaic(t) {
  t /= 0.5;
  if (t < 1) return t * t * t / 2;
  t -= 2;
  return (t * t * t + 2) / 2;
}

function easeInQuart(t) {
  return t * t * t * t;
}

function easeOutQuart(t) {
  t--;
  return -(t * t * t * t - 1);
}

function easeInOutQuart(t) {
  t /= 0.5;
  if (t < 1) return 0.5 * t * t * t * t;
  t -= 2;
  return -(t * t * t * t - 2) / 2;
}

function easeInQuint(t) {
  return t * t * t * t * t;
}

function easeOutQuint(t) {
  t--;
  return t * t * t * t * t + 1;
}

function easeInOutQuint(t) {
  t /= 0.5;
  if (t < 1) return t * t * t * t * t / 2;
  t -= 2;
  return (t * t * t * t * t + 2) / 2;
}

function easeInSine(t) {
  return -Mathf.Cos(t / (Mathf.PI / 2)) + 1;
}

function easeOutSine(t) {
  return Mathf.Sin(t / (Mathf.PI / 2));
}

function easeInOutSine(t) {
  return -(Mathf.Cos(Mathf.PI * t) - 1) / 2;
}

function easeInExpo(t) {
  return Mathf.Pow(2, 10 * (t - 1));
}

function easeOutExpo(t) {
  return -Mathf.Pow(2, -10 * t) + 1;
}

function easeInOutExpo(t) {
  t /= 0.5;
  if (t < 1) return Mathf.Pow(2, 10 * (t - 1)) / 2;
  t--;
  return (-Mathf.Pow(2, -10 * t) + 2) / 2;
}

function easeInCirc(t) {
  return -Mathf.Sqrt(1 - t * t) - 1;
}

function easeOutCirc(t) {
  t--;
  return Mathf.Sqrt(1 - t * t);
}

function easeInOutCirc(t) {
  t /= 0.5;
  if (t < 1) return -(Mathf.Sqrt(1 - t * t) - 1) / 2;
  t -= 2;
  return (Mathf.Sqrt(1 - t * t) + 1) / 2;
}
Very long page.Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page.Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page. Very long page.
<button type="button">To the top</button>

Upvotes: 4

Views: 266

Answers (2)

Luke Kot-Zaniewski
Luke Kot-Zaniewski

Reputation: 1161

So the motion function you happen to pass in your example is easeOutCubic which is defined as

function easeOutCubic(t) {
  t--;
  return t * t * t + 1;
}

First, you increment t by speed * step every time scrollToX is called. Because both of these variables are constant within the scope of the function, t will approach 1 at a constant rate. If you decrement t by 1, it will always yield a negative number greater than -1 since the function works on the interval (0,1). As you keep calling scrollToX then (t-1)^3 + 1 will get a larger but at a slower rate. This is because as t gets constantly larger, then the absolute value of (t-1) gets decremented constantly and because (t-1) is always less than 0, (t-1)^3 will approach 0 at a decreasing rate, and so (t-1)^3 + 1 will approach 1 at the same decreasing rate. This could all be expressed more concisely by the fact that d^2((t-1)^3)/dt^2 < 0 for all t < 1 but decided to give a more drawn out explanation.

You can think of the function motion(t) as a factor between 0 and 1 that you multiply by the end final distance traveled or (xFrom-xTo), to find the total distance traveled at a specific point in time.

Edit (taken from my comments below which I deleted):

t is a variable whose value is directly proportional to the amount of times scrollToX has been called, and because scrollToX gets called at specific time intervals (more or less), that value is a function of time. Now we plug that number into any of the motion functions. Lets now take linearTween as our example since it just returns the input.

As you increase t from 0 to .02 to .04, motion(t) will increase at constant rate. You can multiply motion(t), which will yield a number between 0 and 1 (this value will change with time and eventually reach a value of 1) and multiply it by total distance to be traveled, (xFrom-xTo), to find a lesser distance traveled for a specific t. You subtract this lesser-than-final distance traveled from the point of origin, xFrom, to get the position of the scroll bar at a specific t.

Visual example:

xTo = 100
|
|
||
||
||
xFrom = 200

The first column of of vertical bars represents total distance to be traveled which in this case equals 100. The second row of vertical bars represents the distance traveled when motion(t) = 0.6 (arbitrarily chosen). If we want to find the position of the top of the scrollbar when motion(t) = 0.6 we have to calculate 200-(100)*0.6, or in general form xFrom - (xFrom - xTo)*motion(t).

Upvotes: 2

Mauricio Poppe
Mauricio Poppe

Reputation: 4876

First of all, let me quote Robert Penners definition of motion

a motion is a numerical change in position over time.

The above can be written as:

pf = p0 + motion(time)

where:

- p0 = initial position
- pf = final position

Motion can only exist when time exists and therefore it's a function of time. Since any position is reached through motion from an initial position we can also say that position is a function of time so

position = f(time)

The above is the typical y = f(x) 2d function, let's assume for a moment that the function is a line and that the motion occurs over 1 second, the function will be plotted as

position as a function of time

The slope of the line is given by

m = (pf - p0) / (1 - 0) = pf - p0                 (1)

The slope will help us find px when 0 < t < 1 because the slope is the same for any two points that belong to the line, therefore

m = (px - p0) / (t - 0) = (px - p0) / t           (2)

Substituting (1) in (2) and finding the value of px

pf - p0 = (px - p0) / t
t * (pf - p0) = px - p0
px = p0 + (pf - p0) * t

Which is exactly to your original equation (the minus sign is factorized by some reason in your equation)

Also note that:

  • if t = 0 then px = p0
  • if t = 1 then px = pf

Now to the value of t, we know that 0 <= t <= 1, we can create another function that depends on t e.g. motion(t) with the following requirements:

motion(0) = 0
motion(1) = 1

These conditions must hold for the original px equation to have the correct values, the simplest motion function is the linear or identity function:

const linear = t => t

Another function is the quadratic one:

const quadratic = t => t * t

The position function will be then

px = p0 + (pf - p0) * motion(t)

You'll find a lot of easing functions over the internet, finally, as we know the value of t will increase proportionally to the time elapsed, given an initial time t0 we can compute the next time tf with the same definition of motion

tf = t0 + delta(t)

Now if you want to speed up/slow down your animation, you only have to multiply delta(t) with a number k, if k > 1 then the animation will happen faster k < 1 the animation will happen slower, therefore:

px = p0 + (pf - p0) * motion(t0)
tf = t0 + k * delta(t)
// the final time will be the initial time for the next iteration
t0 = tf

For example, if you want the animation to happen in 0.5 seconds (twice as fast) k = 2, if the duration is 2.0 seconds (twice as slow) k = 0.5, the formula to find the value of k is then:

k = 1 / duration

Upvotes: 2

Related Questions