npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ceriousdevtech/vue-cerious-scroll

v1.0.6

Published

Vue 3 bindings for CeriousScroll — high-performance virtual scrolling with O(1) memory and no height estimation

Readme

@ceriousdevtech/vue-cerious-scroll

License Live Demo

Vue 3 bindings for Cerious Scroll™ — high-performance virtual scrolling with O(1) memory, consistent 60 FPS+, and native variable-height support with no height estimation.

Rows are rendered into the engine's own measured containers via Vue's synchronous render(), so every row's real height is measured (never estimated) — exactly the guarantee that makes CeriousScroll precise. Rows are rendered with your app's appContext, so globally registered components, directives, and installed plugins work normally inside each row.


Installation

npm install @ceriousdevtech/vue-cerious-scroll @ceriousdevtech/cerious-scroll

vue (>= 3.3) is a peer dependency.


Demo

Live demo → — 100,000 rows, fixed/variable-height toggle, imperative jump-to-row, and live viewport stats.

To run locally:

npm install
npm run demo        # dev server with HMR
npm run demo:build  # production build to demo/dist

The demo imports the wrapper by its package name, aliased to the library source, so edits to src/ are reflected live.


Quick start (component)

Give the container a height; provide items and an #item scoped slot.

<script setup lang="ts">
import { CeriousScroll } from '@ceriousdevtech/vue-cerious-scroll';

const items = Array.from({ length: 1_000_000 }, (_, i) => ({ id: i, name: `Item ${i}` }));
</script>

<template>
  <CeriousScroll :items="items" :style="{ height: '480px' }">
    <template #item="{ item, index }">
      <div class="row">{{ index }} — {{ item.name }}</div>
    </template>
  </CeriousScroll>
</template>

Variable heights need no configuration — just render rows of whatever height; the engine measures each one.

Without a full array (huge / sparse data)

<CeriousScroll
  :total-elements="100_000_000"
  :get-item="(index) => loadRow(index)"
  :style="{ height: '600px' }"
>
  <template #item="{ item, index }">
    <Row :data="item" :index="index" />
  </template>
</CeriousScroll>

Composable

useCeriousScroll gives you full control. Attach containerRef to your scroll element; the composable renders the rows imperatively into the engine's measured containers.

<script setup lang="ts">
import { h } from 'vue';
import { useCeriousScroll } from '@ceriousdevtech/vue-cerious-scroll';

const { containerRef } = useCeriousScroll({
  items,
  renderItem: (item, index) => h('div', { class: 'row' }, `${index} — ${item.name}`),
});
</script>

<template>
  <div ref="containerRef" style="height: 480px; position: relative; overflow: hidden" />
</template>

renderItem returns a Vue VNodeChild (use h(...), or render JSX/TSX).


Component props

| Prop | Type | Description | | --- | --- | --- | | items | readonly TItem[] | Optional data array. totalElements defaults to items.length. | | totalElements | number | Total item count. Required if items is omitted. | | getItem | (index) => TItem | Lazy item getter for large/sparse datasets. | | renderItem | (item, index) => VNodeChild | Render prop alternative to the #item scoped slot. | | options | CeriousScrollOptions | Engine options (keyboard/touch/wheel/scrollbar/etc.). Read once at creation. | | autoRender | boolean | Re-render on scroll/resize/data changes. Default true. |

The row is provided by the #item scoped slot ({ item, index }) or the render-item prop. In table mode, a #header slot renders the <thead> row (see Table layout). Apply class / style directly to the component — they fall through onto the scroll container (set a height!).

Events

| Event | Payload | Description | | --- | --- | --- | | viewport-change | CeriousViewportChangeDetail | Normalized viewport-change (wheel/touch/keyboard/scrollbar). | | measured-viewport | MeasuredViewportRange | Measured range after each render pass. | | ready | CeriousScrollEngine | The underlying engine instance, once ready. |

Imperative API (via template ref)

const scroll = ref<InstanceType<typeof CeriousScroll> | null>(null);
// scroll.value?.jumpToElement(500);
// scroll.value?.scrollToPercentage(50);
// scroll.value?.reset();
// scroll.value?.render();
// scroll.value?.recalculate(); // drop cached heights + re-measure (see Notes)
// scroll.value?.scroller;      // the raw engine

Table layout

Pass :options="{ layout: 'table' }" to render real <table> / <tr> / <td> rows with a frozen header and native column alignment. The #item slot returns the row's <td> cells; a #header slot provides the (declarative, reactive) <thead> row:

<CeriousScroll
  class="my-scroll"
  :total-elements="100000"
  :get-item="(i) => i"
  :options="{ layout: 'table', table: { tableClassName: 'my-table', autoSizeColumns: true } }"
>
  <template #header>
    <tr><th v-for="c in columns" :key="c.key">{{ c.label }}</th></tr>
  </template>

  <template #item="{ item: index }">
    <td>{{ row(index).id }}</td>
    <td>{{ row(index).name }}</td>
    <td>{{ row(index).email }}</td>
  </template>
</CeriousScroll>
  • The #header slot renders into the engine's <thead> (same <table> as the rows → native column alignment, frozen header) and stays reactive.
  • The #item slot must return <td>s. They render into the row's <tr> via a display: contents wrapper that isolates Vue's renderer from the engine's row recycling.
  • table.autoSizeColumns measures column widths once and pins them (auto-sized + stable); or use table.columnWidths. Variable row heights work as usual.
  • CSS: border-collapse: separate and an opaque <thead> background (see the core README's Table Layout notes).

Notes

  • No height estimation. Rows are committed with Vue's synchronous render() so the engine measures real offsetHeight. Later size changes are picked up by the engine's built-in ResizeObserver.
  • options are read at creation. Changing options after mount has no effect; remount (e.g. with a :key) to apply new engine options.
  • Changing the item count recreates the engine internally (scroll position is preserved). Mutating items without changing the count just re-renders the content in place (cheap; Vue patches each row, so focus/selection survive) — it does not discard cached heights, so editable grids that produce a new items array on every edit don't trigger a full viewport re-measure.
  • If every rendered row's height changes at once (e.g. a density/layout switch) the cached heights become stale and rows can misalign until the next scroll. Call recalculate() (on the template ref, or from the composable result) right after the change to drop the height cache and re-measure. Don't call it on routine edits — a single cell edit keeps its row's size, and the engine's built-in ResizeObserver picks up any incidental resize on its own.

License

Licensed by Cerious DevTech LLC under the MIT License (see LICENSE-MIT).

📧 [email protected]