Skip to content

Migrate from v2 to v3 ​

Use this guide to migrate from v2.0.1 to v3.

Component users ​

If you mainly use RecycleScroller, DynamicScroller, or WindowScroller, migration is usually small and mostly comes down to updated options and guidance.

RecycleScroller ​

New capabilities and guidance:

  • keyField can now be either a string field name or a resolver function (item, index) => string | number.
  • itemSize can now be either a fixed number, null, or a resolver function (item, index) => number.
  • disableTransform lets pooled views use top / left positioning instead of transforms.
  • flowMode keeps active pooled views in normal document flow with spacer elements.
  • startSpacerSize and endSpacerSize are exposed for advanced integrations when flowMode is active.
  • flowMode only supports vertical single-axis layouts. It does not support horizontal virtualization or grids, and hiddenPosition does not apply there.

DynamicScroller ​

DynamicScroller now supports the same layout controls as the core scroller:

  • disableTransform
  • flowMode
  • hiddenPosition

Pass :index="index" to DynamicScrollerItem when you use simple-array mode or a function keyField. With object items and a normal string keyField such as 'id', index is still optional.

WindowScroller ​

WindowScroller is now the preferred documented path when the browser window owns scrolling.

  • Use WindowScroller for new window-scrolling code.
  • Keep pageMode on RecycleScroller only for older compatibility behavior.

Headless composables ​

If you use the headless APIs, this is where the important migration work lives.

useRecycleScroller ​

Main changes:

  • handleScroll is no longer part of the return value.
  • getViewStyle(view) is now the preferred way to position pooled views.
  • startSpacerSize and endSpacerSize were added for flowMode.
  • keyField can now be a resolver function.
  • itemSize can now be a resolver function.

Before:

vue
<script setup lang="ts">
const scrollerEl = useTemplateRef('scrollerEl')

const {
  pool,
  totalSize,
  handleScroll,
} = useRecycleScroller(options, scrollerEl)
</script>

<template>
  <div ref="scrollerEl" class="scroller" @scroll.passive="handleScroll">
    <div :style="{ minHeight: `${totalSize}px`, position: 'relative' }">
      <article
        v-for="view in pool"
        :key="view.nr.id"
        :style="{
          transform: `translateY(${view.position}px)`,
          visibility: view.nr.used ? 'visible' : 'hidden',
          pointerEvents: view.nr.used ? undefined : 'none',
        }"
      />
    </div>
  </div>
</template>

After:

vue
<script setup lang="ts">
const scrollerEl = useTemplateRef('scrollerEl')

const {
  pool,
  totalSize,
  getViewStyle,
} = useRecycleScroller(options, scrollerEl)
</script>

<template>
  <div ref="scrollerEl" class="scroller">
    <div :style="{ minHeight: `${totalSize}px`, position: 'relative' }">
      <article
        v-for="view in pool"
        :key="view.nr.id"
        :style="getViewStyle(view)"
      />
    </div>
  </div>
</template>

If you adopt flowMode, render spacer elements from startSpacerSize and endSpacerSize instead of relying on one absolutely positioned inner wrapper.

useDynamicScroller ​

Main changes:

  • handleScroll is no longer returned.
  • Pooled view objects were flattened for normal rendering.
  • The original item moved from view.item.item to view.item.
  • Advanced size metadata still exists at view.itemWithSize.
  • getViewStyle(view) was added.
  • startSpacerSize and endSpacerSize were added.
  • v-dynamic-scroller-item examples should use view.id where relevant and direct view.item.* access.

Before:

vue
<script setup lang="ts">
const scrollerEl = useTemplateRef('scrollerEl')

const {
  pool,
  totalSize,
  handleScroll,
  vDynamicScrollerItem,
} = useDynamicScroller(options)
</script>

<template>
  <div ref="scrollerEl" class="scroller" @scroll.passive="handleScroll">
    <div :style="{ minHeight: `${totalSize}px`, position: 'relative' }">
      <article
        v-for="view in pool"
        :key="view.nr.id"
        v-dynamic-scroller-item="{ view }"
      >
        <h4>{{ view.item.item.title }}</h4>
        <p>{{ view.item.item.body }}</p>
      </article>
    </div>
  </div>
</template>

After:

vue
<script setup lang="ts">
const scrollerEl = useTemplateRef('scrollerEl')

const {
  pool,
  totalSize,
  vDynamicScrollerItem,
} = useDynamicScroller(options)
</script>

<template>
  <div ref="scrollerEl" class="scroller">
    <div :style="{ minHeight: `${totalSize}px`, position: 'relative' }">
      <article
        v-for="view in pool"
        :key="view.id"
        v-dynamic-scroller-item="{ view }"
      >
        <h4>{{ view.item.title }}</h4>
        <p>{{ view.item.body }}</p>
      </article>
    </div>
  </div>
</template>

The old item path was view.item.item. In v3, use view.item, while advanced size metadata stays at view.itemWithSize.

useWindowScroller ​

useWindowScroller follows the same migration pattern as useRecycleScroller:

  • prefer getViewStyle(view) instead of manually rebuilding pooled styles
  • do not look for handleScroll in returned values
  • keyField can be a resolver function
  • itemSize can be a resolver function

Before:

vue
<div
  v-for="view in pool"
  :key="view.nr.id"
  :style="{
    transform: `translateY(${view.position}px)`,
    visibility: view.nr.used ? 'visible' : 'hidden',
    pointerEvents: view.nr.used ? undefined : 'none',
  }"
/>

After:

vue
<div
  v-for="view in pool"
  :key="view.nr.id"
  :style="getViewStyle(view)"
/>

useTableColumnWidths ​

useTableColumnWidths is a new helper for semantic tables. Use it when you render a real <table> and pooled rows make native column widths drift. It is not needed for ordinary list migrations.

Migration checklist ​

If you use components only:

  • verify DynamicScrollerItem receives :index="index" when you use simple arrays or a function keyField
  • move window-owned scrolling to WindowScroller when you want the newer documented path
  • adopt disableTransform or flowMode only if you need those layout controls

If you use composables:

  • remove old handleScroll usage
  • replace manual pooled positioning with getViewStyle(view)
  • update useDynamicScroller templates from view.item.item to view.item
  • add spacer rendering if you adopt flowMode
  • use useTableColumnWidths only for semantic table layouts

See useRecycleScroller, useDynamicScroller, and useWindowScroller for the full v3 APIs.

Released under the MIT License.