Recording Gaze Data
Record, export, and analyze gaze sessions without React.
Core API
Gaze recording in the core library is built on top of the setGazeListener callback — you collect data yourself and export it however you like.
Basic Recording
javascript
// Start tracking first
await webgazer.begin();
// Set up recording state
const session = {
isRecording: false,
startTime: null,
data: [],
};
// Configure the listener
webgazer.setGazeListener((data, elapsedTime) => {
if (!session.isRecording || !data) return;
session.data.push({
x: data.x,
y: data.y,
timestamp: Date.now(),
relativeTime: Date.now() - session.startTime,
});
});
// Control recording
function startRecording() {
session.data = [];
session.startTime = Date.now();
session.isRecording = true;
console.log('Recording started');
}
function stopRecording() {
session.isRecording = false;
console.log(`Recording stopped. ${session.data.length} points captured.`);
return session.data;
}HTML Demo
A complete standalone recording page:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Gaze Recording</title>
<style>
body { font-family: system-ui; padding: 2rem; background: #0f1117; color: #e2e8f0; }
button {
padding: 10px 20px; border: none; border-radius: 8px;
font-size: 14px; font-weight: 600; cursor: pointer; margin-right: 8px;
}
.start { background: #6366f1; color: white; }
.stop { background: #ef4444; color: white; }
.export { background: #22c55e; color: white; }
#status { margin: 1rem 0; font-size: 14px; color: #94a3b8; }
#gaze { font-size: 24px; font-family: monospace; color: #6366f1; }
</style>
</head>
<body>
<h1>Gaze Recorder</h1>
<div>
<button class="start" id="btnStart">▶ Start</button>
<button class="stop" id="btnStop" disabled>⏹ Stop</button>
<button class="export" id="btnCSV" disabled>📥 CSV</button>
<button class="export" id="btnJSON" disabled>📥 JSON</button>
</div>
<div id="status">Click Start to begin tracking and recording.</div>
<div id="gaze">—</div>
<script src="https://unpkg.com/@webgazer-ts/core@latest/dist/webgazer.js"></script>
<script>
const session = { isRecording: false, startTime: null, data: [] };
webgazer.setGazeListener((data) => {
if (!data) return;
document.getElementById('gaze').textContent = `(${Math.round(data.x)}, ${Math.round(data.y)})`;
if (session.isRecording) {
session.data.push({
x: Math.round(data.x),
y: Math.round(data.y),
timestamp: Date.now(),
relativeTime: Date.now() - session.startTime,
});
document.getElementById('status').textContent =
`🔴 Recording... ${session.data.length} points`;
}
});
webgazer.begin().then(() => {
document.getElementById('status').textContent = 'Tracking active. Click Start to record.';
});
document.getElementById('btnStart').onclick = () => {
session.data = [];
session.startTime = Date.now();
session.isRecording = true;
document.getElementById('btnStart').disabled = true;
document.getElementById('btnStop').disabled = false;
document.getElementById('btnCSV').disabled = true;
document.getElementById('btnJSON').disabled = true;
};
document.getElementById('btnStop').onclick = () => {
session.isRecording = false;
document.getElementById('btnStart').disabled = false;
document.getElementById('btnStop').disabled = true;
document.getElementById('btnCSV').disabled = false;
document.getElementById('btnJSON').disabled = false;
document.getElementById('status').textContent =
`✅ Recorded ${session.data.length} points over ${
(session.data[session.data.length-1]?.relativeTime / 1000).toFixed(1)
}s`;
};
function download(content, filename, type) {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
Object.assign(document.createElement('a'), { href: url, download: filename }).click();
URL.revokeObjectURL(url);
}
document.getElementById('btnCSV').onclick = () => {
const csv = ['x,y,timestamp,relativeTime',
...session.data.map(d => `${d.x},${d.y},${d.timestamp},${d.relativeTime}`)
].join('\n');
download(csv, `gaze-${Date.now()}.csv`, 'text/csv');
};
document.getElementById('btnJSON').onclick = () => {
download(JSON.stringify(session.data, null, 2), `gaze-${Date.now()}.json`, 'application/json');
};
</script>
</body>
</html>Using storePoints (WebGazer-Compatible API)
For compatibility with code that uses the original WebGazer.js storePoints API:
javascript
// storePoints stores a circular buffer of the last 50 gaze positions
// indexed by a slot key k (0–49)
webgazer.storePoints(x, y, k);
// getStoredPoints() returns [xArray, yArray] each of length 50
const [storedX, storedY] = webgazer.getStoredPoints();
console.log(storedX, storedY); // Float32Arrays or number arraysThis matches the original WebGazer.js API for accuracy measurement workflows.
TypeScript Version
typescript
import webgazer from '@webgazer-ts/core';
interface GazeEntry {
x: number;
y: number;
timestamp: number;
relativeTime: number;
}
class GazeRecorder {
private data: GazeEntry[] = [];
private startTime: number | null = null;
private active = false;
start(): void {
this.data = [];
this.startTime = Date.now();
this.active = true;
}
stop(): GazeEntry[] {
this.active = false;
return [...this.data];
}
record(x: number, y: number): void {
if (!this.active || this.startTime === null) return;
this.data.push({
x: Math.round(x),
y: Math.round(y),
timestamp: Date.now(),
relativeTime: Date.now() - this.startTime,
});
}
toCSV(): string {
return [
'x,y,timestamp,relativeTime',
...this.data.map(d => `${d.x},${d.y},${d.timestamp},${d.relativeTime}`),
].join('\n');
}
toJSON(): string {
return JSON.stringify(this.data, null, 2);
}
}
// Usage
const recorder = new GazeRecorder();
await webgazer
.setGazeListener((data) => {
if (data) recorder.record(data.x, data.y);
})
.begin();
// Start/stop recording from user events
startButton.onclick = () => recorder.start();
stopButton.onclick = () => {
const data = recorder.stop();
console.log(`Recorded ${data.length} points`);
};Exporting Data
CSV
javascript
function exportCSV(data) {
const header = 'x,y,timestamp,relativeTime';
const rows = data.map(d => `${d.x},${d.y},${d.timestamp},${d.relativeTime}`);
return [header, ...rows].join('\n');
}JSON
javascript
function exportJSON(data) {
return JSON.stringify(data, null, 2);
}Uploading to Your Server
javascript
async function uploadSession(data, sessionId) {
const response = await fetch('/api/gaze-sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, data }),
});
return response.json();
}Data Analysis Tips
javascript
// Fixation detection — simple velocity threshold
function detectFixations(data, velocityThreshold = 50, minDuration = 100) {
const fixations = [];
let start = 0;
for (let i = 1; i < data.length; i++) {
const dx = data[i].x - data[i-1].x;
const dy = data[i].y - data[i-1].y;
const velocity = Math.sqrt(dx*dx + dy*dy) /
((data[i].relativeTime - data[i-1].relativeTime) || 1);
const isMoving = velocity > velocityThreshold;
if (isMoving || i === data.length - 1) {
const duration = data[i].relativeTime - data[start].relativeTime;
if (duration >= minDuration) {
const segment = data.slice(start, i);
fixations.push({
x: segment.reduce((s, d) => s + d.x, 0) / segment.length,
y: segment.reduce((s, d) => s + d.y, 0) / segment.length,
duration,
startTime: data[start].relativeTime,
});
}
start = i;
}
}
return fixations;
}