Aleš Sýkora / November 25, 2024 / 0 comments
Bricks: Change Menu Anchor Link Active status on scroll
3 min read / Uncategorized / Share on: Twitter, LinkedIn, Facebook
Post summary: If you want to have onepager with menu created from anchor links ─ and you want to change the active styles by scrolling over sections, you should try this snippet.
I have found a way how to do this in Github repo od Werkmind, so credit goes to werkmind Github: https://github.com/werkmind/Bricks-Anchor-Navigation.
However, I have found some difficulties, so I did a little upgrade. You can now modify the menu structure without touching the JavaScript code, and everything will work automatically as long as the href attributes in the menu items are properly formatted.
For the menu items, please use full link with hash:

Upgraded scroller plugin code
The main changes focused on making the code more dynamic by automatically extracting hashes from menu items instead of using a static list.
This code:
- Gets all menu items from the DOM
- Extracts hash from href attribute
- Adds valid hashes to the Set
- Creates a mapping of hash to DOM section
Key Benefits:
- No need to maintain a static list of hashes
- Automatically works with any menu structure
- Only processes hashes that actually exist in the menu
- More flexible and maintainable
- Reduces chance of errors from manual hash list maintenance
Configuration simplified:
- Removed static hash list from config
- Only essential configuration remains
Error Handling:
- Checks if hash exists before adding
- Ignores empty hashes (‘#’)
- Uses optional chaining for safer property access
- More robust against malformed menu structures
Performance Considerations:
- Hash extraction happens once at initialization
- Uses Set for O(1) lookup performance
- Caches section elements for faster access
- Reduces DOM queries during scroll events
Snippet for Bricks Builder menu automatic hash link active status
document.addEventListener("DOMContentLoaded", function() {
const config = {
menuSelector: ".bricks-nav-menu li",
scrollThreshold: 0.2,
rootPath: '/'
};
const menuItems = document.querySelectorAll(config.menuSelector);
const validHashes = new Set();
const sections = {};
menuItems.forEach(item => {
const link = item.querySelector("a");
if (link) {
const href = link.getAttribute("href");
const hash = href?.includes('#') ? '#' + new URL(href).hash.slice(1) : '';
if (hash && hash !== '#') {
validHashes.add(hash);
sections[hash] = document.querySelector(hash);
}
}
});
const state = {
isScrolling: false,
targetHash: null
};
const utils = {
getFullPath: (anchor) => window.location.origin + window.location.pathname + anchor,
updateHistory: (hash) => {
history.pushState({}, '', hash);
utils.setActiveClass(hash);
},
setActiveClass: (anchor) => {
const expectedHref = utils.getFullPath(anchor);
menuItems.forEach(item => {
const link = item.querySelector("a");
item.classList.toggle("current-menu-item",
link?.getAttribute("href") === expectedHref);
});
},
getSectionInView: () => {
const viewportThreshold = window.innerHeight * config.scrollThreshold;
for (const [hash, section] of Object.entries(sections)) {
if (!section) continue;
const rect = section.getBoundingClientRect();
if (rect.top >= 0 && rect.top <= viewportThreshold) {
return hash;
}
}
return null;
}
};
const handlers = {
scroll: () => {
const currentSection = utils.getSectionInView();
if (state.isScrolling) {
if (currentSection && currentSection === state.targetHash) {
state.isScrolling = false;
sessionStorage.removeItem('programmaticScroll');
state.targetHash = null;
utils.updateHistory(currentSection);
}
} else if (currentSection) {
utils.updateHistory(currentSection);
}
},
click: (e, link) => {
const href = link.getAttribute("href");
const targetHash = href.includes('#') ? '#' + href.split('#')[1] : '';
if (!validHashes.has(targetHash)) {
window.location.href = href;
return;
}
e.preventDefault();
if (window.location.pathname === config.rootPath) {
const targetSection = sections[targetHash];
if (targetSection) {
const offset = targetSection.getBoundingClientRect().top + window.pageYOffset;
state.isScrolling = true;
state.targetHash = targetHash;
window.scrollTo({
top: offset,
behavior: 'smooth'
});
}
} else {
sessionStorage.setItem('programmaticScroll', targetHash);
window.location.href = window.location.origin + config.rootPath + targetHash;
}
}
};
window.addEventListener('scroll', handlers.scroll);
menuItems.forEach(menuItem => {
const link = menuItem.querySelector("a");
if (link) {
link.addEventListener("click", (e) => handlers.click(e, link));
}
});
const initialHash = window.location.hash;
if (validHashes.has(initialHash)) {
if (sessionStorage.getItem('programmaticScroll') === initialHash) {
state.isScrolling = true;
state.targetHash = initialHash;
}
utils.setActiveClass(initialHash);
} else {
utils.setActiveClass("");
}
});
Fuel my passion for writing with a beer🍺
Your support not only makes me drunk but also greatly motivates me to continue creating content that helps. Cheers to more discoveries and shared success. 🍻