Skip to the content
Agent Tools
Effects kit

Shaders: small WebGL effects you can copy

Nine background and motion effects for a web page, each running live below. Change the controls, then copy the code under the effect. Every effect is one small module with no dependency.

See the star field

Written by us

We wrote every effect from scratch from standard graphics maths, in the Last Table colours. No code comes from a shader gallery or library, and no outside image is used.

Star field

A slow star field for a hero: three depths of stars drift sideways and twinkle over a low teal haze.

Loops. Module starfield.js: 2.4 KB, 1.2 KB gzipped. Parameter names are shown next to each control.

0.006
0.22
1.2
0.6
<div id="starfield-target" style="min-height: 60vh"></div>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import starfield from "https://tools.openkrill.app/shaders/effects/starfield.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  mount(document.getElementById("starfield-target"), starfield);
</script>

Completion burst

One brass burst when a task completes: a short flash, a ring, rays and sparks that fall a little. It plays once, in 1.4 seconds.

Page content sits under this effect.

Plays once, 1.4 s. Module burst.js: 2.1 KB, 1.1 KB gzipped. Parameter names are shown next to each control.

0.5
0.5
0.42
14
<div id="burst-target" style="min-height: 60vh"></div>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import burst from "https://tools.openkrill.app/shaders/effects/burst.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  const fx = mount(document.getElementById("burst-target"), burst);
  fx.play(); // call fx.play() again each time it should fire
</script>

Nebula

A slow nebula for a page background: soft clouds of teal gas over midnight, lit in a few places by brass.

Loops. Module nebula.js: 1.8 KB, 0.9 KB gzipped. Parameter names are shown next to each control.

0.05
1.6
0.8
0.8
<div id="nebula-target" style="min-height: 60vh"></div>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import nebula from "https://tools.openkrill.app/shaders/effects/nebula.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  mount(document.getElementById("nebula-target"), nebula);
</script>

Glass shimmer

A glass shimmer over a card: a soft band of light crosses the card, then rests, and the top edge keeps a thin sheen.

Sample card

The shimmer passes over this card and its text.

Loops. Module shimmer.js: 1.6 KB, 0.8 KB gzipped. Parameter names are shown next to each control.

4
0.12
0.28
25
<article id="shimmer-target">Your card</article>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import shimmer from "https://tools.openkrill.app/shaders/effects/shimmer.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  mount(document.getElementById("shimmer-target"), shimmer);
</script>

Film grain

A film grain layer over any block: light and dark specks that change a few times a second, like film.

Page content sits under this effect.

Loops. Module grain.js: 1.4 KB, 0.8 KB gzipped. Parameter names are shown next to each control.

0.12
1.5
24
<div id="grain-target" style="min-height: 60vh"></div>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import grain from "https://tools.openkrill.app/shaders/effects/grain.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  mount(document.getElementById("grain-target"), grain);
</script>

Difference ripple

A ripple that points at one difference, as in Mirror Twin: a few teal rings spread from one point and fade. It plays once, in 1.8 seconds.

Page content sits under this effect.

Plays once, 1.8 s. Module ripple.js: 1.6 KB, 0.8 KB gzipped. Parameter names are shown next to each control.

0.5
0.5
0.55
3
0.9
<div id="ripple-target" style="min-height: 60vh"></div>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import ripple from "https://tools.openkrill.app/shaders/effects/ripple.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  const fx = mount(document.getElementById("ripple-target"), ripple);
  fx.play(); // call fx.play() again each time it should fire
</script>

Status pulse

A pulse for a status chip: a dot with a soft glow, and one ring that leaves the dot each period.

Running

Loops. Module pulse.js: 1.3 KB, 0.7 KB gzipped. Parameter names are shown next to each control.

1.6
0.16
<span id="pulse-target" style="display: inline-block; width: 24px; height: 24px"></span>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import pulse from "https://tools.openkrill.app/shaders/effects/pulse.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  mount(document.getElementById("pulse-target"), pulse);
</script>

Brass facets

Slow facets for a section background: midnight cells drift like cut glass, with thin brass seams between them.

Loops. Module facets.js: 1.5 KB, 0.8 KB gzipped. Parameter names are shown next to each control.

4
0.25
0.55
<div id="facets-target" style="min-height: 60vh"></div>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import facets from "https://tools.openkrill.app/shaders/effects/facets.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  mount(document.getElementById("facets-target"), facets);
</script>

Brass glow

A soft brass glow behind a title or a main button: the halo breathes slowly and its edge wavers a little.

Loops. Module glow.js: 1.7 KB, 0.8 KB gzipped. Parameter names are shown next to each control.

0.5
0.45
0.35
0.6
0.12
<div id="glow-target" style="min-height: 60vh"></div>
<script type="module">
  import { mount } from "https://tools.openkrill.app/shaders/runtime.js";
  import glow from "https://tools.openkrill.app/shaders/effects/glow.js";
  // To use an element you already have, pass it instead, such as document.querySelector(".hero").
  // The element needs a size. The effect fills it and keeps its content on top, so no extra CSS is needed.
  mount(document.getElementById("glow-target"), glow);
</script>

Put an effect on your page

  1. Add an element with a size, such as a hero block, a card or a 24 px dot. An element you already have works too.
  2. Import the runtime and one effect as ES modules. Both are plain files on this site, so there is no build step and no package to install.
  3. Call mount(element, effect, values). A loop starts at once. A one-shot effect (burst, ripple) waits for play().

mount returns controls: set(values) changes parameters while it runs, play(), stop(), seek(seconds) and destroy(). A back effect sits under the element's content and a front effect over it; neither takes clicks, and no extra CSS is needed. A colour value can be a CSS variable such as "var(--lt-glow-fill)", read from the element when it mounts. When the element has no size, the browser console says so.

Phones, reduced motion and old browsers

  • Without WebGL2 the element gets the effect's CSS background instead, so the page never shows an empty box.
  • With reduced motion turned on, the effect draws one still frame and does not move.
  • An effect stops drawing when it is off screen or the tab is hidden, and goes on where it stopped.
  • On a phone it draws at most 30 frames a second at 1.5 times the pixel ratio. When frames keep coming late it draws fewer pixels, down to half.

Exact frames for video

Each frame is a pure function of time: the same second, size and values give the same picture, and no clock is read. renderFrame(effect, { t, width, height, params }) returns the RGBA bytes of one frame and renderPng returns a PNG. Video Forge can draw frame 412 of a film without drawing frames 0 to 411. The hash behind the noise uses whole numbers, not sine, so the same seed gives the same stars on every GPU.

File sizes

The shared files (runtime, maths and palette) are 15.1 KB, 6.4 KB gzipped, and are loaded once for any number of effects.

File under /shaders/BytesGzipped bytes
glsl.js31361613
index.js814338
palette.js560373
runtime.js117794580
snippet.js1639864
effects/burst.js21221119
effects/facets.js1544797
effects/glow.js1714860
effects/grain.js1477843
effects/nebula.js1792901
effects/pulse.js1350744
effects/ripple.js1589836
effects/shimmer.js1612856
effects/starfield.js24881181

Originality

The effects are built from textbook techniques: an integer hash (PCG), value noise, simplex noise, fractal noise sums, domain warping, voronoi cells, gaussian rings, film grain and soft glow. We read about techniques, then wrote our own code without copying or rewording code from a shader gallery, a shader library or another renderer. You may copy this code into your own pages.

Each module starts with a header that names the effect, its parameters, its author and its size in bytes. As of 2026-10-07.