-
-
Save felipenmoura/650e7e1292c1e7638bcf6c9f9aeb9dd5 to your computer and use it in GitHub Desktop.
/** | |
* Will gracefuly scroll the page | |
* This function will scroll the page using | |
* an `ease-in-out` effect. | |
* | |
* You can use it to scroll to a given element, as well. | |
* To do so, pass the element instead of a number as the position. | |
* Optionally, you can pass a `queryString` for an element selector. | |
* | |
* The default duration is half a second (500ms) | |
* | |
* This function returns a Promise that resolves as soon | |
* as it has finished scrolling. If a selector is passed and | |
* the element is not present in the page, it will reject. | |
* | |
* EXAMPLES: | |
* | |
* ```js | |
* window.scrollPageTo('#some-section', 2000); | |
* window.scrollPageTo(document.getElementById('some-section'), 1000); | |
* window.scrollPageTo(500); // will scroll to 500px in 500ms | |
* ``` | |
* | |
* @returns {Promise} | |
* @param {HTMLElement|Number|Selector} Target | |
* @param {Number} Duration [default=500] | |
* | |
* Inspired by @andjosh's work | |
* | |
*/ | |
function scrollPageTo (to, duration=500) { | |
//t = current time | |
//b = start value | |
//c = change in value | |
//d = duration | |
const 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; | |
}; | |
return new Promise((resolve, reject) => { | |
const element = document.scrollingElement; | |
if (typeof to === 'string') { | |
to = document.querySelector(to) || reject(); | |
} | |
if (typeof to !== 'number') { | |
to = to.getBoundingClientRect().top + element.scrollTop; | |
} | |
let start = element.scrollTop, | |
change = to - start, | |
currentTime = 0, | |
increment = 20; | |
const animateScroll = function() { | |
currentTime += increment; | |
let val = easeInOutQuad(currentTime, start, change, duration); | |
element.scrollTop = val; | |
if(currentTime < duration) { | |
setTimeout(animateScroll, increment); | |
} else { | |
resolve(); | |
} | |
}; | |
animateScroll(); | |
}); | |
} |
GREAT Job. I would use cubic bezier function instead of quad
http://www.gizma.com/easing/#cub3
to me looks better.
Good stuff. Without having examined your code thoroughly, I see no repercussions when swapping setTimeout(animateScroll, increment);
for requestAnimationFrame(animateScroll);
. That's the only potential for improvement which I see.
Fixed a typo (ease-in-out on line 4) but since I can't create a pull request for your gist, I'll link mine here.
https://gist.github.com/tyleryoungblood/10a8084bf5f166ed7fe33b0436bbd565
In my case, the easing does not seem to work when scroll to a seclector (like #footer).
EDIT :
Here is a very simple CodePen example. The easing is not working when scroll down to the footer. It's like the easing doesn't have the time to be completed.
RE-EDIT :
I get the solution by myself.
This is because the bottom of the page can't scroll to the top of the window (of course!). So, when smooth scroll to the bottom of the page, to get an easing (or a duration) that matches the position I have to use:
var position = document.body.clientHeight - window.innerHeight;
and write the click binding like this:
document.querySelector(".scroll-bottom").onclick = function () {
var position = document.body.clientHeight - window.innerHeight;
window.scrollPageTo(position);
}
The above CodePen example is working well now.
The benefits:
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
Thanks for sharing. Any idea on how to implement it in Angular 7? I've included its reference to angular.json in the scripts array, but don't know how to include the function scrollPageTo in a component.
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();
};
The benefits: