JSFiddle - React, Tailwind, and code Playground
HTML
<div class="menu">
<a class="menu__item" href="#1">One</a>
<a class="menu__item" href="#2">Two</a>
<a class="menu__item" href="#3">Three</a>
<a class="menu__item" href="#4">Four</a>
<a class="menu__item" href="#5">Five</a>
<a class="menu__item" href="#6">Six</a>
<div class="menu__indicator"></div>
</div>
<div class="content">
<section class="content__section" id="1">
<h1>One</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut placerat luctus nunc eget consequat. Nulla vel ante dictum, congue diam eu, sagittis ante. Morbi vitae mauris a arcu vulputate varius. Nam laoreet mollis orci, at imperdiet diam convallis nec. Etiam enim nunc, sodales ut laoreet at, lacinia eu erat. Integer dignissim risus ut lacus tincidunt, tincidunt congue sem consectetur. Donec eu luctus metus, in tincidunt elit. Nulla facilisis iaculis arcu, sed vehicula arcu tincidunt nec. Nam lacinia nec nunc ac ornare.</p>
</section>
<section class="content__section" id="2">
<h1>Two</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut placerat luctus nunc eget consequat. Nulla vel ante dictum, congue diam eu, sagittis ante. Morbi vitae mauris a arcu vulputate varius. Nam laoreet mollis orci, at imperdiet diam convallis nec. Etiam enim nunc, sodales ut laoreet at, lacinia eu erat. Integer dignissim risus ut lacus tincidunt, tincidunt congue sem consectetur. Donec eu luctus metus, in tincidunt elit. Nulla facilisis iaculis arcu, sed vehicula arcu tincidunt nec. Nam lacinia nec nunc ac ornare.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut placerat luctus nunc eget consequat. Nulla vel ante dictum, congue diam eu, sagittis ante. Morbi vitae mauris a arcu vulputate varius. Nam laoreet mollis orci, at imperdiet diam convallis nec. Etiam enim nunc, sodales ut laoreet at, lacinia eu erat. Integer dignissim risus ut lacus tincidunt, tincidunt congue sem consectetur. Donec...
SCSS
html, body { margin: 0; padding: 0; }
* { box-sizing: border-box; }
body {
background: #262c43;
color: #ffffff;
font-family: sans-serif;
}
a { color: #ffffff; text-decoration: none; }
.menu {
position: fixed;
top: 0;
left: 0;
width: 150px;
display: flex;
flex-direction: column;
height: 100vh;
&__item {
flex: 1 0 auto;
position: relative;
display: flex;
justify-content: flex-end;
align-items: center;
text-align: right;
padding-right: 25px;
border-right: 10px solid #404060;
}
}
.menu__indicator {
display: inline-block;
width: 10px;
background: #ffffff;
position: fixed;
top: 0;
left: 140px;
transition: top 750ms ease-in-out;
}
@for $i from 1 through 20 {
:nth-child(#{$i}) + .menu__indicator {
height: calc(100vh / #{$i});
}
}
.content {
position: absolute;
left: 150px;
overflow-x: hidden;
&__section {
min-height: 100vh;
padding: 25px;
p {
position: relative;
left: 100vw;
opacity: 0;
}
&.show p {
transition: left 750ms, opacity 750ms;
left: 0;
opacity: 1;
}
}
}
Babel + JSX
/* utils */
const qs = document.querySelector.bind(document);
const qsa = selector => [...document.querySelectorAll(selector)];
const scrollY = () => Math.max(
window.pageYOffset,
document.body.scrollTop,
document.documentElement.scrollTop
);
/* common */
const indicator = qs('.menu__indicator');
const menuItems = qsa('.menu__item');
const sections = qsa('.content__section');
const numSections = sections.length;
/* smooth scrolling jump links */
const menuClickHandler = e => {
const selector = e.target.href.replace(/^.*#/, '');
const targetSection = qs(`[id="${selector}"]`);
const start = scrollTop();
const end = targetSection.getBoundingClientRect().top + start;
new Tweezer({
start,
end,
duration: 750
}).on('tick', y => window.scrollTo(0, y))
.begin();
e.preventDefault();
return false;
};
menuItems.forEach(item =>
item.addEventListener('click', menuClickHandler)
);
/* content hide/show & indicator movement */
const moveIndicator = index => {
const percentage = (100 / numSections) * index;
indicator.style.top = `${percentage}vh`;
};
const checkScroll = () => {
sections.forEach((section, index) => {
const { clientHeight } = document.documentElement;
const scrollTop = scrollTop();
const scrollBottom = scrollTop + clientHeight;
const { top, bottom } = section.getBoundingClientRect();
const sectionTop = top + scrollTop;
const sectionBottom = bottom + scrollTop;
const tolerance = clientHeight / 2;
if (scrollTop > sectionBottom ||
scrollBottom < sectionTop) {
section.classList.remove('show');
}
if (scrollBottom - tolerance >= sectionTop &&
scrollTop + tolerance <= sectionBottom) {
section.classList.add('show');
moveIndicator(index);
}
});
};
window.addEventListener('scroll',...