Three.js Digital Grid Pulse — Independently Fading WebGL Cube Field

Three.js Digital Grid Pulse · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Per-block material instances: independent opacity animation requires each cube to own its own material
Independent per-block timers: random delay, duration, and staggered starting elapsed time avoid synchronization
Triangular fade envelope: a snappy, linear rise-and-fall shape reinforces a digital, data-like feel
Coupled opacity/scale/position: all three properties derive from one shared envelope value for a grounded emerge-from-floor look
Modulo-based cycling: JavaScript's modulo operator wraps ever-increasing elapsed time into a repeating pulse window
THREE.Clock delta timing: frame-rate-independent animation via accumulated real elapsed time, not a fixed increment
Directional plus ambient lighting: emissive materials glow visibly against a dark, minimal background
Loaded entirely from a CDN: no npm install, bundler, or build step required

About this UI Snippet

How to Build an Independently Pulsing Three.js Digital Grid

Screenshot of the Three.js Digital Grid Pulse snippet rendered live

The Three.js Digital Grid Pulse snippet fills a floor-plane grid with 400 small emissive cubes, each one rising up, glowing, and fading back down entirely on its own random schedule — a "digital data" or matrix-like aesthetic — using per-block materials and a triangular fade envelope, with core Three.js loaded from a CDN.

Why every block needs its own material instance

It would be tempting, for performance, to share one MeshStandardMaterial across all 400 cube meshes — but this snippet deliberately creates a brand-new material for every single block. The reason is direct: a material's opacity is a property of the *material*, not the mesh. If every block shared one material object, animating any single block's opacity would instantly change the opacity of all 400 blocks simultaneously, since they'd all be reading from the exact same object. Independent per-block fading strictly requires independent per-block materials — a rare case where more object instances, not fewer, is the correct choice.

Each block owns its own timer, not a shared clock

Every block stores its own delay (a random initial wait), duration (how long its rise-glow-fade cycle takes), and elapsed (its personal running clock, deliberately started at a random *negative* value so blocks don't all begin their very first cycle at the same instant). Because THREE.Clock().getDelta() is added into every block's own elapsed value independently every frame, no two blocks are ever perfectly synchronized — the grid reads as a field of independently-blinking data points rather than a single wave sweeping through in lockstep.

A triangular envelope, not a sine wave

Rather than a smooth sine-based fade, this snippet computes a simple triangular shape: opacity ramps linearly up to full brightness at the cycle's halfway point, then ramps linearly back down to zero. This is deliberately snappier and more "digital" feeling than a smooth sine curve — a small, characterful choice that reinforces the matrix/data aesthetic more than an eased fade would, and it requires nothing more than a conditional and two multiplications, no easing library.

Scale and position move together with opacity

As a block's envelope value rises, its Y-axis scale grows alongside its opacity, and its Y position is recalculated so it always appears to grow *upward from the floor plane* rather than expanding from its own center in both directions. This coupling — three properties (opacity, scale, position) all derived from one shared envelope number — is what makes each block read as physically emerging from the ground rather than simply materializing in place.

Modulo arithmetic creates the repeating cycle

Each block's progress is computed as (elapsed % (duration + delay)) / duration, using JavaScript's modulo operator to wrap the ever-increasing elapsed time back into a repeating window. Whenever that wrapped value exceeds 1, the block is in its "resting, invisible" phase between pulses — explicitly forced to zero opacity and minimum scale — before its next cycle begins.

Where this technique applies

Independently-timed per-object pulse cycles are the standard technique behind matrix-style data visualizations, equalizer bar grids, and any "field of independently blinking things" effect. Pair it with a network graph for a "raw data versus connected structure" pairing, or contrast its snappy, digital fade against the smooth, continuous motion of the particle wave.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to guess why each block needs its own material by trial and error. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why sharing one material across all 400 cubes would break independent fading, or how the modulo-based progress calculation produces a repeating pulse cycle from an ever-increasing elapsed time value. The same assistant can help optimize it, for instance checking whether per-block materials could be pooled and reused once a block finishes its cycle instead of persisting 400 separate material objects for the page's entire lifetime, or whether the grid could use InstancedMesh with a custom per-instance attribute for opacity instead of individual meshes. It is also useful for extending the effect: ask it to make blocks near the mouse cursor pulse more frequently than distant ones, color-code blocks by their current envelope value instead of one fixed color for all of them, or trigger a wave of pulses that visibly ripples outward from a clicked point. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an "independently pulsing digital grid" in plain HTML, CSS, and JavaScript using Three.js loaded from a CDN (no bundler, no build step) — a floor grid of small cubes that each rise, glow, and fade on their own random schedule.

Requirements:
- A full-viewport canvas with a WebGLRenderer sized to match it, updated on window resize including camera aspect ratio, with a camera angled to look down at a floor-plane grid, lit by ambient plus directional light.
- Create a grid of at least 300 small cube meshes arranged flat on a floor plane, sharing one geometry, but giving every single cube its own separate material instance (not a shared material) using a physically-based material with emissive glow and transparency enabled, starting at zero opacity.
- For each cube, store its own random delay value, its own random duration value, and its own elapsed-time tracker initialized to a random negative starting value so cubes do not all begin their first animation cycle simultaneously.
- Use a Clock (or equivalent accumulated real-time delta) to advance every cube's own elapsed-time tracker independently every animation frame — do not use a single shared global timer for all cubes.
- For each cube, compute a repeating cycle progress value using the modulo operator on its elapsed time against the sum of its duration and delay, divided by its duration; while that value is negative (still in the initial delay) or greater than 1 (in the resting phase), the cube must render at zero opacity and minimum scale.
- While a cube's progress value is between 0 and 1, compute a triangular envelope: ramping linearly from 0 up to 1 as progress goes from 0 to 0.5, then linearly back down to 0 as progress goes from 0.5 to 1 — do not use a sine wave or any easing library for this envelope.
- Use that same envelope value to simultaneously drive the cube's material opacity, its vertical scale, and its vertical position (so it appears to grow upward from the floor plane as it fades in, rather than expanding from its own center or fading in place).

Step by step

How to Use

  1. 1
    Load the Three.js CDNAdd three.min.js from the CDN panel — no add-ons are required for this snippet.
  2. 2
    Paste HTML, CSS, and JSA 20x20 grid of cubes appears, each rising and fading independently on its own schedule.
  3. 3
    Watch the field pulseNo two blocks are synchronized, since each tracks its own random delay and duration.
  4. 4
    Adjust grid sizeChange GRID and SPACING to trade block density and coverage area for performance.
  5. 5
    Tune the pulse timingAdjust the random duration and delay ranges for a busier or calmer, more sparsely-blinking field.
  6. 6
    Resize the windowRenderer size and camera aspect ratio update automatically.

Real-world uses

Common Use Cases

Data and analytics dashboard backgrounds
A field of independently pulsing blocks suggests live data processing, ideal behind a dashboard or metrics page.
Tech and developer tool landing pages
A matrix-like grid signals a technical, data-driven product without needing literal falling-character rain.
Teaching independent per-object animation
A focused, minimal example of why shared versus per-instance materials matter for independent animation.
Digital art and generative portfolios
An algorithmically-timed, ever-changing field of blocks suits generative and procedural art showcases.
Sci-fi and cyberpunk game menus
A pulsing digital grid fits cyberpunk, hacking, or terminal-themed game title screens and menus.
Loading and processing screens
A calm, continuously shifting grid gives a loading screen the sense that background work is actively happening.

Got questions?

Frequently Asked Questions

Opacity is a property of a material object, not of an individual mesh. If all 400 cubes shared one material, changing that material's opacity to animate any single block would simultaneously change the opacity of every block referencing that same material object. Independent per-block fading requires each block to hold its own separate material instance.

Each block tracks its own delay, duration, and elapsed time values, all randomized independently at creation. Because each block's elapsed time is also initialized to a different random negative starting value (staggering when its very first cycle begins) and advances using its own accumulated frame delta, no two blocks' cycles ever line up exactly, even though they all use the same underlying animation logic.

A triangular envelope (linear rise to the midpoint, then linear fall) is snappier and more mechanical-feeling than a smooth sine curve, which suits a digital or data-like aesthetic better. It is also simpler to compute — just a conditional and two multiplications — with no easing function or library required.

A block's Y-axis scale and Y position are both recalculated from the same envelope value that drives its opacity. Coupling these three properties together means each block visibly grows upward from the floor plane as it fades in, rather than simply becoming transparent in place, which reads as more physically grounded and deliberate.

Yes. Narrow the random duration and delay ranges for shorter, more frequent pulses across the whole grid (a busier feel), or widen them for longer, sparser pulses with more blocks resting invisibly at any given moment (a calmer feel).

Yes. Click JSX for a React component, Vue for a Vue 3 SFC, Angular for a standalone component, or Tailwind for a React + Tailwind CSS utility-class version. Build the blocks array inside a mount effect so materials are only created once, and call renderer.dispose() plus dispose each block's own material on cleanup to free GPU memory.