Created
January 22, 2018 09:54
-
-
Save schliflo/a3938feb1a3da322f217156263194296 to your computer and use it in GitHub Desktop.
Smooth scrolling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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