Scrolling
Drive scroll imperatively through the list instance.
The instance from useMugenVirtualizer is your handle on the list. Because every
row's offset is known analytically — on- or off-screen — scrolling to a specific
item lands pixel-exact on the first try, with no measure-and-correct dance.
import { , , , } from '@wingleeio/mugen';
interface Row {
: string;
: string;
}
export function ({ }: { : Row[] }) {
const = ({ : });
return (
<>
< ={() => .('42', { : 'smooth', : 'center' })}>
Reveal #42
</>
<
={}
={() => .}
="15px Inter"
={22}
={() => (
< ={12}>
<>{.}</>
</>
)}
/>
</>
);
}Methods
| Method | |
|---|---|
scrollToItem(key, options?) | Scroll a row into view by its getKey value. |
scrollToIndex(index, options?) | Scroll a row into view by index. |
length | Number of items. |
totalHeight() | The exact scrollable height (px) — every row counted. |
Options
interface ScrollToOptions {
?: 'auto' | 'smooth';
?: 'auto' | 'start' | 'center' | 'end';
}behavior—'smooth'animates;'auto'(default) jumps.align— where the row lands:start(top),center,end(bottom), orauto(default) which scrolls the least — a no-op if the row is already fully visible, otherwise to the nearest edge.
Call these from anywhere you hold the instance — an effect, an event handler, a deep-link router. The instance is stable across renders.
Reach callbacks
Use edge callbacks for pagination. They fire once when the scroll position enters the configured pixel threshold, then arm again after the user leaves that edge:
<MugenVList
instance={list}
getKey={(row) => row.id}
render={Row}
renderTop={() => <TopLoader loading={loadingPrevious} />}
renderBottom={() => <BottomLoader loading={loadingNext} />}
onTopReached={loadPreviousPage}
onBottomReached={loadNextPage}
topReachedThreshold={24}
bottomReachedThreshold={24}
/>renderTop and renderBottom are scrollable slots, not sticky overlays. Their
heights are measured with the same primitive walker as rows, so scrollToItem,
totalHeight(), and distanceFromBottom stay in the same coordinate system.
Start at the bottom
Chat-style lists open at the newest message, not the oldest. Pass
initialScroll="bottom" to <MugenVList> and it opens scrolled to the end:
<MugenVList instance={list} getKey={(m) => m.id} render={Message} initialScroll="bottom" />It's applied once, after the first measure and before paint — no
top-to-bottom flash, no blank gap. Because every row's height is already known,
the landing is exact even though those rows have never mounted. The default is
"top". To animate the landing instead of jumping, pass an object:
<MugenVList … initialScroll={{ to: 'bottom', behavior: 'smooth' }} />You can also open on a measured row by index:
<MugenVList … initialScroll={{ to: 'index', index: 40, align: 'center' }} />This is applied before paint, like top and bottom. If later data changes
insert rows above the visible keyed content, mugen preserves the current visual
anchor by shifting scrollTop by the inserted height. That keeps top pagination
from dumping the user onto the newly prepended page.
Stick to the bottom while streaming
For a chat that streams tokens, set stickToBottom and the list stays pinned to
the bottom as content grows — following each new token with a velocity-based
spring. The instant the reader scrolls up it lets go; when they return to the
bottom it re-engages:
<MugenVList … initialScroll="bottom" stickToBottom />Tune the spring — or snap instantly — by passing options:
<MugenVList
…
stickToBottom={{ behavior: 'smooth', damping: 0.7, stiffness: 0.05, mass: 1.25, threshold: 70 }}
/>threshold is how close to the bottom (px) still counts as "stuck". See both in
the AI chat recipe: it streams the
last turn and follows it down, and you can scroll up to break free or hit Replay.