MMotif· VectoJS gallery
● WebGPUcore 1.9.2GitHub ↗

Virtual List

A 5,000-row list that mounts only the visible window plus overscan — smooth momentum scroll inside a canvas card.

import { Scene } from "@vectojs/core";
import { Card, Text, VirtualList } from "@vectojs/ui";

const canvas = document.getElementById("canvas");
const app = document.getElementById("app");

const scene = new Scene(canvas, { maxFPS: 60, disableWindowResize: true });
// Static list UI: paint on demand and let VirtualList's own scroll integrator
// (which overrides hasPendingAnimations) wake the loop while momentum runs.
scene.renderMode = "onDemand";

const CARD_W = 460;
const CARD_H = 320;
const PAD = 16;
const LIST_W = CARD_W - PAD * 2;
const ROW_H = 34;

const items = Array.from(
  { length: 5000 },
  (_, i) => `Virtualized command ${i + 1}`,
);

function rowText(value, index) {
  const row = new Card({
    width: LIST_W,
    height: ROW_H - 2,
    radius: 8,
    bg: index % 2 ? "rgba(20,23,31,0.7)" : "rgba(27,31,41,0.7)",
    border: "rgba(255,255,255,0.06)",
  });
  row.add(
    new Text(`${String(index + 1).padStart(4, "0")}  ${value}`, {
      font: '13px ui-monospace, "JetBrains Mono", monospace',
      color: "#cbd5e1",
    }).setPosition(12, 9),
  );
  return row;
}

const card = new Card({
  width: CARD_W,
  height: CARD_H,
  bg: "#14171f",
  border: "#26324a",
  borderWidth: 1,
  radius: 16,
  label: "Command palette",
});
card.add(
  new Text("5,000 rows — only the visible window is mounted", {
    font: "600 13px Inter, system-ui",
    color: "#9aa3b0",
  }).setPosition(PAD, PAD),
);

const list = new VirtualList({
  items,
  renderItem: rowText,
  estimatedRowHeight: ROW_H,
  overscan: 3,
  width: LIST_W,
  height: CARD_H - PAD * 2 - 28,
});
card.add(list.setPosition(PAD, PAD + 28));

scene.add(card);

function fit() {
  const w = app.clientWidth || 1;
  const h = app.clientHeight || 1;
  scene.resize(w, h);
  card.setPosition(Math.round((w - CARD_W) / 2), Math.round((h - CARD_H) / 2));
  scene.markDirty();
}

new ResizeObserver(fit).observe(app);
scene.start();
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="/no-ff-webgpu.js"></script>
    <title>Virtual List — Motif</title>
    <style>
      * {
        margin: 0;
        box-sizing: border-box;
      }
      html,
      body {
        width: 100%;
        height: 100%;
        overflow: hidden;
        background: #06070a;
      }
      #app {
        position: relative;
        width: 100%;
        height: 100%;
      }
      canvas {
        display: block;
        width: 100%;
        height: 100%;
      }
    </style>
    <script type="importmap">
      {
        "imports": {
          "@vectojs/core": "https://esm.sh/@vectojs/core@1.9.2",
          "@vectojs/ui": "https://esm.sh/@vectojs/ui@1.9.5?bundle"
        }
      }
    </script>
  </head>
  <body>
    <div id="app"><canvas id="canvas"></canvas></div>
    <script type="module" src="./demo.js"></script>
  </body>
</html>
● liveesm.sh@vectojs/core · @vectojs/uiMIT — copy freely