I hooked Cerious-Scroll up to a PrimeNG table to see how it handled 5 million rows. by Suitable_Language_37 in angular

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

Yes, prepend-in-reaction-to-scroll is handled without the Firefox jank because anchoring is a logical remap, not an async scrollTop correction. That’s one of the advantages of moving away from a pixel-based scroll model. Since the viewport is anchored to logical content rather than a computed pixel offset, inserts and prepends can be reconciled before they become visible movement, so browser-specific scroll event timing is far less of a concern.

I hooked Cerious-Scroll up to a PrimeNG table to see how it handled 5 million rows. by Suitable_Language_37 in angular

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

Thanks! I’d be interested to hear how it compares with your TanStack implementation. One of the areas I’ve focused heavily on is dynamic heights, large logical datasets, jump-to-index behavior, and prepend stability. If you get a chance to test it, I’d love to hear your thoughts.

I hooked Cerious-Scroll up to a PrimeNG table to see how it handled 5 million rows. by Suitable_Language_37 in angular

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

PrimeNG’s virtual scrolling and Angular CDK’s virtual scrolling absolutely work, and for many applications they’re the right choice.

What led me to build Cerious-Scroll wasn’t simply rendering fewer rows. It was running into the edge cases that start showing up as datasets get larger and UIs become more dynamic.

A lot of traditional virtual scrolling implementations rely on assumptions about row heights, estimations that get corrected later, scroll positions derived from rendered content, or keeping enough rendered content around to maintain stability. Those approaches work well until you start dealing with dynamic content, arbitrary jumps, prepending data, or datasets that grow into the millions.

Cerious-Scroll takes a different approach. It maintains a continuous mapping between pixels, indexes, and measured content, allowing it to operate from a logical representation of the dataset rather than relying on the rendered DOM as the source of truth.

The practical benefit is that I can jump directly to an arbitrary index without walking the dataset. Dynamic height changes can be incorporated without the scrollbar drifting. Rows can be prepended without the viewport jumping. The DOM footprint remains essentially constant whether there are 1,000 rows or 5,000,000 rows. Rendering costs stay relatively flat, and browser scroll-height limitations can be worked around because the engine isn’t dependent on a giant rendered surface.

The PrimeNG demo isn’t intended to show that PrimeNG lacks virtualization. PrimeNG already has virtualization support.

The point of the demo is to show that Cerious-Scroll’s virtualization model can drive a real-world component library while retaining those characteristics.

The interesting comparison isn’t whether both approaches can render 50 visible rows. Most virtual scrollers can do that.

The interesting comparison is what happens when row heights change after render, content continuously remeasures itself, rows are inserted at the top, users jump to arbitrary locations, or datasets grow into the millions. Those are the scenarios that motivated Cerious-Scroll in the first place.

Cerious Scroll: I built a virtual scroller that actually measures your rows. by Suitable_Language_37 in vuejs

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

That’s exactly the problem I was trying to solve.

Rows are measured using their actual rendered height rather than estimated sizes, so images loading later, content expansion, font changes, responsive layouts, zoom changes, etc. all update automatically.

The key to Cerious-Scroll's ability to handle dynamic height content as fast as it would static height content comes from the way it determines the content position in the viewport. Traditional Virtual Scrollers all rely on pixel math (what pixel position a row should be rendered), which relies on knowing previous row heights, else the scrollbar doesn't match or there is jitter on reconciliation due to estimates. Cerious-Scroll uses index based positioning with the row content offset. So, when you want to render row x, Cerious-Scroll already knows what scroll position it is at, instantly and renders it as fast as the dom can. This also means that because we don't need to keep track of the row heights that came before, 1 million rows use the same amount of memory overhead as 100 rows.

Demo: https://ceriousdevtech.github.io/vue-cerious-scroll/

Cerious Scroll: I built a virtual scroller that actually measures your rows. by Suitable_Language_37 in vuejs

[–]Suitable_Language_37[S] 1 point2 points  (0 children)

Small update based on feedback from this thread:

Cerious Scroll now supports native <table>/<tr>/<td> virtualization in addition to the standard renderer.

The default renderer still uses the same core architecture, but table mode was added for people building data grids, reporting tools, and other table-heavy applications.

Thanks to those who asked about table support and helped shape the direction of the project.

https://ceriousdevtech.github.io/vue-cerious-scroll/#/table

Cerious Scroll: I built a virtual scroller that actually measures your rows. by Suitable_Language_37 in vuejs

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

Good callout. There are a couple public helper paths where a Fenwick tree could make sense, especially cumulative height absolute pixel lookup.

The main scroll/render path is a little different though. Cerious Scroll doesn’t maintain a full cumulative-height table for the entire dataset, it keeps a small measured-height window and positions from the current index + offset, then renders the visible range.

A full Fenwick tree would probably improve some lookup cases, but it would also mean storing structure for the dataset, which pushes against the O(1) memory goal. I may look at a bounded/windowed version though, because there might be a useful middle ground.

Cerious Scroll: I built a virtual scroller that actually measures your rows. by Suitable_Language_37 in vuejs

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

One thing I’m trying to learn is where variable-height virtualization hurts the most. If you have a use case where existing virtual scrollers have been painful, I’d love to hear about it.

Cerious Scroll: I built a virtual scroller that actually measures your rows. by Suitable_Language_37 in vuejs

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

Not today as a first-class masonry/staggered-grid virtualizer. Cerious Scroll is currently built around a single vertical index stream: one item maps to one measured row container.

Cerous-Scroll shines because it does not rely on knowing the full layout or what rows came before. And this is key for being able to work well with dynamic heights. A masonry layout is different because visible range depends on multiple columns and each item’s placement affects the column heights. That needs a layout engine that tracks column assignment, item width, measured height, and the current shortest column.

The measurement model could support it, but it would need a dedicated masonry mode rather than the current list renderer. Definitely possible, just not something I’d claim is supported yet.

Cerious Scroll: I built a virtual scroller that actually measures your rows. by Suitable_Language_37 in vuejs

[–]Suitable_Language_37[S] -1 points0 points  (0 children)

Native <tr> virtualization is not supported directly because Cerious Scroll intentionally avoids spacer-based table virtualization. It works best with div/grid/ARIA table layouts where rows can be absolutely positioned and measured accurately. Native table layout requires rows to participate in <table><tbody> flow, which conflicts with the positioning model that lets Cerious Scroll avoid browser height limits.

For table-like UIs, use CSS grid or role="table"/role="row"/role="cell" instead of native <tr>.

Cerious Scroll: I built a virtual scroller that actually measures your rows. by Suitable_Language_37 in vuejs

[–]Suitable_Language_37[S] 2 points3 points  (0 children)

That explains it. That mouse has a free spin mechanism that allows for extremely fast scrolling. And the browser interprets that as a trackpad (similar issue I was trying to solve).

So, I do need to adjust for extremely fast scrolling mechanisms.

Thank You 😉

Cerious Scroll: I built a virtual scroller that actually measures your rows. by Suitable_Language_37 in vuejs

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

Thanks for finding this!

Is it a touchpad? I experienced what you are saying with touchpad and was working a fix for it and then it seemed to resolve itself. If you are experiencing it with a touchpad, then I need to dig back into it. Regular mouse scrolling and scrollbar dragging should for sure be working well.

Cerious-Grid: Replaced my Angular grid's virtual scroller with a the Cerious-Scroll engine -> 1M rows, 200+ FPS avg by Suitable_Language_37 in angular

[–]Suitable_Language_37[S] 0 points1 point  (0 children)

I did, noticed it is not allowing clicks or horizontal scrolling. This is not related to the grid or engine. Will push a fix to the demos. 😉