-
Star
(336)
You must be signed in to star a gist -
Fork
(54)
You must be signed in to fork a gist
-
-
Save joshbeckman/6764939 to your computer and use it in GitHub Desktop.
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; | |
}; |
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();
}
Just curious, why mutate the global Math
object?
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
Thanks so much, great solution! :)
@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.
@tomaswolfgang
window.scrollTo
is very nice solution as long as you need to scrollwindow
. 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.
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
withvar
.
thanks very much!
Thanks!!
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 orMath
's prototype) - Has a default duration
- It also uses the
document.scrollingElement
and you don't need to senddocument
,document.body
,window
ordocument.documentElement
according to your page structure
I hope it helps and thanks for both inspiring it making it public ;)
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 !!!!") }
Thanks a lot!
Has anyone added more easing functions?
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();
}
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;
};
@andjosh Thank you!! Very useful!!
Thank you for this!!! Spent all day looking for a solution.
Thanks!!!
دعای خیر جهانیان پشت سرت :)))
Works Great. Thanks!!
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.
Nice resource, is very useful 🥇
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 };
@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 };
how do work with scroll-snap-type: x mandatory;
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
withvar
.
Thanks. it is a lot more soomother
I also changed it to use performance.now()
:
var scrollTo = function(to, duration) {
var element = document.scrollingElement || document.documentElement,
start = element.scrollTop,
change = to - start,
startTs = performance.now(),
// 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(ts) {
var currentTime = ts - startTs;
element.scrollTop = parseInt(easeInOutQuad(currentTime, start, change, duration));
if(currentTime < duration) {
requestAnimationFrame(animateScroll);
}
else {
element.scrollTop = to;
}
};
requestAnimationFrame(animateScroll);
};
For the utility if you just want to paste this around, here is minimized version of the requestAnimationFrame
version:
var scrollTo=function(l,t){var c=document.scrollingElement||document.documentElement,m=c.scrollTop,a=l-m,s=performance.now(),i=function(o){var n,e,r=o-s;c.scrollTop=parseInt((n=r,e=m,o=a,(n/=t/2)<1?o/2*n*n+e:-o/2*(--n*(n-2)-1)+e)),r<t?requestAnimationFrame(i):c.scrollTop=l};requestAnimationFrame(i)};
Then use it like the others
scrollTo(150, 1000);
How would you go about making a linear function, no easing, just scroll down evenly in a set duration? Thank you!
@Alesvetina you would change the easing function for easeInOutQuad = function (t) { ...
to simply this:
function (t) { return t; }
Here's An updated version that satisfies much more strict ESLint parameters, plus new ES syntax:
export const animateScrollTo = (to, duration) => {
const element = document.scrollingElement || document.documentElement;
const start = element.scrollTop;
const change = to - start;
const startDate = +new Date();
// t = current time
// b = start value
// c = change in value
// d = duration
const easeInOutQuad = (t, b, c, d) => {
let t2 = t;
t2 /= d / 2;
if (t2 < 1) return (c / 2) * t2 * t2 + b;
t2 -= 1;
return (-c / 2) * (t2 * (t2 - 2) - 1) + b;
};
const animateScroll = () => {
const currentDate = +new Date();
const currentTime = currentDate - startDate;
element.scrollTop = parseInt(easeInOutQuad(currentTime, start, change, duration), 10);
if (currentTime < duration) {
requestAnimationFrame(animateScroll);
} else {
element.scrollTop = to;
}
};
animateScroll();
};
Hi, an alternative version with top and left props (typescript).
const scrollTo = ({element, top, left, duration}: {
element: HTMLElement,
top: number,
left: number,
duration: number,
}) => {
const startTop = element.scrollTop;
const startLeft = element.scrollLeft;
const changeTop = top - startTop;
const changeLeft = left - startLeft;
const startDate = new Date().getTime();
const animateScroll = function(){
const currentDate = new Date().getTime();
const currentTime = currentDate - startDate;
element.scrollTop = easeInOutQuad(currentTime, startTop, changeTop, duration);
element.scrollLeft = easeInOutQuad(currentTime, startLeft, changeLeft, duration);
if(currentTime < duration) {
requestAnimationFrame(animateScroll);
} else {
element.scrollTop = top;
element.scrollLeft = left;
}
};
animateScroll();
}
Thank you very much! 😄