-
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; | |
}; |
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();
}
Just a heads up that supplying
0
duration will make Math.easeInOutQuad() return the value-Infinity
and supplying1
duration will make it return large negative numbers. If you need to supply0
duration, an easy fix would be to add an extra if statement at the start of this function returning the original supplied duration.Of course, the better way may be to just set scrollTop instead of using this function altogether.