Laser Hands — Neural Light Studio
A browser-native, zero-install, single-file hand-tracking creative experience
Laser Hands is a self-contained single HTML file that uses your device's camera to track your hands in real time, then renders glowing multicolor laser beams across every finger bone, knuckle, and palm connection. No app to install, no server required, no video ever leaves your device. Open the file in a browser served over localhost or https, grant camera permission, and your hands become instruments of light.
It was built as a gesture-vocabulary playground — a creative tool that maps specific hand poses to visual events, with the infrastructure to add more gestures over time.
Delivery
1 HTML file
Dependencies
0 installed
Hand landmarks
21 per hand
Max hands
2 simultaneous
Laser modes
5 + random
Backend calls
Zero
Browsers require a secure origin before granting camera access, so the file must be served — not opened directly from your filesystem.
python -m http.server 8080 in the file's folder, then open http://localhost:8080/hand-laser-spa-v2.html
npx serve . and follow the link it prints
Each gesture is detected by analyzing the 21 landmark positions MediaPipe returns per hand. Gestures are mutually exclusive — the system checks them in priority order each frame.
Open palm — shockwave
Four or more fingers extended and spread apart. An expanding ring pulses outward from your wrist.
4+ fingers extended, avg tip gap > 0.45
Pinch + hold 1s — draw a line
Thumb and index tips together, other fingers open. Hold 1 second — a countdown ring fills — then release to commit a glowing line from anchor to release point. Fades after 0.5s.
pinchD < 0.45, middleExt || ringExt
Index point + hold 1s — free draw
Index extended, all other fingers curled. Hold 1 second to activate. Your fingertip lays a smooth curved laser trail as you move. Closing the index or opening other fingers ends the stroke, which fades over 1.2s.
idxExt && !midExt && !rngExt && !pkyExt
Fist — detected, reserved
All fingers curled. Recognized and shown in the HUD. Reserved for the next gesture to be added.
extended === 0
MediaPipe Hands
AI hand tracking — 21 landmarks per hand
Loaded via CDN · no install
HTML5 Canvas 2D
All laser beam rendering and effects
Native browser API
WebRTC getUserMedia
Camera stream access
Native browser API
requestAnimationFrame
Dual render + detect loops
Native browser API
Google Fonts CDN
Orbitron + Share Tech Mono
UI typography only
MediaPipe Hands is a machine-learning model trained by Google on millions of images of hands. When loaded in the browser, it runs entirely on your device using WebAssembly — a compact, fast format that lets complex AI code run in a browser tab without any server.
Every frame from your camera, it locates your hand and marks 21 key points — the base, middle, and tip of each finger, plus the wrist. These 21 points are called landmarks. The app connects them with laser lines, detects gestures by measuring distances and angles between specific points, and draws everything on a transparent canvas that sits on top of your dimmed camera feed.
The detection pipeline:
Two separate requestAnimationFrame loops run in parallel — one drives the canvas render at up to 60fps, the other sends frames to MediaPipe (throttled by detectLast). Both loops store their rAF IDs so they can be cleanly cancelled on tab-away or page unload.
Model complexity
1 (full model)
Detection confidence
0.6 minimum
Tracking confidence
0.55 minimum
Connections drawn
23 per hand
Landmark coordinates
Normalized 0–1
Mirror correction
x = 1 − x
Each connection is drawn three times on the canvas in a single pass: first a wide soft outer glow layer using shadowBlur, then a crisp core line at full color, then a thin near-white hot-center line at reduced opacity. This three-pass technique produces the glowing neon tube look without any image filters or post-processing.
Raw MediaPipe landmarks jitter slightly between frames. Rather than applying fixed smoothing (which adds lag), the app measures how far each landmark moved since the last frame and adapts the blend factor dynamically. A still hand gets heavy smoothing (steady, no tremor). A fast-moving hand gets almost no smoothing (snaps to position immediately). Alpha ranges from 0.35 at rest to 0.9 in fast motion.
When drawing with the index finger, recorded points are rendered as quadratic Bézier curves through midpoints — the standard technique for smooth freehand paths. Each segment curves through the midpoint between two consecutive recorded points rather than connecting them with straight lines, producing natural flowing curves regardless of drawing speed.
Rather than clearing the canvas every frame, each frame paints a semi-transparent dark rectangle over the entire canvas. The opacity of that rectangle controls how quickly old frames fade. A higher value clears faster with less echo. The default uses a strength multiplier of 2.6, which clears aggressively for crisp hand tracking with minimal ghosting. A Trail toggle lets the user switch to a slower-fading mode for a motion-blur aesthetic.
The canvas is sized at innerWidth × devicePixelRatio (capped at 2×), then scaled back down with ctx.setTransform. This prevents blurry rendering on retina displays without any extra drawing cost.
A finger is extended when its tip landmark is farther from the wrist than its PIP (middle knuckle) landmark. This correctly rejects a fist — where the tip sits behind or beside the PIP — while accepting extended fingers at any angle. All distances are normalized by the wrist-to-middle-MCP distance so the test is scale-invariant.
Pinch distance alone can't distinguish a pinch from a fist (both collapse the thumb-to-index distance). The detector additionally requires at least one of the middle or ring fingers to be extended. Hysteresis is applied: starting a pinch requires distance below 0.45, but sustaining one only requires below 0.60. A 180ms flicker grace window prevents brief tracking noise from resetting the 1-second arm timer.
pagehide and beforeunload handlers.
https:// or http://localhost. Browsers block camera access on file:// as a security requirement — this is by design, not a bug.
The entire app is a single flat JavaScript module — no bundler, no framework, no build step. State is held in a single state object. The two async loops (render and detect) communicate only through state.lastResults — the render loop reads whatever the detect loop last wrote. This avoids synchronization complexity while keeping frame rate independent of inference speed.
Color modes are pure functions — each mode exposes a line(i, t) and glow(i, t) function that takes a connection index and frame counter and returns an hsla() string. Switching modes at runtime is a single variable assignment with no state to reset.
Gesture state per hand is stored as small plain objects in arrays indexed by hand number, reset cleanly on hand loss or tab-away. This design makes adding new gestures straightforward — each gesture reads from the same normalized landmark array and writes to the same particle / shockwave / line queues.