<div class="list">
  <ul class="list__track">
    <li class="list__item">Lorem ipsum dolor sit amet.</li>
    <li class="list__item">Quaerat excepturi ea aspernatur magnam.</li>
    <li class="list__item">Officia corrupti nam excepturi mollitia.</li>
    <li class="list__item">Quod consequatur corporis odit possimus.</li>
    <li class="list__item">Voluptas qui sapiente corporis debitis!</li>
    <li class="list__item">Nemo ipsam quo natus sint!</li>
    <li class="list__item">Cumque pariatur aliquid similique sunt?</li>
    <li class="list__item">Repellendus, alias repellat. Nihil, esse.</li>
    <li class="list__item">Dolorem nam eius esse voluptatum?</li>
    <li class="list__item">Officiis suscipit consectetur accusamus molestias!</li>
    <li class="list__item">Animi, tempore eius. Inventore, aperiam!</li>
    <li class="list__item">Dolor quis illo vitae tempora!</li>
    <li class="list__item">Consectetur quisquam velit magnam illo?</li>
    <li class="list__item">Maxime corporis suscipit velit tempora?</li>
    <li class="list__item">Nihil quisquam fugit minus animi!</li>
    <li class="list__item">Labore non delectus iusto iste.</li>
    <li class="list__item">Eos eveniet vero consequuntur exercitationem.</li>
    <li class="list__item">Dolorem id aperiam nisi repellendus.</li>
    <li class="list__item">Facere vero quos voluptates maiores!</li>
    <li class="list__item">Consequuntur ut labore pariatur perspiciatis.</li>
    <li class="list__item">Non molestias cum enim voluptate!</li>
    <li class="list__item">Ex quae laborum animi repudiandae.</li>
    <li class="list__item">Ipsa unde eveniet nemo rem?</li>
    <li class="list__item">Voluptate, magnam! Non, ab tempore.</li>
    <li class="list__item">Possimus pariatur commodi harum alias?</li>
    <li class="list__item">Qui odit vero voluptas fugiat!</li>
    <li class="list__item">Libero commodi magni praesentium doloremque!</li>
    <li class="list__item">Ducimus laboriosam ullam illum voluptatem.</li>
    <li class="list__item">Accusamus distinctio perferendis laboriosam quidem.</li>
    <li class="list__item">Blanditiis asperiores veniam sint odio.</li>
    <li class="list__item">Fuga cupiditate repudiandae natus? Eos.</li>
    <li class="list__item">Asperiores distinctio quae molestiae optio.</li>
    <li class="list__item">Cupiditate illum suscipit repudiandae facilis.</li>
    <li class="list__item">Repudiandae ducimus ipsam architecto nostrum!</li>
    <li class="list__item">Mollitia explicabo eaque voluptatem nostrum?</li>
    <li class="list__item">Laborum itaque a aliquam eum?</li>
    <li class="list__item">Vitae nobis alias dolorum atque?</li>
    <li class="list__item">Ea nulla quisquam consectetur tenetur!</li>
    <li class="list__item">Commodi quam possimus tempore eos.</li>
    <li class="list__item">Dolores, temporibus. Quibusdam, fugit placeat?</li>
    <li class="list__item">Autem delectus maiores sunt in!</li>
    <li class="list__item">Corporis accusamus architecto modi alias!</li>
  </ul>
</div>
* {
  box-sizing: border-box;
}

body {
  display: flex;
  flex-direction: column;
  margin: 0;
  background-color: #112;
  color: #fff;
  font-family: sans-serif;
  min-height: 100vh;
}

.list {
  max-width: 768px;
  max-height: 320px;
  margin: auto;
  width: 100%;
  height: 100vh;
  padding: 0 15px;
  overflow: hidden;
}

.list__track {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 28px;
  line-height: 1.4;
  will-change: transform;
}

.list__item {
  transform-origin: 0 50%;
  will-change: transform, opacity;
}
const lerp = (a, b, t) => (1 - t) * a + t * b;

const list = document.querySelector('.list');
const track = list.querySelector('.list__track');
const items = Array.from(
  list.querySelectorAll('.list__item'),
  el => ({ el, rect: { x: 0, y: 0, w: 0, h: 0 } })
);

let scrollY = 0;
let scrollSpeed = 0.5;
let listHeight;

function resize() {
  listHeight = list.offsetHeight;
  items.forEach(it => {
    it.rect.x = it.el.offsetLeft;
    it.rect.y = it.el.offsetTop;
    it.rect.w = it.el.offsetWidth;
    it.rect.h = it.el.offsetHeight;
  });
}

resize();
window.addEventListener('resize', resize);

function update() {
  scrollY += scrollSpeed;
  
  const minScroll = -listHeight / 2 + items[0].rect.h / 2;
  const maxScroll = list.scrollHeight - listHeight / 2 - items[items.length - 1].rect.h / 2;
  
  if (scrollY > maxScroll) {
    scrollSpeed = -Math.abs(scrollSpeed);
    scrollY = maxScroll;
  }
  else if (scrollY < minScroll) {
    scrollSpeed = Math.abs(scrollSpeed);
    scrollY = minScroll;
  }
  
  track.style.transform = `translateY(${-scrollY}px)`;
  
  items.forEach(it => {
    const isInView =
      it.rect.y + it.rect.h >= scrollY
      && it.rect.y <= scrollY + listHeight;
    
    if (!isInView) return;
    
    const dist = it.rect.y + it.rect.h / 2 - scrollY - listHeight / 2;
    const f = Math.abs(dist / listHeight) * 2;
    it.el.style.transform = `scale(${lerp(1, 0.5, f)})`;
    it.el.style.opacity = Math.max(0, lerp(1, 0, f));
  });
  
  requestAnimationFrame(update);
}

requestAnimationFrame(update);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.