Canvas Procedural Lightning — Free Midpoint Displacement Snippet

Canvas Procedural Lightning · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Recursive midpoint displacement
The same fractal technique used for terrain/coastline generation.
Self-similar jaggedness
Coarse kinks nest finer kinks at every recursion level.
Perpendicular-only displacement
Keeps bolts progressing toward their target, never scribbling.
Fractal branch spawning
Branches reuse the same displacement function at smaller scale.
Two-layer glow rendering
A soft blurred outer stroke plus a bright narrow core.
Screen-flash on strike
A brief bright fill sells the illumination of a real strike.
Click-to-strike interaction
Bolts target wherever the user clicks.
DPR-aware rendering
Crisp bolt linework on high-density displays.

About this UI Snippet

Canvas Procedural Lightning — Fractal Midpoint Displacement, Not Random Points

Screenshot of the Canvas Procedural Lightning snippet rendered live

A convincing lightning bolt isn't a jagged line drawn from random points — it's a fractal, where the same jaggedness pattern repeats at every zoom level. This snippet builds bolts using recursive midpoint displacement, the classic technique also used to generate fractal terrain heightmaps and coastlines.

Displace the midpoint, then recurse on both halves

displace() takes a straight line from point A to B, finds its exact midpoint, nudges that midpoint sideways (perpendicular to the A-B direction) by a random amount, then recursively calls itself on the two new sub-segments — A-to-midpoint and midpoint-to-B — each with a smaller maximum displacement (maxOffset * 0.55). Because every recursive level operates on a shorter segment with a proportionally smaller displacement budget, the result has large kinks at the coarse level and progressively finer, subtler kinks nested inside them — exactly the self-similar roughness real lightning (and coastlines, and mountain ridgelines) actually has.

Perpendicular displacement, not free-form randomness

The midpoint is only ever nudged along the perpendicular to its segment (nx = -dy/len, ny = dx/len), never along the segment's own direction. That constraint is what keeps the bolt visually progressing from source to target — a lightning bolt with points randomly jittered in *every* direction would double back on itself and look like scribble, not electricity.

Branches spawn off the main bolt's segments, not independently

After the main bolt's point list is built, buildBolt() walks its interior segments and, with a fixed probability per segment, spawns a short secondary bolt starting from that segment's endpoint at a randomly offset angle — itself built with the same displace() function at a smaller depth and displacement range. Reusing the identical fractal-generation function for branches (just with different parameters) is what keeps branches visually consistent with the main bolt instead of looking like a different effect grafted on.

Two-layer stroke for the glow

Each bolt is stroked twice: once wide, low-opacity, and heavily blurred (shadowBlur: 22) for the outer glow, and once narrow, high-opacity, and lightly blurred for the bright core — the same "wide soft layer under a narrow bright layer" trick used for neon and energy effects generally.

A screen-flash pass sells the strike

On every new strike, a flash value spikes to 1 and decays exponentially; while positive, the frame-clearing fill uses a bright, near-white color instead of the usual translucent dark wash, producing a brief whole-canvas flash that reads as the strike illuminating the sky, just like real lightning.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain why recursive midpoint displacement with a shrinking displacement budget per level produces self-similar, fractal-looking jaggedness, and why constraining displacement to the perpendicular direction is essential for the bolt to read as directed rather than scribbled. It's a great snippet to extend with an assistant — ask for a version with multiple simultaneous strikes across the sky, a version that strikes a specific DOM element's position (e.g. hitting a button on click for a "power surge" effect), or a slower-motion strike where the bolt animates growing from source to target instead of appearing instantly.

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 interactive "procedural lightning" effect in plain HTML, CSS, and JavaScript using only the Canvas 2D API and a hand-rolled recursive midpoint-displacement algorithm — no external library.

Requirements:
- A recursive function that takes a straight line segment (two endpoints) and a maximum displacement amount, computes the segment's exact midpoint, displaces that midpoint perpendicular to the segment's direction by a random amount within the displacement budget, then recursively calls itself on the two resulting sub-segments (start-to-midpoint and midpoint-to-end) with a smaller displacement budget (e.g. multiplied by ~0.55) and one less recursion depth. Collect the resulting displaced points into an ordered array forming the jagged bolt path.
- On click anywhere on the canvas, generate a new bolt from a point near the top of the canvas (randomized horizontally) down to the clicked point using that recursive displacement function, and add it to an active-bolts list (capping the list at a small number, dropping the oldest when exceeded).
- After generating the main bolt's point list, walk its interior segments and, with a fixed probability per segment, spawn a short secondary "branch" bolt starting at that segment's endpoint, at a randomly offset angle, generated using the SAME recursive displacement function at a smaller recursion depth and displacement range — branches should look visually consistent with the main bolt, not drawn with different logic.
- Render each bolt with a two-layer glow: first stroke it wide, low-opacity, and heavily blurred (via shadowBlur) for a soft outer glow, then stroke it again narrow, high-opacity, and lightly blurred for a bright core, with branches rendered similarly but thinner and more transparent than the main bolt.
- Fade each bolt's opacity out over roughly 15-25 frames after it strikes, removing it from the active list once fully faded, using a semi-transparent full-canvas fill each frame (rather than clearRect) so trails/afterimages linger briefly.
- On every new strike, trigger a brief whole-canvas "flash": for a few frames immediately after a strike, use a bright near-white fill (decaying exponentially back to the normal translucent dark fill) instead of the normal per-frame background fill, simulating the strike illuminating the surrounding sky.
- Expose a "Jaggedness" slider controlling the initial displacement budget passed into the recursive function. Handle window resize and scale for devicePixelRatio so bolts render crisply on high-DPI screens.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA demo bolt strikes automatically shortly after load.
  2. 2
    Click anywhere on the canvasA new jagged, branching bolt strikes from the sky to that point.
  3. 3
    Watch the flashThe whole canvas briefly brightens on each new strike.
  4. 4
    Watch bolts fadeEach bolt's opacity decays over roughly 20 frames.
  5. 5
    Adjust JaggednessHigher values produce wilder, more chaotic bolt paths.
  6. 6
    Strike repeatedlyUp to 4 bolts can be visible and fading at once.

Real-world uses

Common Use Cases

Weather/storm-themed landing pages
An interactive, physically-inspired storm effect.
Gaming/entertainment sites
A high-energy interactive hero background.
Educational/graphics teaching demos
A compact, readable fractal-generation reference.
Music/event pages
Reinforce an intense, electric visual mood.
Product launch pages
A dramatic click-triggered attention effect.
Portfolio pieces
Demonstrate procedural/fractal generation skill.

Got questions?

Frequently Asked Questions

Midpoint displacement is a fractal-generation technique: take a straight segment, displace its midpoint perpendicular to the segment by a random amount, then recurse on each half with a smaller displacement budget. Because each recursion level operates at a smaller scale with proportionally smaller displacement, the result is self-similar roughness — large kinks with progressively finer kinks nested inside them — which looks like real lightning, terrain, or coastlines, rather than the scribbly path a fully random set of points would produce.

Restricting displacement to the perpendicular direction (computed as the segment's direction vector rotated 90 degrees) guarantees the bolt keeps making net progress from its start point toward its target, since no displacement ever pushes a point backward along the segment's own direction. Removing that constraint would let points jitter freely and the bolt would double back on itself, reading as scribble rather than a directed strike.

Branches are generated by calling the exact same displace() function used for the main bolt, just starting from a point partway along the main bolt, at a randomly offset angle, with a smaller recursion depth and displacement range. Reusing the identical fractal-generation logic (rather than a separate, simpler branch-drawing routine) is what makes branches look like they belong to the same lightning, not a different visual effect layered on top.

Each new strike sets a flash variable to 1, which decays exponentially (multiplied by 0.85) every frame. While flash is above a small threshold, the per-frame background fill uses a bright, near-white color scaled by the current flash value instead of the usual translucent dark wash used for the trailing-fade effect, producing a brief brightening of the entire canvas that reads as the strike illuminating the surrounding sky.

Yes — strike() currently randomizes the bolt's starting x-position with a spread proportional to canvas width; replace that randomized startX with a fixed value (or a value derived from a visible "cloud" element's position) to always originate bolts from the same point, while leaving the recursive displace() and branch logic completely unchanged.