-
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; | |
}; |
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();
}
Very nice.
I forked it and made a few changes:
https://gist.github.com/felipenmoura/650e7e1292c1e7638bcf6c9f9aeb9dd5
#some-section-id
)scrollTo
native function orMath
's prototype)document.scrollingElement
and you don't need to senddocument
,document.body
,window
ordocument.documentElement
according to your page structureI hope it helps and thanks for both inspiring it making it public ;)