JSFiddle - React, Tailwind, and code Playground
by Eugeny Venegrad
HTML
<h2>valera</h2>
<br><br><br><br><br>
<h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><h2>valera</h2>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<div id="titlelist"></div>
CSS
h2.hightlited {
color: green;
}
#titlelist {
position: fixed;
right: 0;
top: 0;
width: 100px;
}
#titlelist button {
display: block;
}
#titlelist button.hightlited {
background-color: green;
}
JavaScript
window.doScrolling = function(speed, duration) {
var startingY = window.pageYOffset
var elementY = speed
var targetY = document.body.scrollHeight - elementY < window.innerHeight ? document.body.scrollHeight - window.innerHeight : elementY
var diff = targetY - startingY
var easing = function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
var start
if (!diff) return
window.requestAnimationFrame(function step(timestamp) {
if (!start) start = timestamp
var time = timestamp - start
var percent = Math.min(time / duration, 1)
percent = easing(percent)
window.scrollTo(0, startingY + diff * percent)
if (time < duration) {
window.requestAnimationFrame(step)
}
})
}
const titles = document.querySelectorAll('h2');
observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const titleindex = [...entry.target.parentElement.querySelectorAll(':scope > h2')].indexOf(entry.target);
let buttons = document.querySelectorAll('#titlelist > button');
if (entry.intersectionRatio > 0) {
entry.target.classList.add('hightlited');
for (let i = 0; i < buttons.length; i++) {
buttons[i].classList.remove('hightlited');
}
document.querySelectorAll("#titlelist > button")[titleindex].classList.add('hightlited');
} else {
entry.target.classList.remove('hightlited');
}
});
});
titles.forEach(title => {
let titleOffset = title.offsetTop - 20;
const titleindex = [...title.parentElement.querySelectorAll(':scope > h2')].indexOf(title);
const titlesDiv = document.getElementById("titlelist").appendChild(document.createElement('button'));
titlesDiv.innerHTML = title.textContent;
titlesDiv.setAttribute("onclick", "doScrolling("+titleOffset+", 500)");
observer.observe(title);
});