Instantly share code, notes, and snippets.

@dezinezync /scroll.easing.js Secret
Last active Nov 24, 2017

Embed
What would you like to do?
Vanilla Javascript Scrolling function for easing based scrolling
function scrollTo(Y, duration, easingFunction, callback) {
var start = Date.now(),
elem = document.documentElement.scrollTop?document.documentElement:document.body,
from = elem.scrollTop;
if(from === Y) {
callback();
return; /* Prevent scrolling to the Y point if already there */
}
function min(a,b) {
return a<b?a:b;
}
function scroll(timestamp) {
var currentTime = Date.now(),
time = min(1, ((currentTime - start) / duration)),
easedT = easingFunction(time);
elem.scrollTop = (easedT * (Y - from)) + from;
if(time < 1) requestAnimationFrame(scroll);
else
if(callback) callback();
}
requestAnimationFrame(scroll)
}
/* bits and bytes of the scrollTo function inspired by the works of Benjamin DeCock */
/*
* Easing Functions - inspired from http://gizma.com/easing/
* only considering the t value for the range [0, 1] => [0, 1]
*/
var easing = {
// no easing, no acceleration
linear: function (t) { return t },
// accelerating from zero velocity
easeInQuad: function (t) { return t*t },
// decelerating to zero velocity
easeOutQuad: function (t) { return t*(2-t) },
// acceleration until halfway, then deceleration
easeInOutQuad: function (t) { return t<.5 ? 2*t*t : -1+(4-2*t)*t },
// accelerating from zero velocity
easeInCubic: function (t) { return t*t*t },
// decelerating to zero velocity
easeOutCubic: function (t) { return (--t)*t*t+1 },
// acceleration until halfway, then deceleration
easeInOutCubic: function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 },
// accelerating from zero velocity
easeInQuart: function (t) { return t*t*t*t },
// decelerating to zero velocity
easeOutQuart: function (t) { return 1-(--t)*t*t*t },
// acceleration until halfway, then deceleration
easeInOutQuart: function (t) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t },
// accelerating from zero velocity
easeInQuint: function (t) { return t*t*t*t*t },
// decelerating to zero velocity
easeOutQuint: function (t) { return 1+(--t)*t*t*t*t },
// acceleration until halfway, then deceleration
easeInOutQuint: function (t) { return t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t }
}
/* Add the following to you main js file
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller
// fixes from Paul Irish and Tino Zijdel
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}()); */
@alexserver

This comment has been minimized.

Show comment
Hide comment
@alexserver

alexserver Mar 27, 2014

May I have an example of how to use it ?

alexserver commented Mar 27, 2014

May I have an example of how to use it ?

@GrzegorzWidla

This comment has been minimized.

Show comment
Hide comment
@GrzegorzWidla

GrzegorzWidla Aug 5, 2014

@alexserver - in case you or somebody else hasn't figured it out:

scrollTo(0, 800, easing.easeInOutCubic);

Where:

  • 0 is Y position in pixels; 0 specifically means top of the window,
  • 800 is duration of the animation in miliseconds.

You can also add a callback to do something after animation is done:

