<div class="controls">
  <label for="vertical">Vertical</label>
  <input type="radio" checked name="orientation" id="vertical"/>
  <label for="horizontal">Horizontal</label>
  <input type="radio" name="orientation" id="horizontal"/>
</div>
<section class="scroller">
  <article>
    <div class="indicator indicator--top">
      <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" />
      </svg>
    </div>
  <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Amet animi consequatur atque accusantium soluta cumque, temporibus ipsam eos voluptas porro architecto expedita quisquam eum et necessitatibus culpa odio, laudantium reprehenderit.</p>
  <p>Maxime perferendis nostrum, temporibus quibusdam delectus, rerum laudantium ad eius reprehenderit labore accusamus voluptatibus doloremque, eveniet autem veritatis nulla nam ipsum quaerat repudiandae ut perspiciatis iste. Voluptatibus quasi ea natus.</p>
  <p>Optio sapiente ipsum, non autem aspernatur aperiam magnam! Vel accusantium unde quibusdam neque. A ullam quod quia aspernatur molestias voluptatem ea in deleniti ad explicabo, pariatur facilis harum inventore aliquam.</p>
  <p>Excepturi suscipit nulla iure sit, doloremque numquam. Ullam id, earum neque cum accusamus debitis illo, dolorem nostrum iste amet optio culpa. Vero labore architecto, soluta at placeat dolorum aliquam debitis.</p>
  <p>Minus amet laudantium ex ab. Harum praesentium in quia accusantium aut perferendis iure inventore impedit officiis, pariatur excepturi voluptate nostrum ipsam quam quod recusandae voluptatibus sit cupiditate fugit ducimus distinctio.</p>
  <p>Cumque, dolorum. Vitae, odio tempore. Illum unde architecto eligendi et. Non quibusdam iusto adipisci ullam est porro magni mollitia neque laboriosam doloribus tempore modi laudantium nostrum fugit, provident sunt dolorum!</p>
    <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus, quis deserunt. Nemo aliquid ratione magni labore asperiores. Consequuntur, laborum perspiciatis porro optio soluta, nesciunt est laudantium eligendi voluptatum quos vel?</p>
    <p>Doloremque eligendi velit voluptas eveniet veniam nesciunt, esse harum exercitationem laborum, soluta ad, vitae ut ullam consequuntur culpa inventore reiciendis iste dolorum! Incidunt corrupti eligendi officiis dolor nemo quidem recusandae.</p>
    <p>Animi inventore culpa eum eaque sed tempore illo, ipsa ea optio soluta atque itaque quos beatae dolore. Nam aliquid inventore itaque ab perferendis ratione soluta quod est, doloribus fugit magni.</p>
    <p>Ad excepturi ex veniam quam quas nesciunt deleniti suscipit voluptatem omnis enim. Soluta magni dolore nostrum omnis? Tempora soluta doloremque vel veritatis, repellendus dolorem dicta dignissimos quaerat atque, recusandae quis.</p>
    <p>Culpa beatae recusandae impedit unde saepe quos explicabo alias reprehenderit accusantium laudantium aspernatur, commodi veniam quasi vel ratione corrupti aliquam consequatur similique enim sunt! Quod facere cum at saepe cumque.</p>
    <p>Rem eos laudantium culpa enim quibusdam, in consequatur non ea, iste illum minima aperiam accusamus ducimus repudiandae soluta, blanditiis at debitis magni excepturi delectus? Inventore necessitatibus laudantium accusantium ullam ad.</p>
    <p>Alias deserunt possimus ipsum nemo provident dolore, reprehenderit id? Similique voluptas ipsa perferendis eius et, a, odit minus neque laboriosam modi quisquam, vel eum blanditiis! Aliquid soluta recusandae nostrum dolor.</p>
    <p>Animi quas dolorum tempora consequatur sequi? Ratione ipsa consequuntur est quibusdam unde hic, inventore rerum alias, quasi aut maiores! Quaerat eligendi ut vitae, veniam officiis delectus animi repudiandae exercitationem! Ducimus.</p>
    <p>Necessitatibus vitae porro, quos eum dolorem, aliquam corporis perspiciatis cumque facilis maxime nobis praesentium quia earum tempora quisquam provident ab laborum ea nostrum facere esse fugiat? Nostrum deleniti eligendi debitis.</p>
    <p>Consequatur odit reprehenderit culpa, delectus libero pariatur placeat nobis ut, quidem cum molestias facere porro aperiam similique perferendis maxime ea debitis numquam, possimus hic. Quisquam modi ipsa consequuntur nemo sint!</p>
    <div class="indicator indicator--bottom">
      <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
      </svg>
  </article>
  </div>
</section>
* {
  box-sizing: border-box;
}

body {
  display: grid;
  place-items: center;
  min-height: 100vh;
  background: hsl(0 0% 80%);
  accent-color: hsl(250 90% 70%);
}

.controls {
  position: fixed;
  top: 1rem;
  right: 1rem;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 1rem;
}

.scroller {
  width: 50ch;
  height: 80vh;
  overflow: auto;
  position: relative;
  background: canvas;
  padding: 0 1rem;
  resize: both;
  color: hsl(0 0% 18%);
  border-radius: 0.5rem;
  box-shadow: 0 0 2rem -1rem hsl(0 0% 10% / 0.75);
}

svg {
  width: 24px;
}

.indicator {
  position: sticky;
  display: flex;
  align-items: center;
  justify-content: center;
  background: canvas;
  height: 48px;
  /* Scroll stuff */
  --scroll-buffer: 2rem;
  opacity: 0;
  animation: reveal both linear;
  animation-timeline: scroll(nearest);
  animation-range: 0 var(--scroll-buffer);
}

.indicator--top {
  top: 0;
  border-bottom: 1px solid hsl(0 0% 0% / 0.5);
}

:root:has(#horizontal:checked) article {
  gap: 2rem;
  display: flex;
  align-items: center;
  height: 100%;
  width: 800vw;
}

:root:has(#horizontal:checked) article p {
  flex: 1 0 100cqi;
  display: grid;
  place-items: center;
  padding: 4rem;
  scroll-snap-align: center;
}

:root:has(#horizontal:checked) .scroller {
  padding: 1rem 0;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}

:root:has(#horizontal:checked) .indicator--bottom svg,
:root:has(#horizontal:checked) .indicator--top svg {
  rotate: -90deg;
}

:root:has(#horizontal:checked) .indicator {
  height: 100%;
  width: 2rem;
  position: absolute;
  animation-timeline: --article;
  clip-path: inset(2rem 0 2rem 0);
}

:root:has(#horizontal:checked) .indicator--top {
  border-bottom: none;
  align-self: flex-start;
  left: 0;
  border-right: 1px solid hsl(0 0% 0% / 0.5);
}


:root:has(#horizontal:checked) .scroller {
  container-type: inline-size;
}

.indicator--bottom {
  bottom: 0;
  border-top: 1px solid hsl(0 0% 0% / 0.5);
  animation-range: calc(100% - var(--scroll-buffer)) 100%;
  animation-direction: reverse;
}

:root:has(#horizontal:checked) .indicator--bottom {
  right: 0;
  bottom: unset;
  border-left: 1px solid hsl(0 0% 0% / 0.5);
  border-top: none;
}

:root:has(#horizontal:checked) article {
  scroll-timeline: --article;
  scroll-timeline-axis: inline;
  width: 100%;
  overflow: auto;
  scroll-snap-type: x mandatory;
}

@keyframes reveal {
  to {
    opacity: 1;
  }
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js