Smooth-Scroll Section Nav
Generates a floating vertical dot-nav from h2 headings on the page with smooth-scroll and active-section highlighting.
Module3 parts · by SnipCraft
PHPMarkup
php
<?php
if ( ! function_exists( 'scseed_section_nav_output' ) ) {
function scseed_section_nav_output() {
if ( ! is_singular( 'post' ) ) {
return;
}
echo '<nav id="scseed-section-nav" aria-label="' .
esc_attr__( 'Page sections', 'scseed' ) . '"></nav>';
}
add_action( 'wp_footer', 'scseed_section_nav_output' );
}CSSStyles
css
#scseed-section-nav {
position: fixed;
right: 1.25rem;
top: 50%;
transform: translateY(-50%);
z-index: 9000;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
#scseed-section-nav a {
display: block;
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(0,0,0,0.25);
text-decoration: none;
transition: background 0.2s, transform 0.2s;
position: relative;
}
#scseed-section-nav a:hover,
#scseed-section-nav a.active {
background: #0073aa;
transform: scale(1.4);
}
#scseed-section-nav a::after {
content: attr(data-label);
position: absolute;
right: 1.4rem;
top: 50%;
transform: translateY(-50%);
background: rgba(0,0,0,0.75);
color: #fff;
font-size: 0.72rem;
white-space: nowrap;
padding: 2px 6px;
border-radius: 3px;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
#scseed-section-nav a:hover::after {
opacity: 1;
}
@media (max-width: 900px) {
#scseed-section-nav { display: none; }
}JavaScriptScript
js
(function () {
'use strict';
var nav = document.getElementById('scseed-section-nav');
if (!nav) { return; }
var headings = Array.prototype.slice.call(document.querySelectorAll('.entry-content h2, article h2'));
if (headings.length < 2) {
nav.style.display = 'none';
return;
}
headings.forEach(function (h, i) {
if (!h.id) { h.id = 'scseed-sec-' + i; }
var a = document.createElement('a');
a.href = '#' + h.id;
a.setAttribute('data-label', h.textContent.trim().substring(0, 40));
a.setAttribute('aria-label', h.textContent.trim());
nav.appendChild(a);
a.addEventListener('click', function (e) {
e.preventDefault();
h.scrollIntoView({ behavior: 'smooth' });
});
});
var links = Array.prototype.slice.call(nav.querySelectorAll('a'));
function onScroll() {
var mid = window.scrollY + window.innerHeight * 0.4;
var active = null;
headings.forEach(function (h, i) {
if (h.getBoundingClientRect().top + window.scrollY <= mid) {
active = links[i];
}
});
links.forEach(function (l) { l.classList.remove('active'); });
if (active) { active.classList.add('active'); }
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
})();#frontend#ux#navigation#accessibility