webdevpuneet.com

Three.js Particle Wave — Animated WebGL Point Cloud Ocean Effect

Three.js Particle Wave · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations

Copy & paste

Source Code

Requires
<canvas id="pwCanvas"></canvas>
<div class="pw-ui">
  <span class="pw-dot"></span>
  <span>8,000 points · WebGL</span>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

GPU point cloud: 10,000 vertices rendered in a single THREE.Points draw call
Dual sine-wave displacement: two overlapping waves avoid a flat, repeating look
Height-driven vertex colors: color and elevation share the same underlying value
In-place BufferAttribute updates: no geometry recreated per frame, just needsUpdate flags
Slow ambient rotation: a single extra line adds perceived camera movement for free
Responsive canvas: renderer size and camera aspect ratio recalculated on window resize
Loaded entirely from a CDN: no npm install, no bundler, no build step
Runs at 60fps on modest hardware thanks to GPU-side vertex processing

About this UI Snippet

How to Build an Animated Three.js Particle Wave (WebGL Point Cloud)

Screenshot of the Three.js Particle Wave snippet rendered live

The Three.js Particle Wave snippet turns a flat grid of 10,000 points into a living, undulating ocean surface, rendered entirely in WebGL via a single <canvas> and the Three.js library loaded from a CDN. Every point's height and color update every frame from a small trigonometric formula — no physics engine, no imported terrain data, just math driving a GPU-rendered point cloud.

A BufferGeometry grid, not individual meshes

The scene starts as a 100×100 grid of coordinates packed into a single Float32Array, assigned to a THREE.BufferGeometry via setAttribute('position', ...). Rendering 10,000 individual mesh objects would be prohibitively expensive; rendering 10,000 vertices of one THREE.Points object is nearly free, since the GPU processes the whole buffer in one draw call. This is the same technique behind every performant particle system, snow effect, or starfield built in WebGL.

Height comes from two overlapping sine waves

Each frame, every point's Y coordinate is recomputed as Math.sin(x * freq + t) + Math.cos(z * freq + t * 0.75) — two independent sine waves at slightly different speeds and axes, summed together. Overlapping two waves like this avoids the flat, mechanical look of a single sine ripple; because the two waves drift in and out of phase with each other, the surface never repeats identically, reading as organic water motion instead of a looping GIF.

Vertex colors driven by the same height value

Rather than a flat particle color, each point's RGB is derived from its own normalized height — peaks shade toward a brighter cyan, troughs toward a deeper blue — using the geometry's color buffer attribute with vertexColors: true on the PointsMaterial. This one line of shared math (computing height, then feeding it into both the Y position and the color) is what makes the wave read as a genuine surface rather than a random point cloud.

Updating a BufferAttribute efficiently

The animation loop never recreates the geometry — it writes directly into the existing position and color BufferAttributes with .setY() and .setXYZ(), then flips needsUpdate = true once per attribute per frame. This is the standard, GPU-friendly pattern for per-vertex animation in Three.js: allocate the buffers once, mutate them in place forever after.

A slow ambient rotation ties it together

On top of the wave motion, the whole point cloud rotates very slowly around its Y axis (points.rotation.y += 0.0016), so the camera appears to drift around the ocean surface even though it never actually moves. Combined with a fixed camera angle looking slightly downward, this single extra line adds a huge amount of perceived depth for almost no cost.

Why WebGL instead of CSS or Canvas 2D

A particle field this dense — 10,000 individually colored, individually positioned points, redrawn 60 times a second — is well past what Canvas 2D or CSS transforms can sustain smoothly. Three.js hands the actual per-vertex math to the GPU via a single draw call, which is why a scene like this holds a steady frame rate even on modest hardware. It's the same reason data visualizations like a radar chart or a particle network reach for canvas or WebGL rather than hundreds of DOM nodes once the element count climbs into the thousands.

Step by step

How to Use

  1. 1
    Load the Three.js CDNAdd the three.min.js script from the CDN panel — this snippet needs only core Three.js, no add-ons.
  2. 2
    Add the canvas and JSPaste the HTML, CSS, and JS panels. The canvas fills the viewport automatically.
  3. 3
    Watch the wave rippleThe point grid animates continuously from two overlapping sine waves — no interaction required.
  4. 4
    Resize the windowThe renderer and camera aspect ratio update automatically on resize, so the wave always fills its container.
  5. 5
    Tune the grid densityEdit COLS, ROWS, and SPACING to trade particle count for performance and coverage area.
  6. 6
    Retint the surfaceChange the color formula in the animation loop to shift the palette from blue-cyan to any gradient.

Real-world uses

Common Use Cases

Landing page hero backgrounds
Drop this behind a hero headline for a calm, high-tech ambient background, paired with a gradient text title.
Music and event sites
An ocean-like particle field suits ambient, electronic, or wellness-brand aesthetics far better than a static image.
Teaching WebGL fundamentals
A focused, minimal example of BufferGeometry, PointsMaterial, and per-frame attribute updates — the building blocks behind every Three.js particle effect.
Portfolio and agency intros
A living background signals technical craft immediately, especially alongside a particle network or starfield on adjacent sections.
Game and app loading screens
A gently animated wave gives a loading screen visual interest without competing for attention with UI text.
Data-adjacent dashboards
Reuse the height-to-color mapping technique to visualize any 2D dataset as an animated 3D surface.

Got questions?

Frequently Asked Questions

A THREE.Points object renders its entire buffer of vertices in one GPU draw call. Ten thousand separate Mesh objects would each carry their own draw call and matrix, which collapses frame rate almost immediately. Point clouds are the standard technique for any effect with thousands of small, similar elements.

A single sine wave produces a perfectly periodic ripple that visibly loops and feels mechanical. Summing two waves at different frequencies and speeds means their combined pattern rarely repeats exactly, which reads as much more natural, ocean-like motion for very little extra math.

The position and color BufferAttributes are allocated once at startup and then mutated in place with setY() and setXYZ() every frame, followed by needsUpdate = true. Recreating the geometry from scratch each frame would be far slower — writing into existing typed arrays is the performant pattern Three.js expects.

Yes. Raise COLS and ROWS for a denser, more detailed wave at a higher GPU cost, or lower them for a sparser, cheaper effect. Adjust SPACING alongside them to keep the wave covering the same physical area.

Yes. The color formula is a simple function of each point's height, so swapping in a different gradient is a one-line change. To react to scroll or audio, feed a scroll-position or frequency-analysis value into the same formula that currently uses the time variable t.

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. In React, create the renderer, scene, and geometry inside useEffect once the canvas ref exists, store the animation frame ID, and call cancelAnimationFrame plus renderer.dispose() on cleanup so the WebGL context is released when the component unmounts.