webdevpuneet.com

Canvas Procedural Lightning — Free Midpoint Displacement Snippet

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

CategoryAnimations
TagsCanvas

Copy & paste

Source Code

<div class="lt-wrap">
  <canvas id="ltCanvas" class="lt-canvas"></canvas>
  <div class="lt-panel">
    <span class="lt-tag">recursive midpoint displacement</span>
    <p>Click anywhere to strike a bolt from the top of the sky to that point.</p>
    <div class="lt-row">
      <label>Jaggedness <span id="ltJagVal">0.5</span></label>
      <input type="range" id="ltJag" min="10" max="90" value="50" step="5" />
    </div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), 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

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.

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

Storm-themed landing pages
Add a click-to-strike thunderstorm to a weather app or storm-chasing site. Each bolt is generated fresh with displace(), so no two strikes ever look the same.
Gaming and entertainment heroes
Give a game or esports page an electric background that reacts to visitors. The layered glow and branching bolts read as high-energy without any video or sprite assets.
Teaching fractal generation
Use it as a compact reference for midpoint displacement, the same recursion behind fractal terrain and coastlines. Change maxOffset or the jaggedness slider to see the shape change.
Music and event mood visuals
Reinforce an intense mood on a concert or festival page with bolts that strike where the audience clicks, punctuating drops, headliner reveals or announcements.
Launch moment attention effect
Trigger a dramatic bolt when a launch countdown reaches zero or a call to action is clicked. Because bolts are procedural, they can start wherever the visitor clicks.
Storytelling and boss encounters
Strike lightning during a story chapter or a game boss fight, and fire a thunder sound from the same click handler so the flash and the sound land together.
Related: Coin Flip
See the Coin Flip for a related animations pattern worth pairing with this one.
Related: Typewriter Glitch Gradient Reveal
See the Typewriter Glitch Gradient Reveal for a related animations pattern worth pairing with this one.

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.