JSFiddle - React, Tailwind, and code Playground

by Christopher

HTML

<div class="boxes-container">
  <div class="box">
    <h2>0</h2>
  </div>
  <div class="box">
    <h2>1</h2>
  </div>
  <div class="box">
    <h2>2</h2>
  </div>
  <div class="box">
    <h2>3</h2>
  </div>
  <div class="box">
    <h2>4</h2>
  </div>
  <div class="box">
    <h2>5</h2>
  </div>
  <div class="box">
    <h2>6</h2>
  </div>
</div>

<div class="boxes-container v30" data-direction="invert">
  <div class="box">
    <h2>A</h2>
  </div>
  <div class="box">
    <h2>B</h2>
  </div>
  <div class="box">
    <h2>C</h2>
  </div>
  <div class="box">
    <h2>D</h2>
  </div>
  <div class="box">
    <h2>E</h2>
  </div>
  <div class="box">
    <h2>F</h2>
  </div>
  <div class="box">
    <h2>G</h2>
  </div>
</div>

<div class="boxes-container v60">
  <div class="box">
    <h2>0</h2>
  </div>
  <div class="box">
    <h2>1</h2>
  </div>
  <div class="box">
    <h2>2</h2>
  </div>
  <div class="box">
    <h2>3</h2>
  </div>
  <div class="box">
    <h2>4</h2>
  </div>
  <div class="box">
    <h2>5</h2>
  </div>
  <div class="box">
    <h2>6</h2>
  </div>
</div>

CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font-family: Sans-Serif;
}
.boxes-container {
  position: fixed;
  display: flex;
  flex-wrap: nowrap;
  flex-direction: row;
  white-space: nowrap;
  min-width: -min-content;
}

.v30 {
  top: 30vh;
}

.v60 {
  top: 60vh;
}

.box {
  position: relative;
  margin: 0 !important;
  padding: 0 50px;
}
.box h2 {
  font-size: 5rem;
}

JavaScript

const eventHandler = (e) => {
  document.querySelectorAll(".boxes-container").forEach(container => {
    const cur = +container.dataset.cur || 0;
    container.dataset.before = container.dataset.cur;
    container.dataset.scrollspeed = (+container.dataset.scrollspeed || 0) +1;
    setTimeout(() => {
    	container.dataset.scrollspeed = +container.dataset.scrollspeed -1;
    }, 33 * +container.dataset.scrollspeed);
    let moveByPixels = Math.round(e.deltaY / (10 - Math.min(+container.dataset.scrollspeed,9)));
    if (container.dataset.direction == "invert") {
      moveByPixels *= -1;
    }
    container.style.left = `${cur + -moveByPixels}px`;
    container.dataset.cur = cur + -moveByPixels;
  });
};

window.addEventListener("wheel", eventHandler);
window.addEventListener("mousewheel", eventHandler);

const observer = new IntersectionObserver((entries, opts) => {
  entries.forEach(entry => {
    entry.target.classList.toggle('visible', entry.isIntersecting);
  });
  document.querySelectorAll(".boxes-container").forEach(container => {
    const before = (+container.dataset.before || 0),
      current = (+container.dataset.cur || 0),
      diff = before - current,
      boxes = [...container.querySelectorAll(".box")],
      visible = [...container.querySelectorAll(".box.visible")],
      first = boxes.indexOf(visible[0]),
      last = boxes.indexOf(visible[visible.length - 1]),
      adjust = (by) => {
        container.dataset.cur = +container.dataset.cur + by;
        container.dataset.before = +container.dataset.before + by;
        container.style.left = +container.dataset.cur + 'px';
      };
    if (diff >= 0) {
      if (first > 0) { // move the first to the end
      	const box = boxes[0];
        box.parentNode.append(box);
        adjust(box.clientWidth);
      }
    } else {
      if (last == 0 || first == 0) { // move the to first
        const box = boxes[boxes.length - 1];
        box.parentNode.prepend(box);
        adjust(-box.clientWidth);

     ...