Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ScrollTo animation using pure javascript and no jquery
document.getElementsByTagName('button')[0].onclick = function () {
scrollTo(document.body, 0, 1250);
}
function scrollTo(element, to, duration) {
var start = element.scrollTop,
change = to - start,
currentTime = 0,
increment = 20;
var animateScroll = function(){
currentTime += increment;
var val = Math.easeInOutQuad(currentTime, start, change, duration);
element.scrollTop = val;
if(currentTime < duration) {
setTimeout(animateScroll, increment);
}
};
animateScroll();
}
//t = current time
//b = start value
//c = change in value
//d = duration
Math.easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};
@magm84

This comment has been minimized.

Copy link

@magm84 magm84 commented Apr 5, 2017

thank is very useful!

@DanielGBullido

This comment has been minimized.

Copy link

@DanielGBullido DanielGBullido commented Aug 21, 2017

Thanks!

@LordZombi

This comment has been minimized.

Copy link

@LordZombi LordZombi commented Sep 6, 2017

not working on firefox and IE ... it's because of document.body, you have to use document.documentElement for firefox and IE

@piotr-placzek

This comment has been minimized.

Copy link

@piotr-placzek piotr-placzek commented Nov 24, 2017

I struggled with this problem some time ago.
I did not want to load the jQuery library for this reason.
Your easeInOutQuad function solved my problem.
My class you can find here.
Thanks for you.

@tarun-dugar

This comment has been minimized.

Copy link

@tarun-dugar tarun-dugar commented Dec 28, 2017

Made a small lib for smooth scroll animations. Check it out: https://github.com/tarun-dugar/easy-scroll

@i3web

This comment has been minimized.

Copy link

@i3web i3web commented Jan 10, 2018

awsome!it works

@notrealdev

This comment has been minimized.

Copy link

@notrealdev notrealdev commented Feb 8, 2018

document.documentElemen it working!!
document.body not works for me

@cnanders

This comment has been minimized.

Copy link

@cnanders cnanders commented Feb 20, 2018

Thank you! Agree, this is very useful.

@exactlyaaron

This comment has been minimized.

Copy link

@exactlyaaron exactlyaaron commented Mar 16, 2018

Awesome and simple solution! Super useful 👍

@naeemrind

This comment has been minimized.

Copy link

@naeemrind naeemrind commented Mar 17, 2018

Thanks :)

@JullietV

This comment has been minimized.

Copy link

@JullietV JullietV commented Mar 20, 2018

Thank you very much!

@staradayev

This comment has been minimized.

Copy link

@staradayev staradayev commented Mar 22, 2018

Thx you this approach is awesome!

But small update:

document.body.scrollTop is deprecated and not works now.

please change to document.documentElement.scrollTop

@kimwes

This comment has been minimized.

Copy link

@kimwes kimwes commented Apr 7, 2018

const
scrollTo = function(to, duration) {
    const
    element = document.scrollingElement || document.documentElement,
    start = element.scrollTop,
    change = to - start,
    startDate = +new Date(),
    // t = current time
    // b = start value
    // c = change in value
    // d = duration
    easeInOutQuad = function(t, b, c, d) {
        t /= d/2;
        if (t < 1) return c/2*t*t + b;
        t--;
        return -c/2 * (t*(t-2) - 1) + b;
    },
    animateScroll = function() {
        const currentDate = +new Date();
        const currentTime = currentDate - startDate;
        element.scrollTop = parseInt(easeInOutQuad(currentTime, start, change, duration));
        if(currentTime < duration) {
            requestAnimationFrame(animateScroll);
        }
        else {
            element.scrollTop = to;
        }
    };
    animateScroll();
};

Here's the code a bit modernized. Now it's a lot smoother and works in Safari as well. If you don't work with babel then replace const with var.

@wellitongervickas

This comment has been minimized.

Copy link

@wellitongervickas wellitongervickas commented Apr 12, 2018

I have changed you code for actual patterns
Thanks, works for me!

function scrollTo(element, to = 0, duration= 1000) {

    const start = element.scrollTop;
    const change = to - start;
    const increment = 20;
    let currentTime = 0;

    const animateScroll = (() => {

      currentTime += increment;

      const val = Math.easeInOutQuad(currentTime, start, change, duration);

      element.scrollTop = val;

      if (currentTime < duration) {
        setTimeout(animateScroll, increment);
      }
    });

    animateScroll();
  };

  Math.easeInOutQuad = function (t, b, c, d) {

    t /= d/2;
    if (t < 1) return c/2*t*t + b;
    t--;
    return -c/2 * (t*(t-2) - 1) + b;
  };

