I’ll make this as a strong vanilla JavaScript header chunk for RX Theme: accessible menu, sticky header, search overlay, dropdowns, scroll progress, active links, back-to-top, dark mode hooks, and safe WordPress-friendly behavior.
Dear friend, create this file:
assets/static-js/static-chunks-js/chunk-002-header.js
Then paste the full code below.
/*!
* RX Theme - Header Chunk JS
* File: assets/static/static-chunks-js/chunk-002-header.js
* Purpose: Advanced header, navigation, search, accessibility, sticky behavior,
* mobile menu, dropdown menu, scroll progress, active links, back-to-top,
* dark mode toggle, reading progress, and performance-safe UI handling.
*
* Author: RX Theme
*/
(function () {
'use strict';
/**
* ------------------------------------------------------------
* RX Header Config
* ------------------------------------------------------------
* You can change selector names here without touching all code.
*/
const RX_HEADER_CONFIG = {
header: '.rx-site-header',
headerInner: '.rx-site-header__inner',
nav: '.rx-primary-nav',
navMenu: '.rx-primary-nav__menu',
navLinks: '.rx-primary-nav a',
mobileToggle: '.rx-header-menu-toggle',
mobileClose: '.rx-header-menu-close',
mobileOverlay: '.rx-mobile-menu-overlay',
dropdownParent: '.menu-item-has-children, .rx-has-dropdown',
dropdownToggle: '.rx-dropdown-toggle',
dropdownMenu: '.sub-menu, .rx-dropdown-menu',
searchToggle: '.rx-search-toggle',
searchClose: '.rx-search-close',
searchOverlay: '.rx-search-overlay',
searchInput: '.rx-search-overlay input[type="search"], .rx-search-overlay .search-field',
darkModeToggle: '.rx-dark-mode-toggle',
progressBar: '.rx-scroll-progress-bar',
backToTop: '.rx-back-to-top',
skipLink: '.skip-link, .rx-skip-link',
mainContent: '#main, main, .rx-main',
activeClass: 'is-active',
openClass: 'is-open',
stickyClass: 'is-sticky',
scrolledClass: 'is-scrolled',
hiddenClass: 'is-hidden',
lockedClass: 'rx-scroll-locked',
dropdownOpenClass: 'is-dropdown-open',
stickyOffset: 80,
scrollHideOffset: 220,
resizeDebounce: 160,
scrollThrottle: 16
};
/**
* ------------------------------------------------------------
* Small Utility Helpers
* ------------------------------------------------------------
*/
const RX = {
qs(selector, scope = document) {
return scope.querySelector(selector);
},
qsa(selector, scope = document) {
return Array.prototype.slice.call(scope.querySelectorAll(selector));
},
has(element, className) {
return element && element.classList.contains(className);
},
add(element, className) {
if (element) element.classList.add(className);
},
remove(element, className) {
if (element) element.classList.remove(className);
},
toggle(element, className, force) {
if (element) element.classList.toggle(className, force);
},
attr(element, name, value) {
if (!element) return null;
if (typeof value === 'undefined') {
return element.getAttribute(name);
}
element.setAttribute(name, value);
return value;
},
removeAttr(element, name) {
if (element) element.removeAttribute(name);
},
on(element, event, callback, options) {
if (!element) return;
element.addEventListener(event, callback, options || false);
},
off(element, event, callback, options) {
if (!element) return;
element.removeEventListener(event, callback, options || false);
},
debounce(callback, delay) {
let timer = null;
return function debounced() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
callback.apply(context, args);
}, delay);
};
},
throttle(callback, limit) {
let waiting = false;
return function throttled() {
if (!waiting) {
callback.apply(this, arguments);
waiting = true;
requestAnimationFrame(function () {
waiting = false;
});
}
};
},
isVisible(element) {
if (!element) return false;
return Boolean(
element.offsetWidth ||
element.offsetHeight ||
element.getClientRects().length
);
},
getFocusable(container) {
if (!container) return [];
return RX.qsa(
[
'a[href]',
'area[href]',
'button:not([disabled])',
'input:not([disabled]):not([type="hidden"])',
'select:not([disabled])',
'textarea:not([disabled])',
'iframe',
'object',
'embed',
'[contenteditable]',
'[tabindex]:not([tabindex="-1"])'
].join(','),
container
).filter(RX.isVisible);
},
prefersReducedMotion() {
return window.matchMedia &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
},
isDesktop() {
return window.matchMedia &&
window.matchMedia('(min-width: 992px)').matches;
}
};
/**
* ------------------------------------------------------------
* DOM Cache
* ------------------------------------------------------------
*/
const DOM = {
html: document.documentElement,
body: document.body,
header: null,
headerInner: null,
nav: null,
navMenu: null,
mobileToggle: null,
mobileClose: null,
mobileOverlay: null,
searchToggle: null,
searchClose: null,
searchOverlay: null,
searchInput: null,
darkModeToggle: null,
progressBar: null,
backToTop: null,
mainContent: null
};
/**
* ------------------------------------------------------------
* State
* ------------------------------------------------------------
*/
const STATE = {
lastScrollY: window.scrollY || 0,
ticking: false,
mobileMenuOpen: false,
searchOpen: false,
activeTrap: null,
previousFocus: null
};
/**
* ------------------------------------------------------------
* Cache Elements
* ------------------------------------------------------------
*/
function cacheElements() {
DOM.header = RX.qs(RX_HEADER_CONFIG.header);
DOM.headerInner = RX.qs(RX_HEADER_CONFIG.headerInner);
DOM.nav = RX.qs(RX_HEADER_CONFIG.nav);
DOM.navMenu = RX.qs(RX_HEADER_CONFIG.navMenu);
DOM.mobileToggle = RX.qs(RX_HEADER_CONFIG.mobileToggle);
DOM.mobileClose = RX.qs(RX_HEADER_CONFIG.mobileClose);
DOM.mobileOverlay = RX.qs(RX_HEADER_CONFIG.mobileOverlay);
DOM.searchToggle = RX.qs(RX_HEADER_CONFIG.searchToggle);
DOM.searchClose = RX.qs(RX_HEADER_CONFIG.searchClose);
DOM.searchOverlay = RX.qs(RX_HEADER_CONFIG.searchOverlay);
DOM.searchInput = RX.qs(RX_HEADER_CONFIG.searchInput);
DOM.darkModeToggle = RX.qs(RX_HEADER_CONFIG.darkModeToggle);
DOM.progressBar = RX.qs(RX_HEADER_CONFIG.progressBar);
DOM.backToTop = RX.qs(RX_HEADER_CONFIG.backToTop);
DOM.mainContent = RX.qs(RX_HEADER_CONFIG.mainContent);
}
/**
* ------------------------------------------------------------
* Body Scroll Lock
* ------------------------------------------------------------
*/
function lockBodyScroll() {
RX.add(DOM.html, RX_HEADER_CONFIG.lockedClass);
RX.add(DOM.body, RX_HEADER_CONFIG.lockedClass);
}
function unlockBodyScroll() {
RX.remove(DOM.html, RX_HEADER_CONFIG.lockedClass);
RX.remove(DOM.body, RX_HEADER_CONFIG.lockedClass);
}
/**
* ------------------------------------------------------------
* Focus Trap for Mobile Menu and Search Overlay
* ------------------------------------------------------------
*/
function activateFocusTrap(container) {
if (!container) return;
STATE.activeTrap = container;
STATE.previousFocus = document.activeElement;
RX.on(document, 'keydown', handleFocusTrap);
}
function deactivateFocusTrap() {
RX.off(document, 'keydown', handleFocusTrap);
if (STATE.previousFocus && typeof STATE.previousFocus.focus === 'function') {
STATE.previousFocus.focus({ preventScroll: true });
}
STATE.activeTrap = null;
STATE.previousFocus = null;
}
function handleFocusTrap(event) {
if (!STATE.activeTrap || event.key !== 'Tab') return;
const focusable = RX.getFocusable(STATE.activeTrap);
if (!focusable.length) {
event.preventDefault();
return;
}
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
}
/**
* ------------------------------------------------------------
* Mobile Menu
* ------------------------------------------------------------
*/
function openMobileMenu() {
if (!DOM.nav && !DOM.mobileOverlay) return;
STATE.mobileMenuOpen = true;
RX.add(DOM.body, 'rx-mobile-menu-active');
RX.add(DOM.nav, RX_HEADER_CONFIG.openClass);
RX.add(DOM.mobileOverlay, RX_HEADER_CONFIG.openClass);
RX.attr(DOM.mobileToggle, 'aria-expanded', 'true');
RX.attr(DOM.nav, 'aria-hidden', 'false');
lockBodyScroll();
const focusTarget =
RX.qs('a, button, input, [tabindex]:not([tabindex="-1"])', DOM.nav) ||
DOM.mobileClose ||
DOM.nav;
if (focusTarget && typeof focusTarget.focus === 'function') {
focusTarget.focus({ preventScroll: true });
}
activateFocusTrap(DOM.nav);
}
function closeMobileMenu() {
STATE.mobileMenuOpen = false;
RX.remove(DOM.body, 'rx-mobile-menu-active');
RX.remove(DOM.nav, RX_HEADER_CONFIG.openClass);
RX.remove(DOM.mobileOverlay, RX_HEADER_CONFIG.openClass);
RX.attr(DOM.mobileToggle, 'aria-expanded', 'false');
RX.attr(DOM.nav, 'aria-hidden', 'true');
closeAllDropdowns();
unlockBodyScroll();
deactivateFocusTrap();
}
function toggleMobileMenu() {
if (STATE.mobileMenuOpen) {
closeMobileMenu();
} else {
openMobileMenu();
}
}
function setupMobileMenu() {
if (DOM.mobileToggle) {
RX.attr(DOM.mobileToggle, 'aria-expanded', 'false');
RX.attr(DOM.mobileToggle, 'aria-controls', 'rx-primary-navigation');
RX.on(DOM.mobileToggle, 'click', toggleMobileMenu);
}
if (DOM.mobileClose) {
RX.on(DOM.mobileClose, 'click', closeMobileMenu);
}
if (DOM.mobileOverlay) {
RX.on(DOM.mobileOverlay, 'click', closeMobileMenu);
}
if (DOM.nav) {
RX.attr(DOM.nav, 'id', RX.attr(DOM.nav, 'id') || 'rx-primary-navigation');
if (!RX.isDesktop()) {
RX.attr(DOM.nav, 'aria-hidden', 'true');
}
}
RX.qsa(RX_HEADER_CONFIG.navLinks).forEach(function (link) {
RX.on(link, 'click', function () {
if (!RX.isDesktop()) {
closeMobileMenu();
}
});
});
}
/**
* ------------------------------------------------------------
* Dropdown Menu
* ------------------------------------------------------------
*/
function setupDropdowns() {
const dropdownParents = RX.qsa(RX_HEADER_CONFIG.dropdownParent, DOM.nav || document);
dropdownParents.forEach(function (parent, index) {
const submenu = RX.qs(RX_HEADER_CONFIG.dropdownMenu, parent);
let toggle = RX.qs(RX_HEADER_CONFIG.dropdownToggle, parent);
const firstLink = RX.qs('a', parent);
if (!submenu) return;
const submenuId = submenu.id || 'rx-submenu-' + index;
submenu.id = submenuId;
if (!toggle && firstLink) {
toggle = document.createElement('button');
toggle.className = 'rx-dropdown-toggle';
toggle.type = 'button';
toggle.setAttribute('aria-label', 'Open submenu');
toggle.innerHTML = '<span aria-hidden="true">+</span>';
firstLink.insertAdjacentElement('afterend', toggle);
}
if (toggle) {
RX.attr(toggle, 'aria-expanded', 'false');
RX.attr(toggle, 'aria-controls', submenuId);
RX.on(toggle, 'click', function (event) {
event.preventDefault();
event.stopPropagation();
const isOpen = RX.has(parent, RX_HEADER_CONFIG.dropdownOpenClass);
if (RX.isDesktop()) {
closeSiblingDropdowns(parent);
}
if (isOpen) {
closeDropdown(parent);
} else {
openDropdown(parent);
}
});
}
RX.on(parent, 'mouseenter', function () {
if (RX.isDesktop()) openDropdown(parent);
});
RX.on(parent, 'mouseleave', function () {
if (RX.isDesktop()) closeDropdown(parent);
});
RX.on(parent, 'keydown', function (event) {
if (event.key === 'Escape') {
closeDropdown(parent);
if (toggle) {
toggle.focus();
}
}
});
});
RX.on(document, 'click', function (event) {
if (!DOM.nav) return;
if (!DOM.nav.contains(event.target)) {
closeAllDropdowns();
}
});
}
function openDropdown(parent) {
const toggle = RX.qs(RX_HEADER_CONFIG.dropdownToggle, parent);
RX.add(parent, RX_HEADER_CONFIG.dropdownOpenClass);
if (toggle) {
RX.attr(toggle, 'aria-expanded', 'true');
}
}
function closeDropdown(parent) {
const toggle = RX.qs(RX_HEADER_CONFIG.dropdownToggle, parent);
RX.remove(parent, RX_HEADER_CONFIG.dropdownOpenClass);
if (toggle) {
RX.attr(toggle, 'aria-expanded', 'false');
}
}
function closeSiblingDropdowns(parent) {
const siblings = Array.prototype.filter.call(parent.parentNode.children, function (child) {
return child !== parent;
});
siblings.forEach(function (sibling) {
closeDropdown(sibling);
});
}
function closeAllDropdowns() {
RX.qsa(RX_HEADER_CONFIG.dropdownParent, DOM.nav || document).forEach(closeDropdown);
}
/**
* ------------------------------------------------------------
* Search Overlay
* ------------------------------------------------------------
*/
function openSearch() {
if (!DOM.searchOverlay) return;
STATE.searchOpen = true;
RX.add(DOM.body, 'rx-search-active');
RX.add(DOM.searchOverlay, RX_HEADER_CONFIG.openClass);
RX.attr(DOM.searchToggle, 'aria-expanded', 'true');
RX.attr(DOM.searchOverlay, 'aria-hidden', 'false');
lockBodyScroll();
window.setTimeout(function () {
if (DOM.searchInput) {
DOM.searchInput.focus();
}
}, 80);
activateFocusTrap(DOM.searchOverlay);
}
function closeSearch() {
if (!DOM.searchOverlay) return;
STATE.searchOpen = false;
RX.remove(DOM.body, 'rx-search-active');
RX.remove(DOM.searchOverlay, RX_HEADER_CONFIG.openClass);
RX.attr(DOM.searchToggle, 'aria-expanded', 'false');
RX.attr(DOM.searchOverlay, 'aria-hidden', 'true');
unlockBodyScroll();
deactivateFocusTrap();
}
function toggleSearch() {
if (STATE.searchOpen) {
closeSearch();
} else {
openSearch();
}
}
function setupSearchOverlay() {
if (DOM.searchToggle) {
RX.attr(DOM.searchToggle, 'aria-expanded', 'false');
RX.on(DOM.searchToggle, 'click', function (event) {
event.preventDefault();
toggleSearch();
});
}
if (DOM.searchClose) {
RX.on(DOM.searchClose, 'click', function (event) {
event.preventDefault();
closeSearch();
});
}
if (DOM.searchOverlay) {
RX.attr(DOM.searchOverlay, 'aria-hidden', 'true');
RX.on(DOM.searchOverlay, 'click', function (event) {
if (event.target === DOM.searchOverlay) {
closeSearch();
}
});
}
}
/**
* ------------------------------------------------------------
* Sticky Header and Hide-on-scroll
* ------------------------------------------------------------
*/
function updateStickyHeader() {
if (!DOM.header) return;
const currentY = window.scrollY || window.pageYOffset;
const isScrolled = currentY > RX_HEADER_CONFIG.stickyOffset;
const scrollingDown = currentY > STATE.lastScrollY;
const shouldHide =
scrollingDown &&
currentY > RX_HEADER_CONFIG.scrollHideOffset &&
!STATE.mobileMenuOpen &&
!STATE.searchOpen;
RX.toggle(DOM.header, RX_HEADER_CONFIG.scrolledClass, isScrolled);
RX.toggle(DOM.header, RX_HEADER_CONFIG.stickyClass, isScrolled);
RX.toggle(DOM.header, RX_HEADER_CONFIG.hiddenClass, shouldHide);
STATE.lastScrollY = currentY;
}
/**
* ------------------------------------------------------------
* Scroll Progress Bar
* ------------------------------------------------------------
*/
function updateScrollProgress() {
if (!DOM.progressBar) return;
const scrollTop = window.scrollY || document.documentElement.scrollTop;
const docHeight =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
DOM.progressBar.style.width = Math.min(100, Math.max(0, progress)) + '%';
}
/**
* ------------------------------------------------------------
* Back to Top Button
* ------------------------------------------------------------
*/
function updateBackToTop() {
if (!DOM.backToTop) return;
const show = (window.scrollY || window.pageYOffset) > 500;
RX.toggle(DOM.backToTop, RX_HEADER_CONFIG.activeClass, show);
RX.attr(DOM.backToTop, 'aria-hidden', show ? 'false' : 'true');
}
function setupBackToTop() {
if (!DOM.backToTop) return;
RX.attr(DOM.backToTop, 'aria-hidden', 'true');
RX.on(DOM.backToTop, 'click', function (event) {
event.preventDefault();
window.scrollTo({
top: 0,
behavior: RX.prefersReducedMotion() ? 'auto' : 'smooth'
});
});
}
/**
* ------------------------------------------------------------
* Active Current Menu Link
* ------------------------------------------------------------
*/
function setupActiveMenuLinks() {
const links = RX.qsa(RX_HEADER_CONFIG.navLinks);
const currentUrl = normalizeUrl(window.location.href);
links.forEach(function (link) {
const href = link.href;
if (!href) return;
const linkUrl = normalizeUrl(href);
if (linkUrl === currentUrl) {
RX.add(link, RX_HEADER_CONFIG.activeClass);
RX.attr(link, 'aria-current', 'page');
const parentLi = link.closest('li');
if (parentLi) {
RX.add(parentLi, 'current-menu-item');
}
}
});
}
function normalizeUrl(url) {
try {
const parsed = new URL(url);
return parsed.origin + parsed.pathname.replace(/\/+$/, '');
} catch (error) {
return url;
}
}
/**
* ------------------------------------------------------------
* Smooth Anchor Scrolling
* ------------------------------------------------------------
*/
function setupSmoothAnchors() {
RX.qsa('a[href^="#"]').forEach(function (link) {
RX.on(link, 'click', function (event) {
const targetId = link.getAttribute('href');
if (!targetId || targetId === '#') return;
const target = RX.qs(targetId);
if (!target) return;
event.preventDefault();
const headerHeight = DOM.header ? DOM.header.offsetHeight : 0;
const targetPosition =
target.getBoundingClientRect().top +
window.pageYOffset -
headerHeight -
16;
window.scrollTo({
top: targetPosition,
behavior: RX.prefersReducedMotion() ? 'auto' : 'smooth'
});
target.setAttribute('tabindex', '-1');
target.focus({ preventScroll: true });
});
});
}
/**
* ------------------------------------------------------------
* Skip Link Improvement
* ------------------------------------------------------------
*/
function setupSkipLinks() {
const skipLinks = RX.qsa(RX_HEADER_CONFIG.skipLink);
skipLinks.forEach(function (link) {
RX.on(link, 'click', function () {
const target = DOM.mainContent;
if (!target) return;
target.setAttribute('tabindex', '-1');
window.setTimeout(function () {
target.focus();
}, 10);
});
});
}
/**
* ------------------------------------------------------------
* Dark Mode Toggle
* ------------------------------------------------------------
* This uses localStorage safely.
* CSS should use:
* html[data-rx-theme="dark"] { ... }
*/
function setupDarkMode() {
if (!DOM.darkModeToggle) return;
const savedTheme = safeLocalStorageGet('rx-theme-mode');
const systemDark =
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
const initialTheme = savedTheme || (systemDark ? 'dark' : 'light');
applyTheme(initialTheme);
RX.on(DOM.darkModeToggle, 'click', function (event) {
event.preventDefault();
const current = DOM.html.getAttribute('data-rx-theme') || 'light';
const next = current === 'dark' ? 'light' : 'dark';
applyTheme(next);
safeLocalStorageSet('rx-theme-mode', next);
});
}
function applyTheme(theme) {
DOM.html.setAttribute('data-rx-theme', theme);
if (DOM.darkModeToggle) {
RX.attr(DOM.darkModeToggle, 'aria-pressed', theme === 'dark' ? 'true' : 'false');
RX.attr(
DOM.darkModeToggle,
'aria-label',
theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'
);
}
}
function safeLocalStorageGet(key) {
try {
return window.localStorage.getItem(key);
} catch (error) {
return null;
}
}
function safeLocalStorageSet(key, value) {
try {
window.localStorage.setItem(key, value);
} catch (error) {
return false;
}
return true;
}
/**
* ------------------------------------------------------------
* Header Height CSS Variable
* ------------------------------------------------------------
* CSS can use:
* padding-top: var(--rx-header-height);
*/
function updateHeaderHeightVariable() {
if (!DOM.header) return;
const height = DOM.header.offsetHeight || 0;
DOM.html.style.setProperty('--rx-header-height', height + 'px');
}
/**
* ------------------------------------------------------------
* Keyboard Global Events
* ------------------------------------------------------------
*/
function setupKeyboardEvents() {
RX.on(document, 'keydown', function (event) {
if (event.key === 'Escape') {
if (STATE.searchOpen) {
closeSearch();
}
if (STATE.mobileMenuOpen) {
closeMobileMenu();
}
closeAllDropdowns();
}
});
}
/**
* ------------------------------------------------------------
* Header Scroll Handler
* ------------------------------------------------------------
*/
function handleScroll() {
updateStickyHeader();
updateScrollProgress();
updateBackToTop();
}
/**
* ------------------------------------------------------------
* Resize Handler
* ------------------------------------------------------------
*/
function handleResize() {
updateHeaderHeightVariable();
if (RX.isDesktop() && STATE.mobileMenuOpen) {
closeMobileMenu();
}
}
/**
* ------------------------------------------------------------
* WordPress Admin Bar Support
* ------------------------------------------------------------
*/
function setupAdminBarOffset() {
const adminBar = RX.qs('#wpadminbar');
if (!adminBar) return;
const adminBarHeight = adminBar.offsetHeight || 0;
DOM.html.style.setProperty('--rx-admin-bar-height', adminBarHeight + 'px');
RX.add(DOM.body, 'rx-has-admin-bar');
}
/**
* ------------------------------------------------------------
* Lazy Header Shadow Enhancement
* ------------------------------------------------------------
*/
function setupHeaderShadowObserver() {
if (!DOM.header || !window.IntersectionObserver) return;
const sentinel = document.createElement('div');
sentinel.className = 'rx-header-sentinel';
sentinel.setAttribute('aria-hidden', 'true');
DOM.header.parentNode.insertBefore(sentinel, DOM.header);
const observer = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
RX.toggle(DOM.header, 'has-shadow', !entry.isIntersecting);
});
},
{
root: null,
threshold: 0
}
);
observer.observe(sentinel);
}
/**
* ------------------------------------------------------------
* Header Search Form Empty Submit Protection
* ------------------------------------------------------------
*/
function setupSearchFormValidation() {
if (!DOM.searchOverlay) return;
const forms = RX.qsa('form[role="search"], form.search-form', DOM.searchOverlay);
forms.forEach(function (form) {
RX.on(form, 'submit', function (event) {
const input = RX.qs('input[type="search"], .search-field', form);
if (!input) return;
if (!input.value.trim()) {
event.preventDefault();
input.focus();
RX.add(form, 'has-error');
window.setTimeout(function () {
RX.remove(form, 'has-error');
}, 1600);
}
});
});
}
/**
* ------------------------------------------------------------
* Mobile Submenu Auto Close on Link Click
* ------------------------------------------------------------
*/
function setupMobileSubmenuLinkBehavior() {
RX.qsa(RX_HEADER_CONFIG.dropdownMenu + ' a', DOM.nav || document).forEach(function (link) {
RX.on(link, 'click', function () {
if (!RX.isDesktop()) {
closeMobileMenu();
}
});
});
}
/**
* ------------------------------------------------------------
* Add Loaded Class
* ------------------------------------------------------------
*/
function markHeaderLoaded() {
RX.add(DOM.body, 'rx-header-js-loaded');
if (DOM.header) {
RX.add(DOM.header, 'is-ready');
}
}
/**
* ------------------------------------------------------------
* Main Init
* ------------------------------------------------------------
*/
function initRXHeader() {
cacheElements();
if (!DOM.header) {
return;
}
setupAdminBarOffset();
setupMobileMenu();
setupDropdowns();
setupSearchOverlay();
setupSearchFormValidation();
setupBackToTop();
setupDarkMode();
setupActiveMenuLinks();
setupSmoothAnchors();
setupSkipLinks();
setupKeyboardEvents();
setupHeaderShadowObserver();
setupMobileSubmenuLinkBehavior();
updateHeaderHeightVariable();
handleScroll();
markHeaderLoaded();
RX.on(
window,
'scroll',
RX.throttle(handleScroll, RX_HEADER_CONFIG.scrollThrottle),
{ passive: true }
);
RX.on(
window,
'resize',
RX.debounce(handleResize, RX_HEADER_CONFIG.resizeDebounce)
);
RX.on(window, 'orientationchange', function () {
window.setTimeout(handleResize, 250);
});
}
/**
* ------------------------------------------------------------
* DOM Ready
* ------------------------------------------------------------
*/
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initRXHeader);
} else {
initRXHeader();
}
})();
Use these matching class names in your header HTML/PHP when possible:
<header class="rx-site-header">
<div class="rx-site-header__inner">
<button class="rx-header-menu-toggle" type="button" aria-label="Open menu">
Menu
</button>
<nav class="rx-primary-nav" aria-label="Primary menu">
<?php
wp_nav_menu([
'theme_location' => 'primary',
'menu_class' => 'rx-primary-nav__menu',
'container' => false,
]);
?>
</nav>
<button class="rx-search-toggle" type="button" aria-label="Open search">
Search
</button>
<button class="rx-dark-mode-toggle" type="button">
Mode
</button>
</div>
</header>
<div class="rx-mobile-menu-overlay" aria-hidden="true"></div>
<div class="rx-search-overlay" aria-hidden="true">
<button class="rx-search-close" type="button" aria-label="Close search">Close</button>
<?php get_search_form(); ?>
</div>
<div class="rx-scroll-progress-bar" aria-hidden="true"></div>
<button class="rx-back-to-top" type="button" aria-label="Back to top">
↑
</button>
And enqueue it like this in your theme:
function rx_theme_enqueue_header_chunk() {
wp_enqueue_script(
'rx-header-chunk',
get_template_directory_uri() . '/assets/static/static-chunks-js/chunk-002-header.js',
array(),
wp_get_theme()->get('Version'),
true
);
}
add_action('wp_enqueue_scripts', 'rx_theme_enqueue_header_chunk');
Important CSS helper classes you should later support:
.rx-scroll-locked {
overflow: hidden;
}
.rx-site-header {
position: sticky;
top: var(--rx-admin-bar-height, 0px);
z-index: 999;
}
.rx-site-header.is-hidden {
transform: translateY(-100%);
}
.rx-primary-nav.is-open {
visibility: visible;
opacity: 1;
}
.rx-search-overlay {
visibility: hidden;
opacity: 0;
}
.rx-search-overlay.is-open {
visibility: visible;
opacity: 1;
}
.rx-scroll-progress-bar {
position: fixed;
top: 0;
left: 0;
height: 3px;
width: 0;
z-index: 9999;
}
.rx-back-to-top {
opacity: 0;
pointer-events: none;
}
.rx-back-to-top.is-active {
opacity: 1;
pointer-events: auto;
}
html[data-rx-theme="dark"] {
color-scheme: dark;
}
This is a strong advanced starting file for chunk-002-header.js. It is big enough for a professional theme, but still safe, fast, and not dependent on jQuery.