Svelte Example: Smooth Scroll

svelte
<script lang="ts">
  import {
    createVirtualizer,
    elementScroll,
    type VirtualizerOptions,
  } from '@tanstack/svelte-virtual'
  import { onMount } from 'svelte'

  let virtualListEl: HTMLDivElement
  let time = Date.now()
  let randomIndex = Math.floor(Math.random() * 10000)
  let scrollToFn: VirtualizerOptions<any, any>['scrollToFn'] = () => {}

  function easeInOutQuint(t: number) {
    return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t
  }

  $: virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({
    count: 10000,
    getScrollElement: () => virtualListEl,
    estimateSize: () => 35,
    overscan: 5,
    scrollToFn,
  })

  onMount(() => {
    scrollToFn = (offset, canSmooth, instance) => {
      const duration = 1000
      const start = virtualListEl.scrollTop
      const startTime = (time = Date.now())

      function run() {
        if (time !== startTime) return
        const now = Date.now()
        const elapsed = now - startTime
        const progress = easeInOutQuint(Math.min(elapsed / duration, 1))
        const interpolated = start + (offset - start) * progress

        if (elapsed < duration) {
          elementScroll(interpolated, canSmooth, instance)
          requestAnimationFrame(run)
        } else {
          elementScroll(interpolated, canSmooth, instance)
        }
      }

      requestAnimationFrame(run)
    }
  })
</script>

<main>
  <p>
    This smooth scroll example uses the <code>`scrollToFn`</code> to implement a
    custom scrolling function for the methods like
    <code>`scrollToIndex`</code> and <code>`scrollToOffset`</code>
  </p>
  <br />
  <br />

  <div>
    <button
      on:click={() => {
        $virtualizer.scrollToIndex(randomIndex)
        randomIndex = Math.floor(Math.random() * 10000)
      }}
    >
      Scroll To Random Index ({randomIndex})
    </button>
  </div>

  <br />
  <br />

  <div class="list scroll-container" bind:this={virtualListEl}>
    <div
      style="position: relative; height: {$virtualizer.getTotalSize()}px; width: 100%;"
    >
      {#each $virtualizer.getVirtualItems() as row (row.index)}
        <div
          class:list-item-even={row.index % 2 === 0}
          class:list-item-odd={row.index % 2 === 1}
          style="position: absolute; top: 0; left: 0; width: 100%; height: {row.size}px; transform: translateY({row.start}px);"
        >
          Row {row.index}
        </div>
      {/each}
    </div>
  </div>
</main>

<style>
  .scroll-container {
    height: 200px;
    width: 400px;
    overflow: auto;
  }
</style>
<script lang="ts">
  import {
    createVirtualizer,
    elementScroll,
    type VirtualizerOptions,
  } from '@tanstack/svelte-virtual'
  import { onMount } from 'svelte'

  let virtualListEl: HTMLDivElement
  let time = Date.now()
  let randomIndex = Math.floor(Math.random() * 10000)
  let scrollToFn: VirtualizerOptions<any, any>['scrollToFn'] = () => {}

  function easeInOutQuint(t: number) {
    return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t
  }

  $: virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({
    count: 10000,
    getScrollElement: () => virtualListEl,
    estimateSize: () => 35,
    overscan: 5,
    scrollToFn,
  })

  onMount(() => {
    scrollToFn = (offset, canSmooth, instance) => {
      const duration = 1000
      const start = virtualListEl.scrollTop
      const startTime = (time = Date.now())

      function run() {
        if (time !== startTime) return
        const now = Date.now()
        const elapsed = now - startTime
        const progress = easeInOutQuint(Math.min(elapsed / duration, 1))
        const interpolated = start + (offset - start) * progress

        if (elapsed < duration) {
          elementScroll(interpolated, canSmooth, instance)
          requestAnimationFrame(run)
        } else {
          elementScroll(interpolated, canSmooth, instance)
        }
      }

      requestAnimationFrame(run)
    }
  })
</script>

<main>
  <p>
    This smooth scroll example uses the <code>`scrollToFn`</code> to implement a
    custom scrolling function for the methods like
    <code>`scrollToIndex`</code> and <code>`scrollToOffset`</code>
  </p>
  <br />
  <br />

  <div>
    <button
      on:click={() => {
        $virtualizer.scrollToIndex(randomIndex)
        randomIndex = Math.floor(Math.random() * 10000)
      }}
    >
      Scroll To Random Index ({randomIndex})
    </button>
  </div>

  <br />
  <br />

  <div class="list scroll-container" bind:this={virtualListEl}>
    <div
      style="position: relative; height: {$virtualizer.getTotalSize()}px; width: 100%;"
    >
      {#each $virtualizer.getVirtualItems() as row (row.index)}
        <div
          class:list-item-even={row.index % 2 === 0}
          class:list-item-odd={row.index % 2 === 1}
          style="position: absolute; top: 0; left: 0; width: 100%; height: {row.size}px; transform: translateY({row.start}px);"
        >
          Row {row.index}
        </div>
      {/each}
    </div>
  </div>
</main>

<style>
  .scroll-container {
    height: 200px;
    width: 400px;
    overflow: auto;
  }
</style>
Subscribe to Bytes

Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.

Bytes

No spam. Unsubscribe at any time.

Subscribe to Bytes

Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.

Bytes

No spam. Unsubscribe at any time.