Skip to main content
assetsforwordpress

Dither Plasma

The Dither Plasma effect is an animated background that displays flowing, colorful abstract patterns, such as waves, tunnels, and swirls, rendered with a chunky, pixelated look and a limited color palette. It supports the user’s `prefers-reduced-motion` setting by displaying a static frame instead of animating when that preference is enabled.

Instructions

What Is Dithering, and Why Are We Voluntarily Doing It Again

Chunky Pixels on a 4K Display: An Exercise in Ironic Overspending

How to Distract Users From Your Thin Landing Page

Canvas, WebGL, or Pure Vibes: Choosing Your Renderer

Your Users Came for the Content. They Left Hypnotized.

"Does It Convert?" No. "Does It Dither?" Enormously.

So You've Decided Your Website Should Look Like a Screensaver from 1993

The Plasma Effect: Because Plain Gradients Were Too Reasonable

Instructions

Open the JS script in the Supa Code Block element and edit the options at the end of the script. Each option is explained below:

Visual options

palette (default: "c64") Selects one of the built-in 8-color gradients used to colorize the plasma field. Valid values are "c64", "metallic", "neon", "ember", "ice", "gameboy", or "custom". If you type an invalid name, the code falls back safely to c64 (PALETTES[opts.palette] || PALETTES.c64). The shader stores these as 8 RGB triplets and interpolates between them based on the field value.

pattern (default: "radial") Chooses the plasma animation itself. Each value maps to a different math function in the shader:

  • "radial" — flowing waves plus a ring that travels around an off-center point (the classic "Radial RGB Shader" look).
  • "sine" — the 1990s demoscene classic: overlapping sine waves with a moving circular ripple.
  • "tunnel" — concentric rings rushing outward, giving the illusion of flying through a tunnel.
  • "swirl" — rotating spiral arms that twist around the center.

An unknown pattern name silently falls back to "radial" (index 0).

customColors (default: null) Only used when palette is set to "custom". It must be an array of eight hex color strings, e.g. "#000000", "#352879", etc. Both shorthand (#abc) and full (#aabbcc) forms are supported. They're parsed into RGB and become the 8-stop palette. If you provide fewer than 8 colors, the option is ignored and the built-in c64 palette is used instead.

dither (default: true) When true, the smooth color transitions between palette stops are quantized using an 8×8 Bayer matrix — that's what gives the retro, pixelated "dithered" look instead of smooth gradients. When false, you get clean, smooth blends between the 8 colors.

pixelSize (default: 2) The size of the "chunky pixels" in device pixels. The shader divides the fragment coordinates by this value (uDownScale) before applying the Bayer dithering, so the dither pattern becomes blocky. Higher values = bigger, more visible retro pixels; 1 means the dither works at full resolution (finest grain).

rgbShift (default: 0) The amount of chromatic aberration / RGB channel separation. The shader evaluates the field three times — once for each channel — sampling slightly offset positions in a direction radiating from the center. The offset grows with distance from center (uRgbShift * dot(p, p) * 0.5), so the effect is strongest at the edges. 0 disables it; 0.065 (as in this example) gives a subtle glitchy fringe. Values that are too large will look smeary.

speed (default: 1) A multiplier on the animation clock. 1 is normal speed, 2 is twice as fast, 0.5 is half-speed, 0 freezes the animation in place (though the renderer still runs).

zoom (default: 1) How much of the pattern you see. The coordinates are multiplied by this value (p = (uv * 2.0 - 1.0) * uZoom), so values greater than 1 zoom out (you see more of the field, patterns appear smaller), and values less than 1 zoom in (patterns appear larger, more magnified). The canvas aspect ratio is also applied here so the pattern isn't stretched.

seed (default: 0) A number that randomizes the starting phase of the animation. The seed is converted into three pseudo-random offsets (using the golden ratio and other irrational constants) that shift the pattern's spatial position and its time phase. This lets two instances with the same settings but different seeds look different from frame one. Non-numeric seeds are treated as 0.

Behavior options

animate (default: true) If true, the animation starts automatically via requestAnimationFrame. If false, the canvas renders a single static frame at creation time and stays frozen until you call plasma.start().

respectReducedMotion (default: true) If true, the code checks the browser's prefers-reduced-motion: reduce media query. If the user has requested reduced motion in their OS/browser settings, the animation is not started automatically — you get a static frame instead. (You can still call plasma.start() manually if you want to override this.) Setting it to false ignores the user's preference and always animates.

pixelRatio (default: 2) The maximum device-pixel-ratio used for the canvas backing store. The code takes Math.min(window.devicePixelRatio, opts.pixelRatio) — so on a retina screen (DPR 2–3) with the default of 2, the canvas renders at 2× resolution instead of the full 3×, which saves GPU work. Lower it to 1 for a more pixelated look and better performance.

maxPixels (default: 2200000) A hard cap on the total number of pixels in the canvas backing store. If the computed canvas size (width × height after applying pixelRatio) exceeds this limit, the canvas is scaled down proportionally so it never exceeds it. This is a performance guard for large displays — it prevents the shader from being run on a huge framebuffer. Note that the canvas is stretched to fill its container via CSS, so the visual result stays the same size; only the rendering resolution drops.


Note on the canvas element itself: the first argument (container) must be a <canvas> element — the code doesn't handle a <div>; it calls canvas.getContext("webgl") directly.

And the returned plasma object gives you the runtime API:

  • start() / stop() — resume or freeze the animation (with a running getter to check state).
  • setOptions({...}) — change any of the above options at runtime (e.g., swap palette, pattern, seed, speed).
  • render() — force a single frame draw.
  • destroy() — stop the animation, remove resize listeners, and release all WebGL resources (buffer, program, and the context itself via WEBGL_lose_context).