SuperAPI AI Directory

infinite-webgl-grid

Frontend & websites

View repository

Updated 10-16 01:14

About this skill

Skill: infinite-webgl-grid

Pure Three.js component — a fixed N×N grid of image tiles, drag (pointer) or scroll (wheel) to browse as if the grid extends infinitely in every direction, using coordinate-wrapping (modulo offset) technique, not continuously spawning/destroying meshes.

WHAT

Purpose and context

  • Purpose: build an N×N WebGL image grid browsable infinitely in every direction (drag/scroll) via coordinate-wrapping, prioritizing copying the original as-is in Rheinmir/uiux-asset.
  • Trigger (when to use):
    • Portfolio/gallery/poster-wall needs a feel of "infinite browsing" in every direction (not ordinary one-directional scrolling).
    • User says "infinite image grid", "infinite scroll grid webgl", "draggable image wall", "infinitely scrolling poster wall".
  • Non-goals:
    • DO NOT use when particles need to converge into ONE specific image by scroll progress (that is threejs-particle-morph — it has a "reached target" state, whereas this has no target, only continuous browsing).
    • DO NOT use for a transition effect between 2 images on the same frame (that is blur).

Mental model

offset ảo (kéo/cuộn/inertia) → mỗi mesh cố định: wrap(localX - offset.x) → vị trí hiển thị → chỉ số tile ảo đổi → đổi material.map từ pool texture cố định. Camera stays still, no add/remove mesh at runtime (details: Reference — Mechanism).

Input and output contract

Field Required? Meaning
In images/content + color no if missing → demo draws a placeholder canvas itself, runs offline
In project constraints: whether external libs/files may be pulled in yes allowed → original version; not allowed → assets/ vanilla
In gridSize/tileSize/gap/poolSize no initialization parameter InfiniteGrid
Out file .html (+ _assets/) or demo.html/.css/.js adapted in the project yes keep the attribution comment on the first line if it is the original version
Out Playwright verification evidence yes before/during/after images when dragging in 4 directions + clean console

Rules and capabilities

  • RULE-01 (MUST): Limit simultaneous tiles/textures: mobile GPUs limit the number of texture units active at once. Keep the actually displayed grid (not counting the wrap buffer) at around ≤ 5×5 tiles at once; the demo defaults to gridSize=7 (5 displayed + 1 buffer ring on each side so tiles jump smoothly, no pop-in). Need more different images than the number of displayed tiles → use texture atlas (merge multiple images into 1 texture, change UV offset instead of changing material.map each individual texture) instead of increasing poolSize infinitely — each individual texture still costs 1 texture unit even if not displayed at the same time, if the code keeps a reference.
  • RULE-02 (MUST): Passive: false is mandatory: listener wheel and pointermove must register { passive: false } then call preventDefault() in the right place (only when dragging or when inside the canvas) — without this flag, modern browsers default to passive so preventDefault() is ignored SILENTLY (no error, no warning) and the background page still scrolls/zooms in parallel with the grid, causing jitter/conflict on mobile. CSS touch-action: none on the container is a necessary accompanying condition, it cannot replace preventDefault.
  • RULE-03 (SHOULD): Ortho camera is used for the demo (flat grid, no perspective distortion) — switch to PerspectiveCamera if a depth effect is needed (distant tiles blurred/shrinking), in which case the wrap formula stays the same, only the way world-units map to pixels changes.
  • RULE-04 (MUST): Verify with playwright-verify — capture before/during/after a strong drag (test all 4 directions), read the console (WebGL context errors are usually silent in the canvas, they do not throw a clear JS error).
  • Capabilities: read/copy files from an external asset repo (or assets/ of the skill); write files into the project; serve over local HTTP; control a headless browser to capture + read the console.

Failure boundaries

  • The project is not allowed to pull external libs → partial is valid: use the trimmed version assets/ instead of the original.
  • Open via file:// → texture/asset error → blocked until it is run over HTTP.
  • Console has a WebGL error or the background page scrolls in parallel with the grid → failed verify, fix (passive:false + touch-action: none) then verify again.
  • The request is actually for particles converging/transitioning 2 images → clarify, switch threejs-particle-morph / blur.

HOW

Main workflow

Step Type Inputs Action Outputs/exit Failure/next
W01 judgment request Confirm the right skill (infinite browse grid, no target) and choose the original version or assets/ selected source not allowed to pull external libs → B01
W02 effect original pen Open link run to see the effect → copy the whole file .html (+ _assets/) into the project, keep the attribution comment file in the project —
W03 judgment copied file Only modify content/images/colors adapted file —
W04 effect file Run locally over HTTP, not file:// page runs asset error → recheck the path
W05 deterministic page Verify with playwright-verify: capture before/during/after a strong drag in 4 directions + read the console images + clean console error → fix, repeat W04

Step-by-step details (source of truth for W01–W05):

Original version — use FIRST (copy the file as-is, don't rewrite it)

The author's VERBATIM original code (public CodePen = MIT) is in an external repo Rheinmir/uiux-asset — live gallery: https://rheinmir.github.io/uiux-asset/scroll-effects/ . Process: open the link run to see the exact effect → copy the file as-is .html (+ the folder _assets/ if the file references it) into the project → keep the attribution comment on the first line → only change the content/images/colors. Run locally over HTTP, not file://. git clone --depth 1 https://github.com/Rheinmir/uiux-asset if you need the whole offline set.

Original Pen — author Link
Infinite Scrollable and Draggable WebGL Grid — ReGGae run · code
Infinite 3D Poster Scroll Wall — photodow run · code
Infinite Scrolling with Image Cards — jkantner run · code

assets/ in this skill is just a self-written vanilla condensed version — use it when the project is NOT allowed to pull external libs; by default use the original version above.

Branches

ID Kind Guard Behavior Skip / failure Rejoin
B01 conditional_required project is NOT allowed to pull external libs use assets/demo.html + demo.css + demo.js, ADAPT the parameters and replace placeholders with real images (Assets section) — W04
B02 user_optional need a depth effect change Ortho to PerspectiveCamera, keep the wrap formula, only change the world-unit → pixel mapping — W05
B03 conditional_required need more different images than the number of visible tiles use a texture atlas (change the UV offset) instead of increasing poolSize — W05

Validation and stopping

Done when Playwright captures enough before/during/after drags in all 4 directions, the grid doesn't pop in, the background page doesn't scroll along, and the console has no errors (WebGL errors are usually silent — you must read the console, not just look at the images).

Examples

  • Positive: "make an infinite scrolling poster wall for a portfolio" → copy infinite-3d-poster-scroll-wall.html from Rheinmir/uiux-asset, replace the image, run locally over HTTP, Playwright drags in 4 directions → tiles jump seamlessly, clean console.
  • Boundary/failure: use assets/demo.js but register wheel does not { passive: false } → preventDefault() get silently ignored, the background page scrolls along with the grid → verify fails, add the flag + touch-action: none then rerun.

Reference — Assets

assets/demo.html + assets/demo.css + assets/demo.js — read then ADAPT (change gridSize/tileSize/gap/poolSize when initializing InfiniteGrid, replace _drawPlaceholder() with a real image via THREE.TextureLoader loading a URL instead of a canvas data URL). The demo draws its own placeholder (canvas numbers + hash colors) so it runs immediately without images/network.

Reference — Mechanism

  • Fixed mesh, no extra generation: gridSize × gridSize mesh (PlaneGeometry shares 1 instance + separate material per mesh) built once at init, NEVER add/remove mesh at runtime — completely different from the naive approach of "spawning a new mesh when dragging into an area that doesn't have one".
  • Virtual offset, don't move the camera infinitely far away: drag/scroll only accumulates into offset.x/offset.y (a float, can theoretically increase infinitely, never hits a practical limit). The camera stays still.
  • Wrap with modulo: each mesh has a fixed local coordinate localX/localY (position within the original N×N grid). The actual displayed position each frame = wrap(localX - offset.x), with wrap(raw) = ((raw + half) % full + full) % full - half (full = gridSize*pitch, half = full/2) — folds infinite coordinates into the display range around the center, exactly the standard mod formula for this technique.
  • Texture reuse when tiles jump: each mesh only changes material.map when its "virtual tile index" changes (computed from wrapped_position + offset, rounded by pitch) — that is, exactly when the tile leaves the viewport on one side and reappears on the opposite side. Textures come from fixed pool (poolSize textures loaded once via THREE.TextureLoader), do NOT create a new texture on each jump.
  • Network-independent placeholder: the texture pool is generated from <canvas> drawing numbers + colors (golden-angle hue spread to distinguish visually), canvas.toDataURL() then still loaded via THREE.TextureLoader.load(dataUrl) — the demo runs offline but still follows the real image-loading API path (when swapping in a real image you only change the URL, not the code flow).
  • Inertia: pointer drag records velocity (movement delta of the last pointermove); on release, each frame offset += velocity; velocity *= damping (0.92) until below the threshold — lerps toward 0 gradually, not a real physics simulation.
  • Cleanup: destroy() removes pointerdown/pointermove/pointerup/pointercancel/wheel/resize listeners, disposes each material, disposes the shared geometry (once), disposes each texture in the pool (once — do NOT dispose per mesh because textures are shared by many meshes), disposes the renderer, removes the canvas from the DOM.

Origin