scrollTo(320, 600, easing.linear, function() { 
    alert('scrolled');  
}

GrzegorzWidla commented Aug 5, 2014

@alexserver - in case you or somebody else hasn't figured it out:

scrollTo(0, 800, easing.easeInOutCubic);

Where:

  • 0 is Y position in pixels; 0 specifically means top of the window,
  • 800 is duration of the animation in miliseconds.

You can also add a callback to do something after animation is done:

scrollTo(320, 600, easing.linear, function() { 
    alert('scrolled');  
}
@dennisbaum

This comment has been minimized.

Show comment
Hide comment
@dennisbaum

dennisbaum Sep 18, 2014

if no callback is definded, and i reload the page, i get an error in line 8
if from === Y

i replaced it with
if(from === Y) {
if(callback) callback();
return; /* Prevent scrolling to the Y point if already there */
}

and tnx for this nice script!

dennisbaum commented Sep 18, 2014

if no callback is definded, and i reload the page, i get an error in line 8
if from === Y

i replaced it with
if(from === Y) {
if(callback) callback();
return; /* Prevent scrolling to the Y point if already there */
}

and tnx for this nice script!

@cybergrunge

This comment has been minimized.

Show comment
Hide comment
@cybergrunge

cybergrunge Dec 4, 2014

Hi, with Firefox (35, Developer Edition), I can't manage to scroll if the current scroll position is on top of the page. Any idea ?

cybergrunge commented Dec 4, 2014

Hi, with Firefox (35, Developer Edition), I can't manage to scroll if the current scroll position is on top of the page. Any idea ?

@Eyal-Shalev

This comment has been minimized.

Show comment
Hide comment
@Eyal-Shalev

Eyal-Shalev Dec 14, 2014

I've forked Nikhil's scroll.easing.js to https://gist.github.com/Eyal-Shalev/24fa303cc98ee1ea4b4a.

Usage example:
var SomeHTMLContainer = document.getElementById('some-scroll-container');
var scrollTime = 1000;
var someCallbackFunction = function() {console.log('hello');};
var scroller = new Scroller(SomeHTMLContainer, scrollTime)
.easing(Scroller.EASING.easeInQuad)
.callback(someCallbackFunction);

var top = 50, left = 150;
scroller.to(top,left);

Eyal-Shalev commented Dec 14, 2014

I've forked Nikhil's scroll.easing.js to https://gist.github.com/Eyal-Shalev/24fa303cc98ee1ea4b4a.

Usage example:
var SomeHTMLContainer = document.getElementById('some-scroll-container');
var scrollTime = 1000;
var someCallbackFunction = function() {console.log('hello');};
var scroller = new Scroller(SomeHTMLContainer, scrollTime)
.easing(Scroller.EASING.easeInQuad)
.callback(someCallbackFunction);

var top = 50, left = 150;
scroller.to(top,left);

@grayghostvisuals

This comment has been minimized.

Show comment
Hide comment
@grayghostvisuals

grayghostvisuals Jan 5, 2015

Here's my example and keeping it Vanilla still (no jQuery). Also to set the scene I placed the event listener on the ul that contains lis with my anchors inside containing the hash values.

<h1 id="table-of-contents">Table of Contents</h1>
<ul>
  <li><a href="#about-the-author">About The Author</a></li>
  <li><a href="#contributing">Contributing</a></li>
  <li><a href="#about-sass">About Sass</a>
    <ul>
      <li><a href="#ruby-sass-or-libsass">Ruby Sass Or LibSass</a></li>
      <li><a href="#sass-or-scss">Sass Or SCSS</a></li>
      <li><a href="#other-preprocessors">Other Preprocessors</a></li>
    </ul>
  </li>
  <li><a href="#introduction">Introduction</a>
    <ul>
      <li><a href="#why-a-styleguide">Why A Styleguide</a></li>
      <li><a href="#disclaimer">Disclaimer</a></li>
      <li><a href="#key-principles">Key Principles</a></li>
    </ul>
  </li>
</ul>
var sgl_toc_list = document.getElementById('table-of-contents').nextElementSibling; // grab the ul wth the links inside it

function sglScrollmation(event) {
  var scroll_speed = 800,
      hash_value   = event.target.attributes.href.value.substring(1), // remove the # from the returned value
      hash_section = document.getElementById(hash_value).offsetTop,
      ease_motion  = easing.easeInOutCubic;

  scrollTo(hash_section, scroll_speed, ease_motion);
}

sgl_toc_list.addEventListener('click', sglScrollmation);

grayghostvisuals commented Jan 5, 2015

Here's my example and keeping it Vanilla still (no jQuery). Also to set the scene I placed the event listener on the ul that contains lis with my anchors inside containing the hash values.

<h1 id="table-of-contents">Table of Contents</h1>
<ul>
  <li><a href="#about-the-author">About The Author</a></li>
  <li><a href="#contributing">Contributing</a></li>
  <li><a href="#about-sass">About Sass</a>
    <ul>
      <li><a href="#ruby-sass-or-libsass">Ruby Sass Or LibSass</a></li>
      <li><a href="#sass-or-scss">Sass Or SCSS</a></li>
      <li><a href="#other-preprocessors">Other Preprocessors</a></li>
    </ul>
  </li>
  <li><a href="#introduction">Introduction</a>
    <ul>
      <li><a href="#why-a-styleguide">Why A Styleguide</a></li>
      <li><a href="#disclaimer">Disclaimer</a></li>
      <li><a href="#key-principles">Key Principles</a></li>
    </ul>
  </li>
</ul>
var sgl_toc_list = document.getElementById('table-of-contents').nextElementSibling; // grab the ul wth the links inside it

function sglScrollmation(event) {
  var scroll_speed = 800,
      hash_value   = event.target.attributes.href.value.substring(1), // remove the # from the returned value
      hash_section = document.getElementById(hash_value).offsetTop,
      ease_motion  = easing.easeInOutCubic;

  scrollTo(hash_section, scroll_speed, ease_motion);
}

sgl_toc_list.addEventListener('click', sglScrollmation);
@azizur

This comment has been minimized.

Show comment
Hide comment
@azizur

azizur Feb 9, 2015

@dezinezync would be kind enough to add some information around under which license is this code being made public? Specifically the easing functions as they are quite different than those found at http://gizma.com/easing/.

The reason I am asking because without a specified license its difficult to use this code in some projects. Also would like to give attribution to the author properly.

azizur commented Feb 9, 2015

@dezinezync would be kind enough to add some information around under which license is this code being made public? Specifically the easing functions as they are quite different than those found at http://gizma.com/easing/.

The reason I am asking because without a specified license its difficult to use this code in some projects. Also would like to give attribution to the author properly.

@Polaris72

This comment has been minimized.

Show comment
Hide comment
@Polaris72

Polaris72 Mar 16, 2015

First: this script is really good, works nice with little payload, well done!
But: When using addEventListener, like grayghostvisuals does, the browser performs the "normal" anchor-jump for a split second and then the nice scrolling takes place, via the script as it is supposed to be.

Any ideas how to prevent the first browser-scroll? I tried "onclick" on the container, instead of addEventListener, didn't work at all.

p.s.
firebug reported an "easedT = easingFunction(time); is not a function"-error on loading a page, so i added:

if (easingFunction)
{
    var currentTime = Date.now(),
    time = min(1, ((currentTime - start) / duration)),
    easedT = easingFunction(time);

    elem.scrollTop = (easedT * (Y - from)) + from;

    if(time < 1) requestAnimationFrame(scroll);
    else if(callback) callback();
}

to function "scroll" to prevent this, which does not affect my primary issue.

Polaris72 commented Mar 16, 2015

First: this script is really good, works nice with little payload, well done!
But: When using addEventListener, like grayghostvisuals does, the browser performs the "normal" anchor-jump for a split second and then the nice scrolling takes place, via the script as it is supposed to be.

Any ideas how to prevent the first browser-scroll? I tried "onclick" on the container, instead of addEventListener, didn't work at all.

p.s.
firebug reported an "easedT = easingFunction(time); is not a function"-error on loading a page, so i added:

if (easingFunction)
{
    var currentTime = Date.now(),
    time = min(1, ((currentTime - start) / duration)),
    easedT = easingFunction(time);

    elem.scrollTop = (easedT * (Y - from)) + from;

    if(time < 1) requestAnimationFrame(scroll);
    else if(callback) callback();
}

to function "scroll" to prevent this, which does not affect my primary issue.

@deanwagman

This comment has been minimized.

Show comment
Hide comment
@deanwagman

deanwagman Mar 17, 2015

There seems to be a bug to where the document will not scroll if scrollTop is 0 in some browsers. And some browsers will always report 0 for scrollTop. Therefore, I've found a patch for line 4.

elem = (document.documentElement.scrollTop++ !== 0) ? document.documentElement : document.body,

This attempts to increment the position to see if changing it does anything. If it does, then use it, else use document.body.

Whew…

deanwagman commented Mar 17, 2015

There seems to be a bug to where the document will not scroll if scrollTop is 0 in some browsers. And some browsers will always report 0 for scrollTop. Therefore, I've found a patch for line 4.

elem = (document.documentElement.scrollTop++ !== 0) ? document.documentElement : document.body,

This attempts to increment the position to see if changing it does anything. If it does, then use it, else use document.body.

Whew…

@deanwagman

This comment has been minimized.

Show comment
Hide comment
@deanwagman

deanwagman Mar 17, 2015

Turns out that wasn't a good fix either. Firefox didn't like the incrementer.

Instead you should set the scrollTop to itself plus one, then check it.

Like so:

                // Used to see which object we should scroll on
        document.documentElement.scrollTop = document.documentElement.scrollTop + 1;

        var start = Date.now(),
            elem = (document.documentElement.scrollTop !== 0) ? document.documentElement : document.body,
            from = elem.scrollTop,
...

Hope that helps.

deanwagman commented Mar 17, 2015

Turns out that wasn't a good fix either. Firefox didn't like the incrementer.

Instead you should set the scrollTop to itself plus one, then check it.

Like so:

                // Used to see which object we should scroll on
        document.documentElement.scrollTop = document.documentElement.scrollTop + 1;

        var start = Date.now(),
            elem = (document.documentElement.scrollTop !== 0) ? document.documentElement : document.body,
            from = elem.scrollTop,
...

Hope that helps.

@marcobiedermann

This comment has been minimized.

Show comment
Hide comment
@marcobiedermann

marcobiedermann Oct 7, 2015

Why not use the build it function Math.min on line 19 instead of creating your own function at line 12?

time = Math.min(1, ((currentTime - start) / duration)),

marcobiedermann commented Oct 7, 2015

Why not use the build it function Math.min on line 19 instead of creating your own function at line 12?

time = Math.min(1, ((currentTime - start) / duration)),
@pawelgrzybek

This comment has been minimized.

Show comment
Hide comment
@pawelgrzybek

pawelgrzybek Jul 5, 2016

This gist was very helpful for me. Because we ended up a bit confused about element detection I put all the things together.

function scrollIt(destination, duration = 200, easing = 'linear', callback) {
  // define timing functions
  let easings = {
    // no easing, no acceleration
    linear(t) {
      return t;
    },
    // accelerating from zero velocity
    easeInQuad(t) {
      return t * t;
    },
    // decelerating to zero velocity
    easeOutQuad(t) {
      return t * (2 - t);
    },
    // acceleration until halfway, then deceleration
    easeInOutQuad(t) {
      return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
    },
    // accelerating from zero velocity
    easeInCubic(t) {
      return t * t * t;
    },
    // decelerating to zero velocity
    easeOutCubic(t) {
      return (--t) * t * t + 1;
    },
    // acceleration until halfway, then deceleration
    easeInOutCubic(t) {
      return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
    },
    // accelerating from zero velocity
    easeInQuart(t) {
      return t * t * t * t;
    },
    // decelerating to zero velocity
    easeOutQuart(t) {
      return 1 - (--t) * t * t * t;
    },
    // acceleration until halfway, then deceleration
    easeInOutQuart(t) {
      return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t;
    },
    // accelerating from zero velocity
    easeInQuint(t) {
      return t * t * t * t * t;
    },
    // decelerating to zero velocity
    easeOutQuint(t) {
      return 1 + (--t) * t * t * t * t;
    },
    // acceleration until halfway, then deceleration
    easeInOutQuint(t) {
      return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t;
    }
  };

  function checkElement() {
    // returns document.documentElement for chrome and safari
    // document.body for rest of the world
    document.documentElement.scrollTop += 1;
    let elm = (document.documentElement.scrollTop !== 0) ? document.documentElement : document.body;
    document.documentElement.scrollTop -= 1;
    return elm;
  }

  let element = checkElement();
  let start = element.scrollTop;
  let startTime = Date.now();

  function scroll() {
    let now = Date.now();
    let time = Math.min(1, ((now - startTime) / duration));
    let timeFunction = easings[easing](time);
    element.scrollTop = (timeFunction * (destination - start)) + start;

    if (element.scrollTop === destination) {
      callback;
      return;
    }
    requestAnimationFrame(scroll);
  }
  scroll();
}

And this is how to use it:

scrollIt(1000, 300, 'easeInQuad', console.log('done'));

or simple version with default arguments if you don't care about duration, easing and callback...

scrollIt(1000);

pawelgrzybek commented Jul 5, 2016

This gist was very helpful for me. Because we ended up a bit confused about element detection I put all the things together.

function scrollIt(destination, duration = 200, easing = 'linear', callback) {
  // define timing functions
  let easings = {
    // no easing, no acceleration
    linear(t) {
      return t;
    },
    // accelerating from zero velocity
    easeInQuad(t) {
      return t * t;
    },
    // decelerating to zero velocity
    easeOutQuad(t) {
      return t * (2 - t);
    },
    // acceleration until halfway, then deceleration
    easeInOutQuad(t) {
      return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
    },
    // accelerating from zero velocity
    easeInCubic(t) {
      return t * t * t;
    },
    // decelerating to zero velocity
    easeOutCubic(t) {
      return (--t) * t * t + 1;
    },
    // acceleration until halfway, then deceleration
    easeInOutCubic(t) {
      return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
    },
    // accelerating from zero velocity
    easeInQuart(t) {
      return t * t * t * t;
    },
    // decelerating to zero velocity
    easeOutQuart(t) {
      return 1 - (--t) * t * t * t;
    },
    // acceleration until halfway, then deceleration
    easeInOutQuart(t) {
      return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t;
    },
    // accelerating from zero velocity
    easeInQuint(t) {
      return t * t * t * t * t;
    },
    // decelerating to zero velocity
    easeOutQuint(t) {
      return 1 + (--t) * t * t * t * t;
    },
    // acceleration until halfway, then deceleration
    easeInOutQuint(t) {
      return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t;
    }
  };

  function checkElement() {
    // returns document.documentElement for chrome and safari
    // document.body for rest of the world
    document.documentElement.scrollTop += 1;
    let elm = (document.documentElement.scrollTop !== 0) ? document.documentElement : document.body;
    document.documentElement.scrollTop -= 1;
    return elm;
  }

  let element = checkElement();
  let start = element.scrollTop;
  let startTime = Date.now();

  function scroll() {
    let now = Date.now();
    let time = Math.min(1, ((now - startTime) / duration));
    let timeFunction = easings[easing](time);
    element.scrollTop = (timeFunction * (destination - start)) + start;

    if (element.scrollTop === destination) {
      callback;
      return;
    }
    requestAnimationFrame(scroll);
  }
  scroll();
}

And this is how to use it:

scrollIt(1000, 300, 'easeInQuad', console.log('done'));

or simple version with default arguments if you don't care about duration, easing and callback...

scrollIt(1000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment