Skip to content

Instantly share code, notes, and snippets.

@geekman
Created May 13, 2021 09:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save geekman/d859ad8c6d7a7d7c62c3039f47fa4791 to your computer and use it in GitHub Desktop.
Save geekman/d859ad8c6d7a7d7c62c3039f47fa4791 to your computer and use it in GitHub Desktop.
a minimal Scrollspy implementation in vanilla JavaScript
//
// a minimal Scrollspy implementation written in pure JS
// not widely tested, may have bugs
//
// 2020.05.13 darell tan
//
function scrollSync(tocSel, headingsSel) {
var offset = 0;
var tocElems = document.querySelector(tocSel).querySelectorAll('a[href^="#"]');
var headings = document.querySelector(headingsSel).querySelectorAll('h1,h2,h3,h4,h5,h6');
var headingsPos;
var matchHeading = function(a) {
var t = a.getAttribute('href');
if (t[0] != '#') return;
t = t.substring(1);
var h = [].filter.call(headings, h => h.id == t);
return h ? h[0] : null;
};
var getOffsets = function() {
// map tocElems => pos
headingsPos = [].map.call(tocElems, (e) => {
var h = matchHeading(e);
return h ? h.offsetTop : -1;
});
// also calculate our intersection offset here
// as the header crosses the top 1/6th of the window, it registers
// for too small window sizes, an arbitrary minimum is ensured
offset = window.innerHeight / 6;
if (offset < 80) offset = 80;
//[].map.call(tocElems, (e, i) => console.log(e, headingsPos[i]) );
}
var onScroll = function(e) {
var pos = document.documentElement.scrollTop + offset;
for (var i = 0; i < tocElems.length; i++) {
if (pos >= headingsPos[i] && (
i == tocElems.length - 1 || // last elem
pos < headingsPos[i+1])) {
tocElems[i].classList.add('active');
} else {
tocElems[i].classList.remove('active');
}
}
};
var onTocClick = function(ev) {
var e = ev.target;
var h = matchHeading(e);
if (h) {
toggleSidebar();
// need to make sure we are still past the offset to be
// recognized as "within" that section
window.scroll({top: h.offsetTop - 0.75*offset, behavior: 'smooth'});
window.history.replaceState({}, document.title, e.getAttribute('href'));
ev.preventDefault();
return false;
}
};
[].forEach.call(tocElems, (e) => {
e.addEventListener('click', onTocClick);
});
// initial calls
getOffsets();
onScroll();
window.addEventListener('scroll', onScroll);
window.addEventListener('resize', () => { getOffsets(); onScroll(); });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment