π This technology powers RealEye.io β the commercial webcam eyetracking platform with heatmaps, gaze plots, and areas of interest. Learn more about real-world online webcam eyetracking β
π Try the live demo: webcam-eyetracker-light-open demo
RealEye Webcam EyeTracker Light Open is a webcam eye-tracker running in your web browser that provides on-screen gaze coordinates. It works with any regular camera β laptop built-in webcam, USB webcam, or mobile device camera. Everything runs locally on your device. No external servers, no dedicated eye-tracking hardware required. Built in TypeScript with custom ridge regression.
This dual-licensed library is the core technology behind RealEye.io, a commercial research platform that adds:
- Heatmaps & gaze plots from real users
- Areas of Interest (AOIs) and dwell-time metrics
- Cloud storage & analytics
- Integration with testing tools
- Zero setup β no eye-tracking hardware required
- Pure browser execution β runs entirely client-side
- CDN-loaded models β MediaPipe models and WASM runtime load from CDN by default (no local model files needed)
- 17-point calibration β proven optimal pattern
- Head-pose compensation β learned during calibration
- TypeScript-first β full type definitions included
- 100% self-contained β no server-side dependencies
- Interactive demo app β calibration wizard, gaze trail animation, technical info panel, click accuracy overlay, webcam selection, background controls, and runtime diagnostics
- β 120 CSS px accuracy β average gaze error after 17-point calibration
Works with any camera your browser can access β laptop built-in webcam, USB webcam, or mobile device camera.
π₯οΈ Desktop recommended β The demo application is designed for desktop/laptop browsers with a fixed webcam.
- Eye tracking requires facing the camera at a consistent distance and angle
- Calibration involves clicking small targets positioned across the entire screen
- Holding a phone at arm's length while trying to look at calibration targets is physically impractical
- The demo will show a warning banner on mobile devices; the app still runs but accuracy is significantly reduced
The demo app provides a full interactive experience: camera preview, face detection overlay, 17-point calibration wizard, and real-time gaze tracking.
π Live Demo β Try it in your browser: realeye-io.github.io/webcam-eyetracker-light-open
Before calibration β the initial landing screen with a Start Camera button (no webcam permission prompt until clicked):
Ready for calibration β face detected in a preview area, status set to Ready for Calibration, and Start Calibration enabled:
During calibration β the 17-point calibration wizard guides you through targets that appear across the screen. Progress is shown as each point is captured:
During tracking β calibrated gaze prediction with a gaze cursor, animated trail, and session statistics:
Settings & Diagnostics β expandable side panels for inference settings (Face Detection Mode, Inference Device, Max Resolution) and detailed diagnostics (video/viewport resolution, device pixel ratio, eye crop):
MediaPipe models and WASM runtime are loaded from CDN by default β no local model files needed. Just install and initialize:
npm install @realeye-io/webcam-eyetracker-light-openimport {
WebcamETLight,
getRecommendedPattern,
getCalibrationPointsInPixels,
type CalibrationSample,
} from '@realeye-io/webcam-eyetracker-light-open';
// Initialize (loads models from CDN automatically)
const tracker = new WebcamETLight({ delegate: 'GPU' });
await tracker.initialize();
// Build calibration samples using the recommended 17-point grid
const pattern = getRecommendedPattern(); // GRID_17
const points = getCalibrationPointsInPixels(
pattern,
window.innerWidth,
window.innerHeight
);
const samples: CalibrationSample[] = [];
for (const point of points) {
// Capture a webcam frame while the user looks at the target
const imageData = await captureCalibrationFrame(point);
samples.push({
image: imageData,
gazeX: point.x,
gazeY: point.y,
});
}
// Fit the ridge regression model
tracker.calibrate(samples);
// Predict gaze (synchronous)
const gazePoint = tracker.predict(liveFrame);
if (gazePoint) {
console.log(`Gaze at: (${gazePoint.x}, ${gazePoint.y})`);
}Tip: See the User Manual for a step-by-step walkthrough of the included demo app.
- User Manual β step-by-step guide for the demo app
- API Reference
- Calibration Pattern
- Technical Architecture
- Project Structure
- Testing
- Contributing
- Changelog
- Security
- License
- Third-Party Notices
- Links
import { WebcamETLight, WebcamETLightConfig } from '@realeye-io/webcam-eyetracker-light-open';
const tracker = new WebcamETLight({
delegate: 'GPU', // 'GPU' | 'CPU'
faceDetectorMode: 'landmarker', // 'landmarker' | 'blazeFace'
// modelPath and wasmPath default to CDN URLs
// Override with local paths if needed:
// modelPath: '/local/models/face_landmarker.task',
// wasmPath: '/local/wasm',
runningMode: 'VIDEO', // 'VIDEO' | 'IMAGE'
ridgeLambda: 1e-5,
minDetectionConfidence: 0.5,
useLandmarks: true,
});
await tracker.initialize();
const gaze = tracker.predict(image); // GazePoint | null| Method | Description |
|---|---|
initialize() |
Load MediaPipe models and face detector |
predict(imageData: ImageData): GazePoint | null |
Return predicted gaze in CSS pixels |
calibrate(samples: CalibrationSample[]) |
Fit ridge regression on samples |
getState(): TrackerState |
Return current tracker state |
isReady(): boolean |
Check if initialized and ready |
isCalibrated(): boolean |
Check if calibration has been applied |
detectFace(image): FaceDetectionResult | null |
Detect face with landmarks in an image |
dispose() |
Release MediaPipe resources |
Standalone functions exported from the library (not methods on WebcamETLight):
| Function | Description |
|---|---|
getRecommendedPattern() |
Return the recommended CalibrationPattern (GRID_17) |
getCalibrationPoints(pattern): CalibrationPoint[] |
Get pattern points normalized 0β1 |
getCalibrationPointsInPixels(pattern, width, height) |
Get pattern in screen-pixel coordinates |
toScreenCoordinates(points, width, height) |
Convert normalized points to screen pixels |
getPatternInfo(pattern) |
Get metadata for a calibration pattern |
interface CalibrationSample {
image: ImageData; // Webcam frame
gazeX: number; // Screen X in CSS pixels
gazeY: number; // Screen Y in CSS pixels
}
interface GazePoint {
x: number; // CSS pixels
y: number; // CSS pixels
}If face detection fails, predict() returns null. The library emits clear error states for missing calibration, initialization failures, and low detection confidence.
The project uses a single 17-point grid calibration pattern, selected for strong full-screen coverage and stable accuracy across corners, edges, center, and intermediate regions.
βββββββββββββββββββββββββββββββββββββββββββββββ
β (0.05,0.05) (0.50,0.05) (0.95,0.05) β
β β
β (0.05,0.25) (0.25,0.25) (0.50,0.25) (0.75,0.25) (0.95,0.25)β
β β
β (0.05,0.50) (0.50,0.50) (0.95,0.50) β
β β
β (0.05,0.75) (0.25,0.75) (0.50,0.75) (0.75,0.75) (0.95,0.75)β
β β
β (0.05,0.95) (0.50,0.95) (0.95,0.95) β
βββββββββββββββββββββββββββββββββββββββββββββββ
import {
CalibrationPattern,
getRecommendedPattern,
getCalibrationPoints,
getCalibrationPointsInPixels,
} from '@realeye-io/webcam-eyetracker-light-open';
const pattern = getRecommendedPattern(); // GRID_17
const normalized = getCalibrationPoints(pattern);
const screen = getCalibrationPointsInPixels(pattern, w, h);
// β [{ x: 85, y: 85 }, { x: 800, y: 85 }, ...]For each webcam frame:
- Face detection β MediaPipe Face Landmarker (default), BlazeFace fallback
- Landmark coordinates β 15 key points Γ 2 (x, y) = 30 features
- Blendshapes β 22 semantic facial parameters (eye blink, brow raise, look direction, ...)
- Eye crops β left + right regions resized to 40Γ20 pixels each (800 px total)
- Z-score + bias β normalize and append 1.0
Feature counts:
| Mode | Features | Breakdown |
|---|---|---|
| Landmarker | 1,653 | 30 + 22 + 2Γ(40Γ20) + 1 |
| BlazeFace | 1,622 | 21 + 2Γ(40Γ20) + 1 |
| Landmarks off | 257 | 16Γ16 face crop + bias |
The included demo app provides a fully-featured interactive playground:
-
Privacy-first startup β no webcam permission prompt on load; preview starts only after clicking Start Camera
-
17-Point Calibration Wizard β interactive point-by-point calibration overlay
-
Real-time Gaze Tracking β live gaze cursor with animated trail effect
-
Technical Info Panel β collapsible sidebar showing webcam resolution/FPS, tracker configuration (inference device, feature count, calibration points), and runtime metrics (processing time, sampling rate, prediction count, tracking duration)
-
Click Accuracy Overlay β click anywhere during tracking to see gaze-vs-click distance lines with average error
-
Gaze Trail Effect β animated trailing dots that visualize recent gaze movement
-
Background Color Controls β test tracking against different backgrounds (default, white, gray, black, random)
-
Webcam Selector β enumerate and switch between connected cameras with resolution presets (VGA through 4K)
-
Session Stats β gaze prediction count and tracking duration counter
-
Diagnostics Panel β optional deep-dive with video resolution, screen/viewport info, device pixel ratio, and raw eye crop previews
# Run the demo locally
npm install
npm run dev
# Opens at http://localhost:8089- Input: 1,653-element feature vector
- Output: Gaze
{ x, y }in CSS pixels - Training: Solve
(Xα΅X + Ξ»I)Ξ² = Xα΅y - Lambda:
1e-5(insensitive to change) - X & Y trained independently
- Matrix operations use
ml-matrix; the ridge regression algorithm itself is custom-implemented
- Origin: top-left of viewport
- X grows right, Y grows down
- Units: CSS pixels
| Constant | Value | Description |
|---|---|---|
EYE_INPUT_WIDTH |
40 | Regression input width |
EYE_INPUT_HEIGHT |
20 | Regression input height (2:1) |
EYE_BOX_SCALE |
1.2 | Bounding-box expansion |
EYE_CROP_PADDING |
4 | Augmentation pixel margin |
AUGMENTATION_OFFSETS |
Β±1 px | 9 shifts per capture |
DEFAULT_FEATURE_COUNT |
1,653 | Total features |
RIDGE_LAMBDA |
1e-5 | Regularization |
MIN_CALIBRATION_SAMPLES |
5 | Minimum samples required |
MIN_DETECTION_CONFIDENCE |
0.5 | Face detection threshold |
webcam-eyetracker-light-open/
βββ README.md # This file
βββ CONTRIBUTING.md # How to contribute
βββ CHANGELOG.md # Version history
βββ package.json
βββ tsconfig.json
βββ tsconfig.lib.json
βββ vitest.config.ts
βββ playwright.config.ts
βββ webpack.config.js
βββ eslint.config.js
β
βββ public/
β βββ models/ # MediaPipe models
β βββ blaze_face_short_range.tflite
β βββ face_landmarker.task
β
βββ lib/ # Core library
β βββ index.ts # All public exports
β βββ WebcamETLight.ts # Main tracker class
β βββ types.ts # TypeScript interfaces
β βββ calibration/
β β βββ CalibrationPatterns.ts
β βββ math/
β β βββ matrix.ts # Ridge regression math
β β βββ matrix-optimized.ts
β βββ face/
β β βββ FaceDetector.ts
β β βββ FaceLandmarkerAdapter.ts
β β βββ BlazeFaceAdapter.ts
β βββ features/
β βββ FeatureExtractor.ts
β βββ EyeBoxScale.ts
β βββ FeatureConfig.ts
β βββ HeadPoseAugmentation.ts
β
βββ apps/
β βββ demo_app/ # Demo (React 18)
β βββ App.tsx
β βββ index.tsx
β βββ demo_app.md # Demo app technical documentation
β βββ components/
β βββ styles/
β
βββ tests/ # Tests (unit & e2e)
βββ unit/ # Vitest unit tests
βββ e2e/ # Playwright E2E
This project uses RealTesting Open β a library making it easier to write acceptance tests of web browser apps using webcam, screen recording, full screen mode, WebRTC and more. E2E tests in this repository leverage RealTesting for virtual camera simulation and browser testing.
npm run test:unit # Vitest unit tests
npm run test:e2e # Playwright E2E (virtual camera)
npm run test:css # CSS rendering tests
npm run test:screenshot # Screenshot capture tests
npm test # All default test gates
npm run lint # ESLint
npm run lint:fix # ESLint with auto-fix
npm run typecheck # TypeScript type checking (no emit)
npm run build # Build library + demo
npm run build:lib # Compile library only (for npm publish)
npm run build:demo # Production demo build
npm run test:watch # Watch mode for unit testsSee CONTRIBUTING.md for:
- Development environment setup
- Coding standards (TypeScript strict, ESLint)
- Pull-request process
- Calibration and tracking quality guidance
When submitting changes that affect prediction accuracy:
- Update calibration tests
- Include example frames if landmark mapping changes
- Re-run
npm testfor regression checks
Copyright Β© 2025-2026 RealEye sp. z o.o.
This project (the eye-tracker library and the demo application) was created by Damian Sromek.
This repository is available under either of the following licenses:
- GNU AGPL v3 or later β see LICENSE-AGPL.md
- Commercial license β see LICENSE-COMMERCIAL.md
Under the commercial option, you may use the project without a license fee if you qualify as either:
- a company whose qualifying valuation, acquisition value, or fallback trailing-twelve-month revenue does not exceed USD $1,000,000, including Affiliates; or
- a university, academic, or nonprofit research project whose project or lab grant funding does not exceed USD $1,000,000.
If you exceed those thresholds, or if you need written commercial terms beyond the no-fee allowance, use the paid commercial license path described in LICENSE-COMMERCIAL.md.
Third-party components remain subject to their own licenses. See THIRD_PARTY_NOTICES.md and public/models/README.md.
- npm package
- RealEye.io β webcam eyetracking platform
- GitHub repository
- Issues
- User Manual β step-by-step demo guide
- Demo App Docs β technical demo documentation
- MediaPipe Models β local model hosting details
- Third-Party Notices β dependency and model attributions
- Changelog β version history
- Security Policy β vulnerability reporting
- RealTesting Open β browser acceptance testing library (webcam, screen recording, WebRTC, and more)
Made with β€οΈ by RealEye