Ex: scrollTo(document.documentElement);
@albannurkollari

This comment has been minimized.

Copy link

@albannurkollari albannurkollari commented May 14, 2018

Thank you very, very much. Much helpful. One tip though, I'd suggest updating to ES6 standards, you know like replcaing var with let or const and arrow functions etc.

@manufufu

This comment has been minimized.

Copy link

@manufufu manufufu commented May 16, 2018

Can anyone provide an example on how to use this code? :(

@JHoelzel

This comment has been minimized.

Copy link

@JHoelzel JHoelzel commented May 21, 2018

Thanks guys!

Bonus points:
Scroll an element into view but center it on screeen too:

const nextItem = document.getElementById(nextName);
if (nextItem !== null) {
    const middle = (nextItem.getBoundingClientRect().top + window.pageYOffset) - (window.innerHeight / 2);
    scrollTo(document.body, middle, 1250);
}

@JHoelzel

This comment has been minimized.

Copy link

@JHoelzel JHoelzel commented May 21, 2018

Bonus Bonus Points:
a) rename the function so it doesnt actually override "scrollto" for the window
b) add a flag that prevents "upwards" scrolling, yes that may only be my usecase but YMMV

   //Scrollto copied from github.com/andjosh https://gist.github.com/andjosh/6764939
      function cstmScrollTo(element, to = 0, duration = 1000, onlyDown) {

          const start = element.scrollTop;
          if(onlyDown && (start > to)){
             return;
          }
          const change = to - start;
          const increment = 20;
          let currentTime = 0;

          const animateScroll = (() => {

              currentTime += increment;

              const val = Math.easeInOutQuad(currentTime, start, change, duration);

              element.scrollTop = val;

              if (currentTime < duration) {
                  setTimeout(animateScroll, increment);
              }
          });

          animateScroll();
      };

      Math.easeInOutQuad = function (t, b, c, d) {

          t /= d / 2;
          if (t < 1) return c / 2 * t * t + b;
          t--;
          return -c / 2 * (t * (t - 2) - 1) + b;
      };
..........
usage:
const nextItem = document.getElementById(nextName);
if (nextItem !== null) {
  const middle = (nextItem.getBoundingClientRect().top + window.pageYOffset) - (window.innerHeight / 2);
  cstmScrollTo(document.body, middle, 1250);
}



@kingname

This comment has been minimized.

Copy link

@kingname kingname commented Jun 6, 2018

maybe scrollBy is also useful.

@kriodis

This comment has been minimized.

Copy link

@kriodis kriodis commented Jul 3, 2018

for typescript in angular 6
//animation for scroll
//duration set in 1000 ms
public scrollTo() {
if (window.scrollY !== 0) {
console.log("entro al rolling");
const
start = window.scrollY,
change = 0 - start,
startDate = +new Date(),
easeInOutQuad = function (t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
},
animateScroll = function () {
const currentDate = +new Date();
const currentTime = currentDate - startDate;
const val = parseInt(easeInOutQuad(currentTime, start, change, 1000));
window.scroll(0, val);
if (currentTime < 1000) {
requestAnimationFrame(animateScroll);
}
else {
window.scroll(0, 0);
}
};
animateScroll();
}
}

@AdaltonLeite

This comment has been minimized.

Copy link

@AdaltonLeite AdaltonLeite commented Jul 12, 2018

Thank you very much! 😄

@Cdvalencia

This comment has been minimized.

Copy link

@Cdvalencia Cdvalencia commented Aug 22, 2018

thanks, this example used it, with react.js, so:

constructor(props){
   super(props);
   this.scrollTo = this.scrollTo.bind(this);
 }; 

 scrollDown(num) {    
   console.log(num)
   this.scrollTo(document.body, num, 1250);
 }

 scrollTo(element, to, duration) {
     var start = document.scrollingElement.scrollTop,
         change = to - start,
         currentTime = 0,
         increment = 20;

     var animateScroll = function(){
         var easeInOutQuad = function (t, b, c, d) {
           t /= d/2;
         	if (t < 1) return c/2*t*t + b;
         	t--;
         	return -c/2 * (t*(t-2) - 1) + b;
         };
         currentTime += increment;
         var val = easeInOutQuad(currentTime, start, change, duration);
         document.scrollingElement.scrollTop = val;
         if(currentTime < duration) {
             setTimeout(animateScroll, increment);
         }
     };
     animateScroll();
 }

@hugotox

This comment has been minimized.

Copy link

@hugotox hugotox commented Sep 5, 2018

Just curious, why mutate the global Math object?

@tomaswolfgang

This comment has been minimized.

Copy link

@tomaswolfgang tomaswolfgang commented Sep 6, 2018

If you don't want to deal with any sort of intense implementation and you don't care about the easing function, duration, animation speed or any particulars:
scrollTo(ypos){ window.scrollTo({ top: ypos, behavior: "smooth" }) }
This should do the trick

@vinczebalazs

This comment has been minimized.

Copy link

@vinczebalazs vinczebalazs commented Sep 11, 2018

Thanks so much, great solution! :)

@c-emil

This comment has been minimized.

Copy link

@c-emil c-emil commented Sep 18, 2018

@tomaswolfgang window.scrollTo is very nice solution as long as you need to scroll window. It's unfortunately not usable for any element on the page.

@DenysMorozov

This comment has been minimized.

Copy link

@DenysMorozov DenysMorozov commented Oct 3, 2018

@tomaswolfgang window.scrollTo is very nice solution as long as you need to scroll window. It's unfortunately not usable for any element on the page.

const scrolEl = document.querySelector('.modal-window-container');
const x = elX - (scrolEl.clientWidth / 2);
const y = elY - (scrolEl.clientHeight / 2);
scrolEl.scrollTo(x, y);

Everything works fine for us.

@whossein

This comment has been minimized.

Copy link

@whossein whossein commented Nov 22, 2018

const
scrollTo = function(to, duration) {
    const
    element = document.scrollingElement || document.documentElement,
    start = element.scrollTop,
    change = to - start,
    startDate = +new Date(),
    // t = current time
    // b = start value
    // c = change in value
    // d = duration
    easeInOutQuad = function(t, b, c, d) {
        t /= d/2;
        if (t < 1) return c/2*t*t + b;
        t--;
        return -c/2 * (t*(t-2) - 1) + b;
    },
    animateScroll = function() {
        const currentDate = +new Date();
        const currentTime = currentDate - startDate;
        element.scrollTop = parseInt(easeInOutQuad(currentTime, start, change, duration));
        if(currentTime < duration) {
            requestAnimationFrame(animateScroll);
        }
        else {
            element.scrollTop = to;
        }
    };
    animateScroll();
};

Here's the code a bit modernized. Now it's a lot smoother and works in Safari as well. If you don't work with babel then replace const with var.

thanks very much!

@ClausClaus

This comment has been minimized.

Copy link

@ClausClaus ClausClaus commented Nov 22, 2018

Thanks!!

@felipenmoura

This comment has been minimized.

Copy link

@felipenmoura felipenmoura commented Dec 9, 2018

Very nice.

