<scrolly-ruler>
  <ruler-indicator></ruler-indicator>
  <ruler-grid>
    <div data-ruler-value="0"></div>
    <span data-ruler-value="0.25"></span>
    <span data-ruler-value="0.5"></span>
    <span data-ruler-value="0.75"></span>

    <div data-ruler-value="1"></div>
    <span data-ruler-value="1.25"></span>
    <span data-ruler-value="1.5"></span>
    <span data-ruler-value="1.75"></span>

    <div data-ruler-value="2"></div>
    <span data-ruler-value="2.25"></span>
    <span data-ruler-value="2.5"></span>
    <span data-ruler-value="2.75"></span>

    <div data-ruler-value="3"></div>
    <span data-ruler-value="3.25"></span>
    <span data-ruler-value="3.5"></span>
    <span data-ruler-value="3.75"></span>

    <div data-ruler-value="4"></div>
    <span data-ruler-value="4.25"></span>
    <span data-ruler-value="4.5"></span>
    <span data-ruler-value="4.75"></span>

    <div data-ruler-value="5"></div>
    <span data-ruler-value="5.25"></span>
    <span data-ruler-value="5.5"></span>
    <span data-ruler-value="5.75"></span>

    <div data-ruler-value="6"></div>
    <span data-ruler-value="6.25"></span>
    <span data-ruler-value="6.5"></span>
    <span data-ruler-value="6.75"></span>

    <div data-ruler-value="7"></div>
    <span data-ruler-value="7.25"></span>
    <span data-ruler-value="7.5"></span>
    <span data-ruler-value="7.75"></span>

    <div data-ruler-value="8"></div>
    <span data-ruler-value="8.25"></span>
    <span data-ruler-value="8.5"></span>
    <span data-ruler-value="8.75"></span>

    <div data-ruler-value="9"></div>
    <span data-ruler-value="9.25"></span>
    <span data-ruler-value="9.5"></span>
    <span data-ruler-value="9.75"></span>

    <div data-ruler-value="10"></div>
    <span data-ruler-value="10.25"></span>
    <span data-ruler-value="10.5"></span>
    <span data-ruler-value="10.75"></span>

    <div data-ruler-value="11"></div>
    <span data-ruler-value="11.25"></span>
    <span data-ruler-value="11.5"></span>
    <span data-ruler-value="11.75"></span>

    <div data-ruler-value="12"></div>
  </ruler-grid>
</scrolly-ruler> 
<input type="number" name="ruler" min="0" max="12" step=".25" value="0">
@import "https://unpkg.com/open-props" layer(support.design-system);
@import "https://unpkg.com/open-props/normalize.min.css" layer(support.demo);
@import "https://unpkg.com/open-props/buttons.min.css" layer(support.demo);

@keyframes promote {
  0%, 100% {
    opacity: .5;
    transform: scale(.25);
  }
  50% {
    transform: scale(1);
    opacity: 1;
  }
}

scrolly-ruler {
  container: --scrolly-ruler / inline-size;
  width: clamp(90vw, 50vw, 12in);
  height: 1in;

  background: var(--surface-2);
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-6);

  display: grid;
  grid-template-rows: auto 1fr;
  position: relative;
  overflow: clip;
  counter-set: --inches;

  @media (prefers-color-scheme: light) {
    background: white;
  }

  & > ruler-indicator {
    content: "";
    place-self: center;
    height: .2in;
    width: .2in;
    border-radius: var(--radius-1);
    background: var(--surface-4);
    place-self: start center;
    transform-origin: center;
    transform: translateX(-50%) rotateZ(.125turn) translateY(-75%);
    transition: background-color .3s ease;

    &.has-target {
      background: var(--link);
    }
  }

  > ruler-grid {
    display: grid;
    grid-auto-flow: column;
    place-items: end;
    gap: calc(1in / 5);

    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-behavior: smooth;
    touch-action: pan-x;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;

    &:focus {
      outline: none;
    }

    &::before, &::after {
      content: "";
      display: inline-block;
      width: 80cqi;
    }

    & > * {
      background: var(--gray-4);
      inline-size: 1px;
      scroll-snap-align: center;
      border-top-left-radius: 2px;
      border-top-right-radius: 2px;
    }

    & > div {
      background: var(--gray-5);
      inline-size: 2px;
      block-size: .25in;

      view-timeline-name: --inch-timeline;
      view-timeline-axis: x;
      
      &:not(:first-of-type) {
        counter-increment: --inches;
      }

      &::before {
        display: inline-block;
        content: counter(--inches);
        position: relative;
        top: 0;
        left: 0;
        translate: -50% -1lh;
        transform-origin: bottom center;

        color: var(--text-2);
        font-size: var(--font-size-5);
        font-weight: 200;
        text-align: center;

        animation: promote var(--ease-in-out-1) both;
        animation-timeline: --inch-timeline;
        animation-range: cover, cover 30% cover 70%;
      }
    }

    & > span {
      block-size: .1in;
      transition: 
        background-color .3s ease,
        block-size .3s ease;

      &:nth-of-type(3n - 1) {
        block-size: .15in;
      }

      &.has-target {
        block-size: .5in;
        background: var(--link);
      }
    }
  }
}

@layer support.demo {
  body {
    display: grid;
    place-content: center;
    place-items: center;
    padding: var(--size-5);
    gap: var(--size-5);
  } 
}
const ruler = document.querySelector('scrolly-ruler')
const rulerInput = document.querySelector('input[type="number"]')
const rulerGrid = ruler.querySelector(':scope ruler-grid')
const rulerMark = ruler.querySelector(':scope ruler-indicator')

rulerGrid.onscrollsnapchange = e => {
  rulerMark.classList.add('has-target')
  e.snapTargetInline.classList.add('has-target')
}

rulerGrid.onscrollsnapchanging = e => {
  rulerInput.value = e.snapTargetInline.dataset.rulerValue
  rulerMark.classList.remove('has-target')
  rulerGrid.querySelector(':scope span.has-target').classList.remove('has-target')
}

rulerInput.oninput = e => {
  const destination = rulerGrid.querySelector(`:scope > [data-ruler-value="${e.target.value}"]`)
  const scrollX = destination.offsetLeft - (rulerGrid.offsetWidth / 2)

  rulerGrid.scrollTo(scrollX, 0, {
    behavior: 'smooth',
  })
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.