Laser Body — Multi-Person Stickman Studio
A browser-native full-body pose tracker that turns up to six people into glowing skeletons
Laser Body is a self-contained single HTML file that uses your device's camera to detect the full-body pose of every person visible, then renders each of them as a glowing multicolor stickman skeleton. Up to six people are tracked simultaneously — walk into frame with friends and everyone lights up with their own moving body of light.
Where Laser Hands tracks 21 fingers-and-palm landmarks per hand for close-up gesture work, Laser Body tracks 17 major body landmarks per person (head, shoulders, elbows, wrists, hips, knees, ankles) for full-body movement across a room. Both apps run entirely on-device — no video ever leaves the browser tab.
Delivery
1 HTML file
Dependencies
0 installed
Body landmarks
17 per person
Max people
6 simultaneous
Backbone
MoveNet Lightning
Backend calls
Zero
Like all camera-based browser apps, this must be served over a secure origin — https:// or http://localhost. Direct file:// access is blocked by the browser for camera security.
python -m http.server 8080 in the file's folder, open http://localhost:8080/laser-body.html
npx serve . and use the printed link
MoveNet MultiPose is Google's real-time multi-person pose model, distributed via TensorFlow.js. It runs entirely in the browser using WebGL for GPU acceleration — every frame from your camera is passed through the neural network, which returns a list of detected people, each with their 17 keypoint coordinates and per-keypoint confidence scores.
Single person
Stand in front of the camera at full body distance (3–5 feet works best). Your skeleton renders in real time with a colored glowing outline.
Multi-person
Up to 6 people simultaneously. Each person is tracked as a separate pose object and rendered with their own color scheme.
Full-body motion
Dance, stretch, jump — every joint is tracked at 30+ fps on modern devices. Great for movement art, warmups, or performance capture visualization.
Partial occlusion
The model gracefully handles partial visibility — hidden limbs get low confidence scores and are omitted from rendering rather than drawing incorrectly.
MoveNet returns 17 canonical keypoints per person: nose, both eyes, both ears, both shoulders, both elbows, both wrists, both hips, both knees, both ankles. The app draws a curated set of connections between these to form a stickman — spine, arms (shoulder-elbow-wrist), and legs (hip-knee-ankle) — plus head decorations connecting the facial landmarks.
Keypoints per person
17 (nose to ankles)
Backbone model
MoveNet Lightning
Backend
WebGL (GPU)
Coordinate space
Pixels (mapped to canvas)
Confidence threshold
Per-keypoint filtering
Mirror correction
x = w − x
TensorFlow.js Core
JavaScript ML runtime
CDN · v4.10
TF.js Converter
Loads MoveNet's graph model format
CDN · v4.10
TF.js WebGL backend
GPU-accelerated tensor ops via WebGL shaders
CDN · v4.10
pose-detection model
MoveNet MultiPose Lightning wrapper
CDN · v2.1
HTML5 Canvas 2D
All skeleton rendering and glow effects
Native browser
WebRTC getUserMedia
Camera stream access
Native browser
MoveNet is a neural network trained on millions of images of people. When you load it in the browser, TensorFlow.js unpacks the model into your device's GPU using WebGL. Every frame from your camera, MoveNet finds each person in the image and marks 17 key body points — nose, shoulders, elbows, and so on down to the ankles. The app connects those points with glowing lines to make a stickman, and does this for every person at the same time.
The detection pipeline:
MoveNet MultiPose Lightning is optimized for speed over accuracy. The Lightning variant achieves ~30+ fps on modern mobile devices; the Thunder variant offers higher accuracy at lower fps. This app uses Lightning for its multi-person mode because interactive art favors responsive tracking of many bodies over perfect landmarks on one.
The render loop uses requestAnimationFrame; pose inference runs in parallel (async). The render loop reads whatever the latest inference produced — decoupling the two means dropped inference frames don't stall rendering, and vice versa.
Each bone connection is drawn with a three-pass technique similar to Laser Hands: a wide soft outer glow using shadowBlur, a crisp colored core line, and a hot white center line. This creates the neon tube effect without any post-processing shaders or filters.
Each detected person gets a distinct color scheme — the app assigns colors by tracking pose IDs across frames so the same person keeps the same color even as they move. When someone leaves and re-enters the frame, they may get a new color since MoveNet has no cross-frame identity system built in.
Each of the 17 keypoints returns a confidence score from MoveNet. If a keypoint's confidence is below a threshold (typically because the joint is occluded), the connections touching that keypoint are skipped for that frame. This prevents the "jumping to origin" bug where hidden joints snap to (0,0) and draw a line across the whole canvas.
The camera feed is mirrored horizontally so users see themselves as they'd appear in a mirror. All keypoint x-coordinates are flipped (x = width − x) before rendering so the skeleton overlays the visible video correctly.
pagehide/beforeunload.https:// or http://localhost. Camera access is blocked on file:// by browser security policy.The entire app is a single flat script — no bundler, no framework, no build step. Model loading happens once at startup; a loading spinner shows during the ~2–3 second initial download and warmup. Once loaded, all subsequent inference runs against the already-loaded model in GPU memory.
State is a single state object holding the latest pose results, per-person color assignments, and rendering options. The render and detect loops are decoupled — the render loop consumes whatever pose data the detect loop last produced, so dropped inference frames never stall rendering.
The bone-connection table (BODY_CONNECTIONS) is a plain array of keypoint-index pairs. Adding or removing skeleton features means editing this array — no other code changes required.