I forked it and made a few changes:
https://gist.github.com/felipenmoura/650e7e1292c1e7638bcf6c9f9aeb9dd5

  • It returns a promise that resolves when the animation is done
  • It accepts an element as coordinate and scrolls to it (also works with a selector like #some-section-id)
  • It will not overwrite any existing public structure (like the scrollTo native function or Math's prototype)
  • Has a default duration
  • It also uses the document.scrollingElement and you don't need to send document, document.body, window or document.documentElement according to your page structure

I hope it helps and thanks for both inspiring it making it public ;)

@sundayhd

This comment has been minimized.

Copy link

@sundayhd sundayhd commented Apr 26, 2019


function scrollTo(element, to = 0, duration= 1000, scrollToDone = null) {
    const start = element.scrollTop;
    const change = to - start;
    const increment = 20;
    let currentTime = 0;

    const animateScroll = (() => {

      currentTime += increment;

      const val = Math.easeInOutQuad(currentTime, start, change, duration);

      element.scrollTop = val;

      if (currentTime < duration) {
        setTimeout(animateScroll, increment);
	} else {
		if (scrollToDone) scrollToDone();
	}
    });

    animateScroll();
  };

  Math.easeInOutQuad = function (t, b, c, d) {

    t /= d/2;
    if (t < 1) return c/2*t*t + b;
    t--;
    return -c/2 * (t*(t-2) - 1) + b;
  };

Thanks guys!! I added "scrollToDone" to execute callback function when scrolling done.

usage example:
scrollTo(document.body, 500, 1000, () => { console.log("Done with scrolling !!!!") }

@ruucm

This comment has been minimized.

Copy link

@ruucm ruucm commented May 6, 2019

Thanks a lot!

@marsd

This comment has been minimized.

Copy link

@marsd marsd commented May 9, 2019

Has anyone added more easing functions?

@orrybaram

This comment has been minimized.

Copy link

@orrybaram orrybaram commented Jun 18, 2019

Here's a typescript version if anyone is interested:

type EaseInOutQuadOptions = {
  currentTime: number;
  start: number;
  change: number;
  duration: number;
};

const easeInOutQuad = ({
  currentTime,
  start,
  change,
  duration,
}: EaseInOutQuadOptions) => {
  let newCurrentTime = currentTime;
  newCurrentTime /= duration / 2;

  if (newCurrentTime < 1) {
    return (change / 2) * newCurrentTime * newCurrentTime + start;
  }

  newCurrentTime -= 1;
  return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
};

type SmoothScrollOptions = {
  duration: number;
  element: HTMLElement;
  to: number;
};
export default function smoothScroll({
  duration,
  element,
  to,
}: SmoothScrollOptions) {
  const start = element.scrollTop;
  const change = to - start;
  const startDate = new Date().getTime();

  const animateScroll = () => {
    const currentDate = new Date().getTime();
    const currentTime = currentDate - startDate;
    element.scrollTop = easeInOutQuad({
      currentTime,
      start,
      change,
      duration,
    });

    if (currentTime < duration) {
      requestAnimationFrame(animateScroll);
    } else {
      element.scrollTop = to;
    }
  };
  animateScroll();
}
@rlawnsgh78

This comment has been minimized.

Copy link

@rlawnsgh78 rlawnsgh78 commented Aug 21, 2019

	function scrollTo(element, durationTop, durationLeft) {
	    var startTop = element.scrollTop,
	    	startLeft = element.scrollLeft,
	        changeTop = durationTop - startTop,
	        changeLeft = durationLeft - startLeft,//window.scrollX - startLeft,
	        currentTimeTop = 0,
	        currentTimeLeft = 0,
	        increment = 20;
	       
	    var animateScrollTop = function(){        
	        currentTimeTop += increment;
	        var val = Math.easeInOutQuad(currentTimeTop, startTop, changeTop, durationTop);
	        element.scrollTop = val;
	        if(currentTimeTop < durationTop) {
	            setTimeout(animateScrollTop, increment);
	        }
		};

		var animateScrollLeft = function(){        
	        currentTimeLeft += increment;
	        var val = Math.easeInOutQuad(currentTimeLeft, startLeft, changeLeft, durationLeft);
	        element.scrollLeft = val;
	        if(currentTimeLeft < durationLeft) {
	            setTimeout(animateScrollLeft, increment);
	        }
		};

		animateScrollTop();
		animateScrollLeft();
	}

//t = current time
//b = start value
//c = change in value
//d = duration
Math.easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2tt + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};

@darkhorse-coder

This comment has been minimized.

Copy link

@darkhorse-coder darkhorse-coder commented Nov 20, 2019

@andjosh Thank you!! Very useful!!

@joloiuhj

This comment has been minimized.

Copy link

@joloiuhj joloiuhj commented Feb 10, 2020

@friedboats

This comment has been minimized.

Copy link

@friedboats friedboats commented Mar 2, 2020

Thank you for this!!! Spent all day looking for a solution.

@mohsenasm

This comment has been minimized.

Copy link

@mohsenasm mohsenasm commented May 5, 2020

Thanks!!!
دعای خیر جهانیان پشت سرت :)))

@emanavas

This comment has been minimized.

Copy link

@emanavas emanavas commented Jul 26, 2020

Works Great. Thanks!!

@RaymondBakker

This comment has been minimized.

Copy link

@RaymondBakker RaymondBakker commented Sep 16, 2020

Just a heads up that supplying 0 duration will make Math.easeInOutQuad() return the value -Infinity and supplying 1 duration will make it return large negative numbers. If you need to supply 0 duration, an easy fix would be to add an extra if statement at the start of this function returning the original supplied duration.

Math.easeInOutQuad = function (t, b, c, d) {
    if (d <= 0)
        return c;

...

}

Of course, the better way may be to just set scrollTop instead of using this function altogether.

@quirozcarlos

This comment has been minimized.

Copy link

@quirozcarlos quirozcarlos commented Oct 6, 2020

Nice resource, is very useful 🥇

@KarloZKvasin

This comment has been minimized.

Copy link

@KarloZKvasin KarloZKvasin commented Nov 4, 2020

Here's a typescript version if anyone is interested:

type EaseInOutQuadOptions = {
  currentTime: number;
  start: number;
  change: number;
  duration: number;
};

const easeInOutQuad = ({
  currentTime,
  start,
  change,
  duration,
}: EaseInOutQuadOptions) => {
  let newCurrentTime = currentTime;
  newCurrentTime /= duration / 2;

  if (newCurrentTime < 1) {
    return (change / 2) * newCurrentTime * newCurrentTime + start;
  }

  newCurrentTime -= 1;
  return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
};

