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:
keyFieldcan now be either a string field name or a resolver function(item, index) => string | number.itemSizecan now be either a fixed number,null, or a resolver function(item, index) => number.disableTransformlets pooled views usetop/leftpositioning instead of transforms.flowModekeeps active pooled views in normal document flow with spacer elements.startSpacerSizeandendSpacerSizeare exposed for advanced integrations whenflowModeis active.flowModeonly supports vertical single-axis layouts. It does not support horizontal virtualization or grids, andhiddenPositiondoes not apply there.
DynamicScroller
DynamicScroller now supports the same layout controls as the core scroller:
disableTransformflowModehiddenPosition
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
WindowScrollerfor new window-scrolling code. - Keep
pageModeonRecycleScrolleronly for older compatibility behavior.
Headless composables
If you use the headless APIs, this is where the important migration work lives.
useRecycleScroller
Main changes:
handleScrollis no longer part of the return value.getViewStyle(view)is now the preferred way to position pooled views.startSpacerSizeandendSpacerSizewere added forflowMode.keyFieldcan now be a resolver function.itemSizecan now be a resolver function.
Before:
<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:
<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:
handleScrollis no longer returned.- Pooled
viewobjects were flattened for normal rendering. - The original item moved from
view.item.itemtoview.item. - Advanced size metadata still exists at
view.itemWithSize. getViewStyle(view)was added.startSpacerSizeandendSpacerSizewere added.v-dynamic-scroller-itemexamples should useview.idwhere relevant and directview.item.*access.
Before:
<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:
<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
handleScrollin returned values keyFieldcan be a resolver functionitemSizecan be a resolver function
Before:
<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:
<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
DynamicScrollerItemreceives:index="index"when you use simple arrays or a functionkeyField - move window-owned scrolling to
WindowScrollerwhen you want the newer documented path - adopt
disableTransformorflowModeonly if you need those layout controls
If you use composables:
- remove old
handleScrollusage - replace manual pooled positioning with
getViewStyle(view) - update
useDynamicScrollertemplates fromview.item.itemtoview.item - add spacer rendering if you adopt
flowMode - use
useTableColumnWidthsonly for semantic table layouts
See useRecycleScroller, useDynamicScroller, and useWindowScroller for the full v3 APIs.