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);
});