type SmoothScrollOptions = {
  duration: number;
  element: HTMLElement;
  to: number;
};
export default function smoothScroll({
  duration,
  element,
  to,
}: SmoothScrollOptions) {
  const start = element.scrollTop;
  const change = to - start;
  const startDate = new Date().getTime();

  const animateScroll = () => {
    const currentDate = new Date().getTime();
    const currentTime = currentDate - startDate;
    element.scrollTop = easeInOutQuad({
      currentTime,
      start,
      change,
      duration,
    });

    if (currentTime < duration) {
      requestAnimationFrame(animateScroll);
    } else {
      element.scrollTop = to;
    }
  };
  animateScroll();
}

Small update... and extend about direction / type

interface EaseInOutQuadOptions {
  currentTime: number;
  start: number;
  change: number;
  duration: number;
}

const easeInOutQuad = (currentTime, start, change, duration): EaseInOutQuadOptions => {
  let newCurrentTime = currentTime;
  newCurrentTime /= duration / 2;

  if (newCurrentTime < 1) {
    return (change / 2) * newCurrentTime * newCurrentTime + start;
  }

  newCurrentTime -= 1;
  return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
};

interface SmoothScrollOptions {
  duration: number;
  element: HTMLElement;
  to: number;
  type: 'scrollTop' | 'scrollLeft';
}

const smoothScroll = (duration, element, to, type = 'scrollTop'): SmoothScrollOptions => {
  const start = element[type];
  const change = to - start;
  const startDate = new Date().getTime();

  const animateScroll = () => {
    const currentDate = new Date().getTime();
    const currentTime = currentDate - startDate;
    element[type] = easeInOutQuad(currentTime, start, change, duration);

    if (currentTime < duration) {
      requestAnimationFrame(animateScroll);
    } else {
      element[type] = to;
    }
  };
  animateScroll();

  return null;
};

export { smoothScroll };
@dhovart

This comment has been minimized.

Copy link

@dhovart dhovart commented Dec 17, 2020

@KarloZKvasin I think your update is not properly typed. easeInOutQuad is set to return an EaseInOutQuadOptions. Same goes for smoothScroll, set to return a SmoothScrollOptions.
You should dump these interfaces and type each function argument instead.

Updated:

const easeInOutQuad = (
  currentTime: number,
  start: number,
  change: number,
  duration: number,
): number => {
  let newCurrentTime = currentTime;
  newCurrentTime /= duration / 2;

  if (newCurrentTime < 1) {
    return (change / 2) * newCurrentTime * newCurrentTime + start;
  }

  newCurrentTime -= 1;
  return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
};

const smoothScroll = (
  duration: number,
  element: HTMLElement,
  to: number,
  property: 'scrollTop' | 'scrollLeft',
): void => {
  const start = element[property];
  const change = to - start;
  const startDate = new Date().getTime();

  const animateScroll = () => {
    const currentDate = new Date().getTime();
    const currentTime = currentDate - startDate;

    element[property] = easeInOutQuad(currentTime, start, change, duration);

    if (currentTime < duration) {
      requestAnimationFrame(animateScroll);
    } else {
      element[property] = to;
    }
  };
  animateScroll();
};

export { smoothScroll };
@aLIEzsss4

This comment has been minimized.

Copy link

@aLIEzsss4 aLIEzsss4 commented Jan 13, 2021

how do work with scroll-snap-type: x mandatory;

@ruucm

This comment has been minimized.

Copy link

@ruucm ruucm commented Feb 17, 2021

const
scrollTo = function(to, duration) {
    const
    element = document.scrollingElement || document.documentElement,
    start = element.scrollTop,
    change = to - start,
    startDate = +new Date(),
    // t = current time
    // b = start value
    // c = change in value
    // d = duration
    easeInOutQuad = function(t, b, c, d) {
        t /= d/2;
        if (t < 1) return c/2*t*t + b;
        t--;
        return -c/2 * (t*(t-2) - 1) + b;
    },
    animateScroll = function() {
        const currentDate = +new Date();
        const currentTime = currentDate - startDate;
        element.scrollTop = parseInt(easeInOutQuad(currentTime, start, change, duration));
        if(currentTime < duration) {
            requestAnimationFrame(animateScroll);
        }
        else {
            element.scrollTop = to;
        }
    };
    animateScroll();
};

Here's the code a bit modernized. Now it's a lot smoother and works in Safari as well. If you don't work with babel then replace const with var.

Thanks. it is a lot more soomother

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment