Skip to content

Instantly share code, notes, and snippets.

@schliflo
Created January 22, 2018 09:54
Show Gist options
  • Save schliflo/a3938feb1a3da322f217156263194296 to your computer and use it in GitHub Desktop.
Save schliflo/a3938feb1a3da322f217156263194296 to your computer and use it in GitHub Desktop.
Smooth scrolling
'use strict';
/*
This is a dependency free drop-in solution for smooth anchor scrolling.
It was largely inspired by this answer: https://stackoverflow.com/questions/17722497/scroll-smoothly-to-specific-element-on-page#answer-39494245
Added support for changing documents and excluding anchors with .no-scroll class as well as browser history
*/
(function () {
let anchors;
let scrollDuration = 500;
let lastHash = location.hash;
function getElementY(query) {
if(query) {
let element = document.querySelector(query);
if (element) {
return window.pageYOffset + element.getBoundingClientRect().top;
} else {
return 0;
}
}
}
function smoothScroll(element, duration, forceTop) {
let startingY = window.pageYOffset;
let elementY;
if (forceTop) {
elementY = 0;
} else {
elementY = getElementY(element) + 1;
}
// If element is close to page's bottom then window will scroll only to some position above the element.
let targetY = document.body.scrollHeight - elementY < window.innerHeight ? document.body.scrollHeight - window.innerHeight : elementY;
let diff = targetY - startingY;
// Easing function: easeInOutCubic
// From: https://gist.github.com/gre/1650294
let easing = function (t) {
return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
};
let start;
if (!diff) return;
// Bootstrap our animation - it will get called right before next frame shall be rendered.
window.requestAnimationFrame(function step(timestamp) {
if (!start) start = timestamp;
// Elapsed miliseconds since start of scrolling.
let time = timestamp - start;
// Get percent of completion in range [0, 1].
let percent = Math.min(time / duration, 1);
// Apply the easing.
// It can cause bad-looking slow frames in browser performance tool, so be careful.
percent = easing(percent);
window.scrollTo(0, startingY + diff * percent);
// Proceed with animation as long as we wanted it to.
if (time < duration) {
window.requestAnimationFrame(step);
} else {
// finally update the location hash according to the anchor
lastHash = element;
if (!forceTop && location.hash !== element) {
if (history.pushState) {
history.pushState(null, null, element);
}
else {
location.hash = element;
}
}
}
})
}
function initSmoothScrolling() {
anchors = document.querySelectorAll('[href^="#"]:not(.no-scroll)');
for (let i = 0; i < anchors.length; ++i) {
anchors[i].addEventListener('click', function (e) {
e.preventDefault();
smoothScroll(e.target.getAttribute('href'), scrollDuration, false);
});
}
}
// call smoothscrolling once if location hash is set
if (lastHash !== '') {
smoothScroll(location.hash, scrollDuration, false);
}
// init smoothscrolling once
initSmoothScrolling();
// then we need to watch for changes made to the document
let observer = new window.MutationObserver(function () {
initSmoothScrolling();
});
// start observer
observer.observe(document, {
subtree: true,
childList: true
});
// watch for hashchanges
window.addEventListener('hashchange', function (e) {
e.preventDefault();
if (location.hash !== lastHash) {
if (location.hash !== '') {
smoothScroll(location.hash, scrollDuration, false);
} else {
smoothScroll('', scrollDuration, true);
}
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment