v1.0.0 · Unity 2021.3+

VeloScroll

A high-performance recycling ScrollView for uGUI. Render thousands of rows with a bounded handful of live cells, and configure the whole thing in the Inspector — no code required. Pure C#, built by composition over Unity's own ScrollRect, with no native binaries.

Unity 2021.3 LTS – Unity 6 Pure C# · uGUI No-code authoring Mono + IL2CPP · WebGL
What VeloScroll does. It virtualizes (recycles) the cells of a scrolling list or grid. A bounded pool of cell GameObjects is reused as you scroll, so the number of live cells stays roughly constant no matter how many items your data has. Drive it entirely from the Inspector, or from script — code always wins.
Measured, not hand-waved. VeloScroll renders 10,000+ items with a bounded handful of live cells (verified: 12 live cells for 10k and 50k items in tests; 9 live cells render 10k items in the shipped player) and performs no per-item instantiation during scroll. The recycler's steady-state bookkeeping allocates zero bytes. The shipped player reported RESULT=PASS on both Mono and IL2CPP. See How It Works.

This guide starts with installation and a five-minute Quick Start, explains how the recycler works, then walks each feature, the no-code editor tooling, and the full scripting API.

Installation

Import the package, make sure uGUI and the Input System are present, and add a component.

Import from the Asset Store
In Window → Package Manager → My Assets, download and import VeloScroll. Everything lands under Assets/VeloScroll/ — that is the only folder that ships.
uGUI is required
The runtime depends only on com.unity.ugui (Unity's standard UI), which is present in virtually every project. No other package is required for the core component.
Input System (for clicks/drags in built scenes)
The scaffolding menus and sample scenes wire an EventSystem for the new Input System. Make sure com.unity.inputsystem is installed so pointer drags are routed. The core recycler itself uses uGUI drag handlers and works under either input backend.
Add a component
Add a VeloScroll View via Add Component → UI → VeloScroll → VeloScroll View, or scaffold a ready-to-play one from GameObject → UI → VeloScroll → Recycling List.

What gets installed

Assets/VeloScroll/
  Runtime/        // VeloScrollView, Recycler, CellPool, layouts, animator, data sources
  Editor/         // Custom inspector, scaffolding menus, Welcome window, project validator
  Demo/           // The interactive WebGL-ready demo scene (Build Index 0)
  Samples/        // Per-feature example scenes + the no-code template scene
  Documentation/  // You are here
  Tests/          // EditMode + PlayMode tests (run headless via Unity -runTests)Project
Deletable extras. The assembly layering keeps the core independent: Demo/ and Samples/ are never referenced by the runtime, so you can delete them and the asset still compiles. Only Assets/VeloScroll/ ships — nothing is written outside it, and there are no network calls.

Quick Start

Your first recycling list in about five minutes — no code, then one optional line of script.

Option A — Add the component

Create a UI object
In a scene with a Canvas, add an empty UI GameObject (or any RectTransform under the Canvas).
Add VeloScroll View
Add Component → UI → VeloScroll → VeloScroll View. The component self-builds a viewport and content and drives a ScrollRect for you — nothing else to wire.
Press Play
With all-default fields it renders a built-in deterministic sample list immediately. Scroll it: the item count is in the hundreds by default, but only a handful of cells are ever alive.
Watch the live-cell count
Select the view while playing — the inspector's Preview line shows Items, Live cells and Created. Live cells stay bounded as you scroll.

Option B — Scaffold from the menu

Prefer a one-click setup? Use GameObject → UI → VeloScroll → Recycling List. It creates a Canvas and EventSystem only if the scene lacks them, drops in a pre-wired VeloScroll View, and registers full Undo. Press Play and it renders the sample. (There is a Recycling Grid variant too.)

Make it 10,000 items from code

One call turns the same view into a 10,000-row list bound to your own data. The recycler binds only the cells near the viewport, so the binder runs O(visible), never O(count):

using UnityEngine;
using VeloScroll;

public class QuickStart : MonoBehaviour
{
    [SerializeField] private VeloScrollView view;

    void Start()
    {
        // 10,000 items, a handful of live cells, no per-item instantiation while scrolling.
        view.SetData<VeloScrollTextCell>(10000, (i, c) => c.SetContent("Item " + i, ""));
    }
}
💡
Code always wins. Any call to SetData / SetDataSource marks the view as code-configured, so its automatic build-on-play will defer to you and never overwrite your data with the sample.

How It Works

Recycling (a.k.a. virtualization) in one idea: reuse a small, bounded pool of cells as the list scrolls.

A naive scroll list instantiates one cell GameObject per item, so a 10,000-row list creates 10,000 cells — slow to build and heavy on memory. VeloScroll instead keeps only the cells that are near the viewport alive. As a cell scrolls out one edge, it is returned to a pool and immediately reused to show the item appearing at the other edge. The count of live cells therefore stays roughly constant regardless of how many items the data has.

Scroll changes Compute visible range Release out-of-range cells Get + bind incoming cells (Optional) animate
  1. The driven ScrollRect's onValueChanged reports the new scroll offset.
  2. The active layout (List or Grid) maps that offset to the range of item indices currently visible. For variable sizes a cumulative-offset cache is searched by binary search.
  3. The recycler returns any now-off-screen cells to the cell pool and pulls cells back out for the items entering view.
  4. The active data source binds each incoming cell (Bind(index, cell)) — only for the small visible set.
  5. If an animation preset is set, the animator nudges each visible cell's scale/opacity by its distance from the viewport center.
The numbers behind the claim. The live-cell count is bounded by roughly visible + 2 × buffer cells — a small constant independent of your item count. Tests confirm 12 live cells render 100, 10k and 50k items; in the shipped player 9 cells render 10,000 items. A 1,000-tick scroll stays at the same pool size (no growth), and the steady-state recycler path measures zero managed allocation.

Built by composition, not subclassing

VeloScroll drives a standard uGUI ScrollRect by composition; it never subclasses it. That means it coexists with any existing ScrollRect setup, and added to a bare GameObject it self-builds the viewport + content it needs. There is a blank-first-frame guard, too: because UI rects are 0×0 until a layout pass runs, the first build defers one frame until the viewport has a real size.

📋 Feature: Recycling List

A single-track recycled list, vertical or horizontal, with uniform cell sizes.

The default layout is a list: one column when scrolling vertically, one row when scrolling horizontally. Set the scroll Axis, the uniform Cell Main Size (height for vertical, width for horizontal), Spacing, inner Padding and an off-screen Buffer (extra cells kept alive just outside the viewport for smoother fast scrolling) in the Inspector.

// Switch back to a single-track list from code (e.g. after using a grid):
view.SetListLayout();

// Drive it from a typed binder. The default cell is VeloScrollTextCell.
view.SetData<VeloScrollTextCell>(10000, (i, c) => c.SetContent("Row " + i, "subtitle"));
The default cell needs no prefab. If you leave the Cell Prefab slot empty, VeloScroll builds a dependency-light VeloScrollTextCell (a background image plus uGUI title/subtitle text), so the zero-setup path needs no prefab and no TextMeshPro. Provide your own prefab holding a VeloScrollCell subclass when you want custom visuals.

Feature: Grid

A fixed-count grid recycler — columns when scrolling vertically, rows when scrolling horizontally.

Switch the Layout Kind to Grid and set the Grid Cross Count (number of columns for a vertical scroll, or rows for a horizontal scroll) and the Grid Cross Spacing. The grid recycles exactly like the list — only the visible lines are alive.

// 3-column grid (vertical scroll), 6px between columns:
view.SetGridLayout(3, 6f);
view.SetData<VeloScrollTextCell>(5000, (i, c) => c.SetContent("Cell " + i, ""));
💡
One click from the menu. GameObject → UI → VeloScroll → Recycling Grid scaffolds a view already set to a 3-column grid.

Feature: Variable Cell Sizes

Per-cell heights (vertical) or widths (horizontal), with the live set still bounded.

Uniform cells are the fast default, but real lists often need rows of different sizes. Provide a size function and the list layout measures each item along the scroll axis, caches cumulative offsets, and finds the visible range by binary search — so even a non-uniform 10k-row list keeps a bounded live-cell set. Pass null to revert to uniform sizing.

// Alternating tall/short rows; the recycler stays bounded.
view.SetVariableSizes(i => (i % 2 == 0) ? 120f : 72f);
view.SetData<VeloScrollTextCell>(10000, (i, c) => c.SetContent("Item " + i, "variable height"));
Scope. Variable sizing applies to the single-track List layout. Grid cells use the uniform cell size along the scroll axis.

Feature: Animated Cells

Scroll-driven cell emphasis — the no-code "juice" layer, on a real virtualizer.

Pick an Animation Kind and the animator shapes each visible cell by its distance from the viewport center. The math is a pure, testable function of that distance, and writes only when a value changes (no per-frame allocation):

KindEffect
NoneAnimation disabled (default).
FocusScaleEdge cells shrink toward the configured strength; the centered cell is full size.
FadeEdge cells fade out via a CanvasGroup; centered cell is fully opaque.
DepthA combined gentle scale + fade for a depth-of-focus look.
ParallaxIn V1, a gentle depth-style focus (true cross-axis parallax is on the roadmap).
// Strength 0..1 controls how strongly edge cells shrink/fade; curve is optional.
view.SetAnimation(VeloScrollAnimationKind.FocusScale, 0.35f);

// Shape the falloff with an AnimationCurve (empty = linear):
view.SetAnimation(VeloScrollAnimationKind.Depth, 0.5f, myCurve);
💡
No-code path. The same effect is fully Inspector-driven: set Animation Kind and Animation Strength (and an optional curve) on the component — the strength and curve fields only appear once a kind other than None is selected. Cells reset their scale/alpha automatically when recycled, so a reused cell never carries a stale transform.

🎯 Feature: Snapping / Paging

Snap to the nearest cell when a drag ends, and jump to any index instantly or with an animated scroll.

Enable Snapping and choose where the snapped cell lands in the viewport with Snap Align (Start, Center or End). When the user releases a drag, VeloScroll animates the content so the nearest cell settles at that alignment over the configured Snap Duration, using a smoothstep ease (or your own curve). Each settle raises the OnSnap event with the resolved item index.

// Turn on drag-end snapping, center-aligned, quarter-second ease.
view.SetSnapping(true, VeloScrollSnapAlign.Center, 0.25f);

// Get notified when a snap settles (drag-end or animated jump):
view.OnSnap += index => Debug.Log("Snapped to " + index);

// Jump to an item. animated:true performs an eased scroll and then fires OnSnap.
view.ScrollToIndex(500, VeloScrollSnapAlign.Center, animated: true);
Paging. Center- or start-aligned snapping with a full-viewport cell size gives page-style scrolling. The snap target is computed from the layout at the current scroll position, with a half-pixel boundary epsilon so exact cell boundaries never round to the wrong index.

🔄 Feature: Dynamic Data

Change the count or the contents at runtime — the list re-binds without an instantiation storm.

VeloScroll never stores your model; it asks the data source for a Count and binds cells on demand. When your data changes, call the matching operation and the recycler resizes the content and refreshes the window without rebuilding the world:

CallUse when
Reload()The count or contents changed and you want a full re-evaluation of the visible window.
InsertItems(index, count)You inserted rows in your source (the new count comes from your source).
RemoveItems(index, count)You removed rows in your source.
RefreshVisible()Item contents changed but the count did not — re-binds the visible cells in place.
// A live count delegate keeps the list in sync with a growing/shrinking collection.
view.SetData<VeloScrollTextCell>(() => items.Count, (i, c) => c.SetContent(items[i].Title, ""));

items.Add(newItem);
view.Reload();          // content resized, window refreshed, correct re-bind, no NaN
Hardened. Dynamic-count changes (e.g. 1000 → 500 → 3000) re-bind correctly with no NaN, and a 60× repeated reload stays bounded with no cell growth — both covered by tests.

🧱 Feature: Masonry / Staggered Grid

A Pinterest-style grid: fixed columns, but every cell has its own height, packed into the shortest column.

Where the regular Grid keeps a uniform cell size, the staggered grid takes a per-item main size and places each item into whichever column is currently shortest — the masonry look — while staying fully recycled and bounded.

// 3 columns, 6px cross spacing, each item's height from your model.
view.SetStaggeredGrid(3, 6f, i => 80f + items[i].ExtraHeight);
view.SetData<VeloScrollTextCell>(items.Count, (i, c) => c.SetContent(items[i].Title, ""));

📑 Feature: Sticky Section Headers

Grouped lists with a header per section that floats, pinned to the top, until the next header pushes it up.

Describe your list as sections — each one header followed by its rows — and VeloScroll flattens them into the recycler's index space. Headers and rows recycle through separate pools. With sticky on, the current section's header is mirrored into a floating overlay pinned to the viewport top, and the next section's incoming header shoves it out for a seamless hand-off.

// 40 sections, 12 rows each; 50px headers, 44px rows; sticky on.
view.SetSections(
    sectionCount: 40,
    rowsInSection: s => 12,
    headerSize: 50f, rowSize: 44f,
    bindHeader: (s, cell) => ((VeloScrollTextCell)cell).SetContent("Section " + s, ""),
    bindRow:    (s, r, cell) => ((VeloScrollTextCell)cell).SetContent("Row " + r, ""),
    sticky: true);

🔁 Feature: Looping & Auto-Scroll

Endless, seamless loops for pickers, carousels and tickers — optionally self-scrolling.

Looping repeats your data as one long, seamless run (the virtual extent is capped so scroll precision stays sub-pixel), wrapping each index back onto your real data. Pair it with auto-scroll for a hands-off marquee that wraps with no visible seam.

// A spinning value picker that wraps forever.
view.SetData<VeloScrollTextCell>(24, (i, c) => c.SetContent(hours[i], ""));
view.SetLooping(true);

// A news ticker: loop + continuous auto-scroll at 120 px/s.
view.SetLooping(true);
view.SetAutoScroll(120f);

Feature: Pull-to-Refresh & Load-More

The familiar mobile gesture: over-drag past an end to refresh or load the next page.

Enable it, set a threshold, and wire the events (in code or the Inspector). Over-pull past the start fires OnPullToRefresh; past the end fires OnLoadMore — perfect for infinite feeds.

view.SetPullToRefresh(true, thresholdPx: 90f);
view.OnPullToRefresh.AddListener(() => ReloadFromServer());
view.OnLoadMore.AddListener(() => AppendNextPage());

🧩 Feature: Multi-Prefab Cells

Mix different cell types in one list — ads between posts, dividers, rich vs plain rows — all bounded.

Register several cell prefabs plus a per-index type selector. Each type recycles through its own pool, so a heterogeneous list stays as bounded and allocation-free as a uniform one. The untyped binder casts each cell to the type it was handed.

// Two cell types alternating, 20,000 items, still a bounded handful of live cells.
view.SetMultiPrefabData(20000, new[] { postPrefab, adPrefab },
    typeOf: i => (i % 8 == 7) ? 1 : 0,   // every 8th row is an ad
    bind: (i, cell) => Populate(i, cell));

🎨 Feature: Themes & Animation Presets

Reusable ScriptableObject assets for look-and-feel — assign in the Inspector, no code required.

A Theme asset recolors the built-in text cells (background, title, subtitle) and drives spacing; it is applied on every bind through a zero-allocation hook. An Animation Preset asset bundles a scroll animation (kind + strength + curve) and overrides the inline settings. Create both from Assets › Create › VeloScroll, drop them on the component, or assign from code.

view.SetTheme(darkTheme);                 // recolors cells + spacing
view.SetAnimationPreset(focusPreset);      // overrides inline animation
No-code friendly. Both assets appear as object fields on the component — the Inspector's asset picker lists every Theme / Animation Preset in your project. The Create › VeloScroll menu and the Window › VeloScroll › Create… window scaffold a configured view in one click.

📊 Feature: Table / Columns (frozen header)

A virtualized data grid: aligned columns, a header row that stays pinned at the top, thousands of rows recycled.

Describe your columns (a title + a pixel width; a width of 0 flexes to share the leftover space), give a row count and a string[] per row, and VeloScroll renders a table whose rows recycle like any list — bounded no matter how many rows. The header row is mirrored into a frozen overlay pinned to the top.

var columns = new[] {
    new VeloScrollColumn("ID", 80),
    new VeloScrollColumn("Name", 0),     // flex
    new VeloScrollColumn("Score", 100),
};
view.SetTable(columns, 10000, row => new[] { ""+row, data[row].Name, ""+data[row].Score });

Feature: Drag-to-Reorder

Drag a cell to a new position; on drop you move the data and the list reflects it.

Turn it on with a callback. Press-drag a cell and it lifts and follows the pointer; on release VeloScroll computes the drop index and raises onReorder(from, to) so you mutate your own list, then it reloads into the new order. Your data stays the single source of truth.

view.SetReorderable(true, (from, to) => {
    var item = list[from];
    list.RemoveAt(from);
    list.Insert(to, item);   // the view reloads to show the new order
});

🗂 Feature: Nested Scrolling

Horizontal lists inside a vertical list (a "shelf of shelves") that route drags correctly.

A plain uGUI ScrollRect swallows the whole drag, so a vertical swipe started on an inner horizontal list never reaches the outer one. Add VeloScrollNestedRouter to the inner scroll's GameObject: it forwards drags that belong to the parent — those orthogonal to the inner axis, or continuing past the inner's edge — to the nearest parent scroll, while the inner keeps handling its own axis.

// On each inner (e.g. horizontal) VeloScrollView's GameObject:
innerGo.AddComponent<VeloScrollNestedRouter>();   // auto-finds the parent scroll

📥 Feature: Import CSV / JSON

A universal file reader: turn a real CSV or JSON payload into a list or a table, and let the user choose which columns to show.

Pass raw text to VeloScrollImport and it returns a VeloScrollTabularData — the discovered columns plus the rows. It is deliberately forgiving: it strips byte-order marks, sniffs the CSV delimiter (comma / semicolon / tab / pipe), honours RFC-4180 quoting (quoted commas, embedded newlines, doubled "" escapes) and pads ragged rows; for JSON it locates the record array (top-level, a wrapper property, or a single object), unions every record's keys into the schema and flattens nested objects into dotted columns (address.city). It is pure C# and culture-invariant, so it runs in players too — not just the editor.

Every column starts included; toggle any of them and the projection (IncludedHeader() / IncludedRow(i)) collapses to just the chosen columns — the data is never mutated, so you can re-include later. Then drive the view with the ApplyAsTable / ApplyAsList extensions.

var data = VeloScrollImport.FromCsv(csvText);   // or .FromJson(json) / .Auto(text) / .FromFile(path)

data.SetIncluded("internal_id", false);     // the user excludes a column they don't want

view.ApplyAsTable(data);                   // virtualized table of the included columns…
view.ApplyAsList(data);                    // …or a list: first column as title, the rest as subtitle

Let the user pick a real file at runtime. VeloScrollFilePicker opens the platform file dialog and hands back the chosen file's text. In a WebGL build it opens the browser's native file picker (via a bundled .jslibSystem.IO cannot read a user-selected file in WebGL); in the editor it shows an open-file panel, so the same button works while you iterate. Combine it with Auto (which sniffs CSV vs JSON) for a complete "import my data" button:

VeloScrollFilePicker.Open(".csv,.json,.txt", (name, text) => {
    var data = VeloScrollImport.Auto(text);   // CSV vs JSON auto-detected from the contents
    view.ApplyAsTable(data);
});
See it live in the Import module of the interactive demo: hit Open a file… to import your own CSV / JSON (live in the WebGL build), or explore the bundled CSV / JSON samples — switch Table / List and tick columns on and off, and the view updates instantly.

🔧 Editor Tools / No-Code

VeloScroll is built to be used without writing a line of code. The component, its custom inspector, the scaffolding menus, the data-source asset and the Welcome window cover the whole authoring loop.

The headline: add the component, press Play — done. A freshly added VeloScroll View with all-default fields renders a correct, non-empty, scrollable sample list with the default cell — zero setup, zero code.

1. The component & custom inspector

The VeloScroll View component exposes every option as a serialized field with sensible defaults, so it "just works" unconfigured. Its custom inspector groups the fields and shows only the knobs that apply:

Safe editor authoring. The inspector draws purely from SerializedObject (multi-edit safe), and the Rebuild button defers its work to the next editor tick rather than mutating the scene mid-IMGUI — the correct, crash-free way to materialize cells from a button.

2. GameObject scaffolding menus

Two one-click entry points create a ready-to-play view, adding a Canvas and an EventSystem only if the scene lacks them (so running an item twice never litters the hierarchy), wiring the EventSystem for the new Input System, and registering full Undo with the new object selected and pinged:

3. The VeloScrollDataSource asset

For a fully no-code list, create a data asset via Assets → Create → VeloScroll → Data Source (or the CreateAssetMenu entry). Each row is a small struct — Title, Subtitle, Tint — authored in the Inspector or generated with a deterministic Generate button. Set the component's Data Source Mode to Asset, drop the asset in the slot, and press Play: it renders the asset's rows with no scripting. A ready-made sample data asset ships under Assets/VeloScroll/Samples/ for you to duplicate.

4. Precedence: "just works" vs "code wins"

On Play, the gated auto-build resolves data in this order:

Configured by code? yes: defer to your data | Asset assigned? build from the asset | else: built-in sample

So a code-set data source is never overwritten by the auto-build, and a truly unconfigured component is never blank.

5. The Welcome window

On first load, a one-screen Welcome window orients you: a 3-step getting-started, a Create your first Recycling List button (which reuses the exact scaffolding the menu uses), an Open Documentation button, and a "don't show again" toggle. It is available any time under Window → VeloScroll → Welcome.

Batch-safe. The Welcome window's auto-open and the component's auto-build both short-circuit in -batchmode, so headless/CI runs never hang on an editor window or an unexpected build.

Sample scenes

Generate per-feature example scenes from Tools → VeloScroll → Generate Sample Scenes. Each one isolates a single feature: the recycling list, grid, masonry, variable sizes, sticky sections, animation, snapping, looping + marquee, pull-to-refresh, multi-prefab, table, drag-to-reorder, nested scrolling, dynamic data, CSV / JSON import, and a fully no-code template (a VeloScrollView in Asset mode bound to a VeloScrollDataSource). Open any one, press Play, and see exactly that feature. The interactive demo scene showcases all of them together with live controls.

📙 Scripting API

The public surface of VeloScroll.VeloScrollView and its supporting types. Namespace: VeloScroll.

Getting a reference and setting data

using UnityEngine;
using VeloScroll;

var view = GetComponent<VeloScrollView>();

// Count + typed binder (the default cell type is VeloScrollTextCell):
view.SetData<VeloScrollTextCell>(10000, (i, c) => c.SetContent("Item " + i, ""));

// Live-count delegate for dynamic collections:
view.SetData<VeloScrollTextCell>(() => model.Count, (i, c) => c.SetContent(model[i].Name, ""));

// Full control: implement IVeloScrollDataSource and assign it.
view.SetDataSource(myDataSource);

Methods

SignatureWhat it does
void SetData<TCell>(int count, Action<int, TCell> bind)Drive the list from a fixed count + a typed per-cell binder. TCell : VeloScrollCell.
void SetData<TCell>(Func<int> count, Action<int, TCell> bind)Same, with a live count delegate for dynamic lists.
void SetDataSource(IVeloScrollDataSource source)Assign a custom data source and rebuild. Marks the view code-configured.
void SetListLayout()Switch to a single-track list and rebuild.
void SetGridLayout(int crossCount, float crossSpacing = 6f)Switch to a fixed column/row grid and rebuild.
void SetStaggeredGrid(int crossCount, float crossSpacing = 6f, Func<int, float> mainSizeOf = null)Masonry layout: fixed columns, variable per-item main sizes packed into the shortest column.
void SetVariableSizes(Func<int, float> sizeOf)Per-cell sizes along the scroll axis (binary-search range). null reverts to uniform.
void SetCellSize(float mainSize)Set the uniform cell extent along the scroll axis (height for vertical, width for horizontal).
void SetContentPadding(int left, int top, int right, int bottom)Padding (px) around the cells inside the content. Reloads if already built.
void SetSections(int sectionCount, Func<int,int> rowsInSection, float headerSize, float rowSize, Action<int,VeloScrollCell> bindHeader, Action<int,int,VeloScrollCell> bindRow, GameObject headerPrefab = null, GameObject rowPrefab = null, bool sticky = true)Sectioned list (header + rows per section), flattened into the recycler. With sticky, the current section's header floats pinned to the viewport top.
void SetMultiPrefabData(int count, GameObject[] prefabs, Func<int,int> typeOf, Action<int,VeloScrollCell> bind)Heterogeneous cells: N prefabs + a per-index type selector + an untyped binder. Each type recycles through its own pool.
void SetCellPrefabs(GameObject[] prefabs, Func<int,int> typeOf)Register the cell prefabs + type selector for a multi-prefab view (drive data with SetMultiPrefabData).
void SetAnimation(VeloScrollAnimationKind kind, float strength = 0.35f, AnimationCurve curve = null)Set the scroll-driven cell animation preset (None disables).
void SetAnimationPreset(VeloScrollAnimationPreset preset)Assign an Animation Preset asset (overrides the inline animation). null reverts.
void SetTheme(VeloScrollTheme theme)Assign a Theme asset: recolors built-in text cells on bind and drives spacing. null clears.
void SetSnapping(bool enabled, VeloScrollSnapAlign align = Start, float duration = 0.25f)Enable/disable drag-end snapping and its alignment + duration.
void SetPaging(bool enabled)Snap by full viewport pages on drag end (document-style paging). OnSnap reports the page index.
void SetLooping(bool enabled, int copies = 0)Repeat the data as a long seamless loop (uniform List/Grid) for pickers, carousels and endless feeds. copies = 0 auto-sizes.
void SetAutoScroll(float pixelsPerSecond)Continuously advance the scroll (0 = off). With looping on it wraps seamlessly — an endless marquee/ticker.
void SetPullToRefresh(bool enabled, float thresholdPx = 80f)Fire OnPullToRefresh / OnLoadMore when the list is over-dragged past its ends.
void SetTable(VeloScrollColumn[] columns, int rowCount, Func<int, string[]> rowValues, float rowHeight = 36f, bool frozenHeader = true)Virtualized table: column titles + widths (a width ≤ 0 flexes), string[] per row, optional frozen header pinned at the top.
void SetReorderable(bool enabled, Action<int,int> onReorder = null)Drag a cell to reorder; on drop onReorder(from, to) fires so you mutate your data, then the view reloads.
void Reload()Reapply after the data count or contents changed (resizes content, refreshes window).
void InsertItems(int index, int countToInsert)Reflect an insertion in your source.
void RemoveItems(int index, int countToRemove)Reflect a removal in your source.
void RefreshVisible()Re-bind the visible cells in place (contents changed, count did not).
void ScrollToIndex(int index, VeloScrollSnapAlign align = Start, bool animated = false)Bring an item into view at the given alignment, instant or eased.
void Build() / void RebuildFromInspector()(Re)build the hierarchy, layout, pool and initial visible set. The editor preview uses the same path.

Importing data (CSV / JSON)

SignatureWhat it does
VeloScrollTabularData VeloScrollImport.FromCsv(string text, char delimiter = '\0', bool hasHeader = true)Parse delimited text. BOM-stripped; delimiter auto-sniffed (comma / semicolon / tab / pipe) when '\0'; RFC-4180 quoting; ragged rows padded; blank lines dropped.
VeloScrollTabularData VeloScrollImport.FromJson(string text)Parse arbitrary JSON. Finds the record array (top-level, a wrapper property, or a single object), unions keys into the schema, flattens nested objects to dotted.names and joins primitive arrays.
VeloScrollTabularData VeloScrollImport.Auto(string text) · FromFile(string path)Sniff JSON-vs-CSV from the text, or read a file from disk and dispatch on the extension.
void data.SetIncluded(int col, bool) · SetIncluded(string col, bool) · SetAllIncluded(bool)Choose which discovered columns to show (all start included). Toggling is a projection — the data is never mutated.
IReadOnlyList<string> data.Columns · int RowCount · string Cell(int row, int col) · string[] IncludedHeader() · string[] IncludedRow(int)Read the full grid, or the projection of just the included columns.
void view.ApplyAsTable(VeloScrollTabularData data, float rowHeight = 34, bool frozenHeader = true)Render the included columns as a virtualized table (extension method).
void view.ApplyAsList(VeloScrollTabularData data, float cellHeight = 60)Render each row as a built-in text cell: first included column as the title, the rest as a "Name: value" subtitle (extension method).
void VeloScrollFilePicker.Open(string accept, Action<string name, string text> onLoaded) · bool IsSupportedOpen the platform file dialog — the browser file picker in a WebGL build, an open-file panel in the editor — and return the chosen file's name + text. Pair with Auto to import it.

Properties & events

MemberPurpose
int CountTotal logical item count, from the active data source.
int LiveCellCountLive (instantiated + active) cells right now. Bounded; independent of Count.
int TotalCellsCreatedTotal cells ever instantiated. Constant during scroll once the pool is warm — the proof of reuse.
bool IsLoopingTrue while looping is active.
ScrollRect ScrollRectThe driven ScrollRect (created on first build if absent).
event Action<int> OnSnapRaised when a snap (drag-end, page, or animated ScrollToIndex) settles on an item/page index.
UnityEvent OnPullToRefreshInvoked when the user over-pulls past the start and releases (wire in code or the Inspector).
UnityEvent OnLoadMoreInvoked when the user over-pulls past the end and releases.

Supporting types

TypePurpose
IVeloScrollDataSourceThe virtual data contract: int Count { get; } + void Bind(int index, VeloScrollCell cell).
VeloScrollCellBase cell. Override OnBind(index) / OnRecycle() for custom cells.
VeloScrollTextCellBuilt-in default cell (background image + title/subtitle uGUI text). SetContent(title, subtitle), SetBackground(color).
VeloScrollDataSourceScriptableObject no-code data asset (rows of Title/Subtitle/Tint; deterministic GenerateSample(count)).
VeloScrollAnimationPresetScriptableObject animation preset (Kind + Strength + Curve). Assign via SetAnimationPreset or the Inspector. Create: Assets › Create › VeloScroll › Animation Preset.
VeloScrollThemeScriptableObject theme (cell/background/title/subtitle colors, spacing, font sizes). Assign via SetTheme or the Inspector. Create: Assets › Create › VeloScroll › Theme.
VeloScrollColumnA table column: Title + pixel Width (≤ 0 = flex). Used by SetTable.
VeloScrollNestedRouterAdd to an inner scroll's GameObject for nested scrolling — forwards orthogonal / at-edge drags + wheel to the parent ScrollRect.
VeloScrollAxisVertical / Horizontal.
VeloScrollLayoutKindList / Grid / Staggered.
VeloScrollSnapAlignStart / Center / End.
VeloScrollAnimationKindNone / FocusScale / Parallax / Fade / Depth.

Custom cells

public class MyCell : VeloScrollCell
{
    public override void OnBind(int index)
    {
        base.OnBind(index);     // records Index; recycler treats this as "show item N now"
        // ... populate your visuals from your model[index] ...
    }

    public override void OnRecycle()
    {
        base.OnRecycle();         // resets scale/alpha so a reused cell carries no stale state
        // ... release any per-item state ...
    }
}

🖥 Compatibility

Where VeloScroll runs, and what it was verified against.

AreaSupport
Unity versionCompiles on Unity 2021.3 LTS through Unity 6. Verified on 2021.3.30f1 and 6000.0.62f1; no post-2021.3 API is used in the shipped code.
UI systemuGUI (com.unity.ugui) — the only runtime dependency.
Render pipelineBuilt-in, URP and HDRP. The runtime is uGUI canvas-space, so it is pipeline-agnostic.
InputRouted through uGUI's EventSystem, so it works with the new or the legacy input handler. Scaffolded scenes wire the new Input System's UI module.
Scripting backendMono and IL2CPP. The shipped player's self-test reported RESULT=PASS on both.
PlatformsDesktop, mobile and WebGL (the demo builds to WebGL). The recycler is pure managed code with no native plugin.
No native code, no network. VeloScroll is pure C# plus standard Unity UI components. It bundles no native binaries, makes no network calls, and collects nothing.

💡 Troubleshooting

The issues that come up most, and how to fix them.

The list is blank on the first frame, then appears

Expected and handled. uGUI rects are 0×0 until a layout pass runs, so the first build defers one frame until the viewport has a real size. If it stays blank, confirm the view (or an ancestor) actually has a non-zero RectTransform size — a VeloScroll View under a stretched Canvas, or with an explicit size, will build correctly.

Dragging or clicking does nothing in a built/scaffolded scene

The scene needs an EventSystem with a working UI input module. The scaffolding menus add one wired for the new Input System; if you built the scene yourself, make sure com.unity.inputsystem is installed and the EventSystem uses InputSystemUIInputModule (the menu calls AssignDefaultActions() for you). Without a valid module, pointer drags are silently ignored.

Play shows the sample data instead of mine

Either call SetData/SetDataSource from code (which marks the view code-configured, so the auto-build defers to you), or set Data Source Mode to Asset and assign a VeloScrollDataSource. With no data configured, the component deliberately renders a built-in sample so the "add component + Play" path is never blank.

My data changed but the list didn't update

Tell the view what changed: RefreshVisible() when only contents changed, or Reload() (also InsertItems/RemoveItems) when the count changed. If you used a fixed-count SetData overload, switch to the Func<int> overload so the count tracks your collection.

A reused cell shows a stale scale or opacity

If you wrote a custom VeloScrollCell and overrode OnRecycle(), call base.OnRecycle() — it resets the cell's scale and CanvasGroup alpha so a recycled cell never carries an animation value from its previous item.

Snapping lands one cell off at an exact boundary

This was hardened: the snap target uses a half-pixel boundary epsilon so a float round-trip through the normalized scroll position cannot floor to the wrong index. Make sure your Snap Align matches where you expect the cell to land (Start/Center/End).

Compile error mentioning FindFirstObjectByType on Unity 2021.3

The shipped code already guards newer convenience APIs behind version checks and only uses APIs available on the 2021.3 floor. If you see this, confirm you imported the shipped Assets/VeloScroll/ unmodified — the package compiles clean on a fresh 2021.3.30f1 project.

FAQ

Quick answers to the questions that come up most.

Do I have to write code to use VeloScroll?

No. Add the VeloScroll View component (or scaffold one from GameObject → UI → VeloScroll), optionally assign a VeloScrollDataSource asset, and press Play. The whole thing is configurable in the Inspector. Scripting is available when you want full control — and code always takes precedence over the Inspector data.

How many cells does it actually keep alive?

A small constant, roughly visible + 2 × buffer — independent of your item count. In tests, 12 live cells render 100, 10k and 50k items; in the shipped player 9 cells render 10,000 items. The steady-state recycler path allocates zero bytes and instantiates no cells while scrolling once the pool is warm.

Does it use native plugins or make network calls?

No. VeloScroll is pure C# plus standard uGUI components. There are no native binaries, no network access, and no data collection. A static audit fails the build if a network symbol appears anywhere in the shipped package.

Will it conflict with my existing ScrollRect?

No. VeloScroll drives a ScrollRect by composition and never subclasses it, so it coexists with existing setups. Added to a bare GameObject, it self-builds the viewport and content it needs.

Which render pipelines and platforms are supported?

Built-in, URP and HDRP — the runtime is uGUI canvas-space and therefore pipeline-agnostic. It runs on Mono and IL2CPP across desktop, mobile and WebGL.

What's the minimum Unity version?

Unity 2021.3 LTS. The package compiles on 2021.3 through Unity 6 and was verified on 2021.3.30f1 and 6000.0.62f1, with no post-2021.3 API in the shipped code.

Can cells be different sizes?

Yes, in the List layout. Provide a size function via SetVariableSizes; the layout measures each item and finds the visible range by binary search, keeping the live set bounded. Grid cells use the uniform cell size along the scroll axis.

Does it support TextMeshPro?

The core recycler is text-agnostic and the built-in default cell uses uGUI Text so the core depends only on com.unity.ugui. To use TextMeshPro, put a TMP component on your own cell prefab (a VeloScrollCell subclass) and bind it in your data source — the demo and sample cells do exactly that.

📬 Support

Need help? We're here for you.

📧

Get in Touch

Bug reports, feature requests, setup questions — reach out and we'll get back to you.

✉ szekipapa77@gmail.com

We typically respond within 24–48 hours.

💬 Before contacting
  • Check the Troubleshooting section
  • Confirm the scene has an EventSystem for input
  • Confirm a VeloScroll View is on the object
  • Check the Console for messages
📋 Helpful info to include
  • Unity version (e.g. 6000.0.62f1)
  • VeloScroll version (v1.0.0)
  • Render pipeline (Built-in / URP / HDRP)
  • Scripting backend (Mono / IL2CPP) and target platform
Enjoying VeloScroll? A review on the Unity Asset Store helps other developers discover the asset and helps us keep improving it. Thank you!

VeloScroll v1.0.0 · Built for Unity 2021.3 LTS through Unity 6 · Pure C#, no native binaries

Support: szekipapa77@gmail.com