Vanilla JavaScript Smooth Scroll
While working on the site’s 24th redesign, my goal was to simplify the UI as much as possible. As much as I enjoy lots of cool effects, I wanted the new design to be as minimalist as possible. So that meant dropping a LOT of little bells and whistles that were included in the previous design. One feature that didn’t make the cut was smooth scrolling to internal anchor targets. Like when you click a link that takes you #somewhere
on the same page, but instead of just jumping directly it sort of scrolls you there nice and easy.
Demo
The Code
To implement smooth scrolling for internal anchor links, add the following slice of vanilla JavaScript (no jQuery required!):
// Vanilla JavaScript Scroll to Anchor
// @ https://perishablepress.com/vanilla-javascript-scroll-anchor/
(function() {
scrollTo();
})();
function scrollTo() {
const links = document.querySelectorAll('.scroll');
links.forEach(each => (each.onclick = scrollAnchors));
}
function scrollAnchors(e, respond = null) {
const distanceToTop = el => Math.floor(el.getBoundingClientRect().top);
e.preventDefault();
var targetID = (respond) ? respond.getAttribute('href') : this.getAttribute('href');
const targetAnchor = document.querySelector(targetID);
if (!targetAnchor) return;
const originalTop = distanceToTop(targetAnchor);
window.scrollBy({ top: originalTop, left: 0, behavior: 'smooth' });
const checkIfDone = setInterval(function() {
const atBottom = window.innerHeight + window.pageYOffset >= document.body.offsetHeight - 2;
if (distanceToTop(targetAnchor) === 0 || atBottom) {
targetAnchor.tabIndex = '-1';
targetAnchor.focus();
window.history.pushState('', '', targetID);
clearInterval(checkIfDone);
}
}, 100);
}
After including that code snippet, any anchor link with a class of scroll
will scroll to the target, as shown in the demo. No modifications are required, just drop it in. If you want to change the value of the class from “scroll” to “slide” or whatever, replace scroll
in the first line of the scrollTo()
function.
How it works
Without going into painful detail about how this code works, the heavy lifting happens in the scrollAnchors()
function. That’s where the calculations are made, logic applied, etc. And that function is called by scrollTo()
, which simply applies the scroll function to each anchor link that has the scroll
class. And then to kick it all off, the scrollTo()
function is called via Immediately Invoked Function Expression (IIFE).
Once the smooth-scroll script is included on the web page, any internal link that includes the scroll
class will smoothly scroll to the target anchor element (via #fragment-identifier
, aka hash tag).
No class required
What if you don’t want to add any scroll classes, and would rather automatically scroll any/all anchor links on the page? Easy. Just replace the scrollTo()
function with this:
function scrollTo() {
var links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
var link = links[i];
if ((link.href && link.href.indexOf('#') != -1) && ((link.pathname == location.pathname) || ('/' + link.pathname == location.pathname)) && (link.search == location.search)) {
link.onclick = scrollAnchors;
}
}
}
No modifications are required. How does it work? First grab all links as an array. Then loop through the array and check whether each link includes a hash character #
, and also whether the link target is the same as the current location. If both of these conditions prove true, clicking the link will result on a smooth scroll to the target. All happens automatically, giving your pages complete smooth scrolling without having to add any classes.
Smooth scroll for Ajax content
The previous technique works great in all cases except where the HTML is loaded via Ajax. For any content loaded live via Ajax, the link elements are not recognized by the scrollTo()
function, so smooth scrolling fails in that case. So, if you want to enable smooth scrolling in Ajax-loaded content, add the following JavaScript:
document.addEventListener('click', function(e) {
event = e || window.e;
var target = e.target || e.srcElement;
var respond = document.getElementById('respond-link');
if (e.target.classList.contains('scroll-live')) scrollAnchors(e, respond);
});
After adding this code, any links that include the class scroll-live
will receive the smooth-scrolling treatment. And if you don’t want to use two different classes, scroll
for static content, and scroll-live
for Ajax content, just change scroll-live
in the Ajax code snippet. Change it to scroll
or whatever works best for you.
More smooth scroll techniques
- Smooth Scroll (Vanilla JS)
- Smooth Scrolling (jQuery)
- Skinny.js (jQuery)
CSS solution
Just a heads up for latest Chrome, Firefox, or Opera, you can enable smooth scroll via the scroll-behavior
property (no JS required!), for example:
.module {
scroll-behavior: [ auto | smooth ];
}
More infos at CSS-Tricks and caniuse. Thanks to DKeu for the tip!
4 responses to “Vanilla JavaScript Smooth Scroll”
Hey Jeff,
Nice script! I think a few changes might be in order though:
If there are a lot of links on given page, looping through all of them could be computationally expensive, so I reworked your script slightly to use event delegation across the board:
https://codepen.io/maxxheth/pen/oVYRdm
For anyone who’s not aware, I think it’s also worth noting that using ES6 features (const, let, fat arrows, etc.) means that you have to transpile them using a transformer such as Babel (and preferably via a bundler or task runner such as Gulp, Webpack, RollUp, or Parcel for example) if you need to support older web browsers (and even certain mobile browsers).
If that sounds like too much effort for anyone who just wants to take the code and plug it in somewhere, I refactored the code in Jeff’s example above to ES5 JavaScript in my Code Pen example, so it should have fairly widespread support, though your mileage may vary (if you need ironclad browser support, there’s always jQuery! =)). In any case, I hope you find it useful! Good luck!
Pure awesome, thank you Maximillian! :)
My pleasure! Your books are a godsend (bought them all about two weeks ago), so thank you in return! I’m kicking myself for not getting the .htaccess one years ago…I can recall at least one time when a client’s site got hacked where those blacklisting scripts would’ve saved my bacon. That was a painful lesson! Lol!
Have a good one and keep up the great work! =)
Always looking for more handy JavaScripts.
I have always appreciated what CSS3 can do for animations too. Just imagine putting this baby together with some sweet, smooth-transitions from the CSS3-side!
I wasn’t sure if you visited DynamicDrive website very often, but I remembered downloading and adapting a really sweet bit of CSS for an animated “toggle menu”. It made a great replacement for the old JavaScripted dynamic menu I was using, and looked a lot cleaner.
Anyway,
Consider my exploration of this bit of script on my “To Do” bucket list. :-)
– Jim