eyetracker / index.html
devarajns's picture
Update index.html
c89aeb6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Eye Movement Tracker</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #5d69b2;
--secondary-color: #3a416f;
--accent-color: #ff7e5f;
--bg-color: #f5f7fa;
--text-color: #333;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
padding: 2rem;
position: relative;
overflow-x: hidden;
}
header {
text-align: center;
margin-bottom: 2rem;
width: 100%;
}
h1 {
color: var(--primary-color);
margin-bottom: 0.5rem;
font-size: 2.2rem;
}
.subtitle {
color: var(--secondary-color);
opacity: 0.8;
margin-bottom: 1.5rem;
}
.tracker-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 800px;
gap: 2rem;
}
.face-container {
position: relative;
width: 300px;
height: 300px;
background-color: #fff;
border-radius: 50%;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 1rem;
transition: transform 0.3s ease;
}
.face-container:hover {
transform: scale(1.02);
}
.eyes-wrapper {
display: flex;
gap: 60px;
position: relative;
top: -20px;
}
.eye {
width: 60px;
height: 60px;
background-color: #fff;
border-radius: 50%;
position: relative;
box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.pupil {
width: 30px;
height: 30px;
background-color: var(--secondary-color);
border-radius: 50%;
position: absolute;
top: 15px;
left: 15px;
transition: all 0.1s ease;
}
.metrics-container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
width: 100%;
}
.metric-card {
background-color: white;
border-radius: 12px;
padding: 1.5rem;
min-width: 200px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
flex-grow: 1;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.metric-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.metric-title {
color: var(--primary-color);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.metric-value {
font-size: 1.8rem;
font-weight: bold;
color: var(--secondary-color);
}
.metric-unit {
font-size: 0.9rem;
color: #888;
margin-left: 0.3rem;
}
.chart-container {
width: 100%;
height: 200px;
background-color: white;
border-radius: 12px;
padding: 1rem;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
.controls {
display: flex;
gap: 1rem;
margin-top: 1rem;
}
button {
padding: 0.8rem 1.5rem;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
button:hover {
background-color: var(--secondary-color);
transform: translateY(-2px);
}
button.secondary {
background-color: white;
color: var(--primary-color);
border: 1px solid #ddd;
}
button.secondary:hover {
background-color: #f0f0f0;
}
.data-points {
position: absolute;
top: 20px;
right: 20px;
width: 15px;
height: 15px;
background-color: var(--accent-color);
border-radius: 50%;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
footer {
margin-top: 3rem;
text-align: center;
color: #888;
font-size: 0.9rem;
}
@media (max-width: 600px) {
.tracker-container {
gap: 1rem;
}
.face-container {
width: 250px;
height: 250px;
}
.eyes-wrapper {
gap: 40px;
}
.eye {
width: 50px;
height: 50px;
}
.pupil {
width: 25px;
height: 25px;
top: 12.5px;
left: 12.5px;
}
.metric-card {
min-width: 150px;
padding: 1rem;
}
.metric-value {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<header>
<h1><i class="fas fa-eye"></i> Eye Movement Tracker</h1>
<p class="subtitle">Interactive demonstration of gaze tracking using mouse position</p>
</header>
<div class="tracker-container">
<div class="face-container">
<div class="eyes-wrapper">
<div class="eye">
<div class="pupil" id="left-pupil"></div>
</div>
<div class="eye">
<div class="pupil" id="right-pupil"></div>
</div>
</div>
<div class="data-points" id="data-point"></div>
</div>
<div class="metrics-container">
<div class="metric-card">
<div class="metric-title">
<i class="fas fa-crosshairs"></i> Gaze Position
</div>
<div class="metric-value" id="gaze-position">
<span id="gaze-x">0</span>, <span id="gaze-y">0</span>
</div>
</div>
<div class="metric-card">
<div class="metric-title">
<i class="fas fa-running"></i> Movement Speed
</div>
<div class="metric-value" id="movement-speed">0<span class="metric-unit">px/s</span></div>
</div>
<div class="metric-card">
<div class="metric-title">
<i class="fas fa-history"></i> Time Focused
</div>
<div class="metric-value" id="time-focused">0<span class="metric-unit">s</span></div>
</div>
<div class="metric-card">
<div class="metric-title">
<i class="fas fa-bullseye"></i> Fixations
</div>
<div class="metric-value" id="fixation-count">0</div>
</div>
</div>
<div class="chart-container">
<canvas id="gaze-chart"></canvas>
</div>
<div class="controls">
<button id="start-btn"><i class="fas fa-play"></i> Start Tracking</button>
<button id="reset-btn" class="secondary"><i class="fas fa-redo"></i> Reset</button>
<button id="heatmap-btn" class="secondary"><i class="fas fa-fire"></i> Show Heatmap</button>
</div>
</div>
<footer>
<p>Eye Movement Tracker &copy; 2025 | Uses mouse position to simulate eye tracking</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Elements
const leftPupil = document.getElementById('left-pupil');
const rightPupil = document.getElementById('right-pupil');
const gazeX = document.getElementById('gaze-x');
const gazeY = document.getElementById('gaze-y');
const movementSpeed = document.getElementById('movement-speed');
const timeFocused = document.getElementById('time-focused');
const fixationCount = document.getElementById('fixation-count');
const startBtn = document.getElementById('start-btn');
const resetBtn = document.getElementById('reset-btn');
const heatmapBtn = document.getElementById('heatmap-btn');
const dataPoint = document.getElementById('data-point');
const faceContainer = document.querySelector('.face-container');
// Variables
let trackingActive = false;
let startTime = 0;
let lastPosition = { x: 0, y: 0 };
let lastTime = 0;
let currentSpeed = 0;
let fixations = 0;
let fixationStartTime = 0;
let isFixated = false;
let gazeHistory = [];
let heatmapPoints = [];
// Chart setup
const ctx = document.getElementById('gaze-chart').getContext('2d');
const gazeChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Gaze Movement Speed (px/s)',
data: [],
borderColor: '#5d69b2',
backgroundColor: 'rgba(93, 105, 178, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
},
animation: {
duration: 0
}
}
});
// Helper functions
function calculatePupilPosition(eye, event) {
const eyeRect = eye.getBoundingClientRect();
const eyeCenterX = eyeRect.left + eyeRect.width / 2;
const eyeCenterY = eyeRect.top + eyeRect.height / 2;
const angle = Math.atan2(event.clientY - eyeCenterY, event.clientX - eyeCenterX);
const distance = Math.min(15, Math.sqrt(
Math.pow(event.clientX - eyeCenterX, 2) +
Math.pow(event.clientY - eyeCenterY, 2)
) / 10);
const x = distance * Math.cos(angle);
const y = distance * Math.sin(angle);
return { x, y };
}
function updateMetrics(event) {
const now = Date.now();
const timeElapsed = (now - lastTime) / 1000;
if (timeElapsed > 0) {
const dx = event.clientX - lastPosition.x;
const dy = event.clientY - lastPosition.y;
const distance = Math.sqrt(dx * dx + dy * dy);
currentSpeed = distance / timeElapsed;
// Check for fixation (hovering in the same area)
if (distance < 15) { // 15px threshold for fixation
if (!isFixated) {
isFixated = true;
fixationStartTime = now;
}
// If fixation lasts more than 200ms, count it
if (isFixated && now - fixationStartTime > 200 && !fixationCounted) {
fixations++;
fixationCount.textContent = fixations;
fixationCounted = true;
// Show fixation point
if (dataPoint) {
dataPoint.style.left = `${event.clientX - faceContainer.getBoundingClientRect().left - 7.5}px`;
dataPoint.style.top = `${event.clientY - faceContainer.getBoundingClientRect().top - 7.5}px`;
dataPoint.style.opacity = '0.7';
setTimeout(() => {
dataPoint.style.opacity = '0';
}, 500);
}
// Store heatmap point
const rect = faceContainer.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
heatmapPoints.push({ x, y });
}
} else {
isFixated = false;
fixationCounted = false;
}
}
// Update metrics display
movementSpeed.textContent = Math.round(currentSpeed);
timeFocused.textContent = Math.round((now - startTime) / 1000);
// Update chart data
if (gazeHistory.length > 50) {
gazeHistory.shift();
gazeChart.data.labels.shift();
gazeChart.data.datasets[0].data.shift();
}
gazeHistory.push(currentSpeed);
gazeChart.data.labels.push('');
gazeChart.data.datasets[0].data.push(currentSpeed);
gazeChart.update();
// Update last position and time
lastPosition = { x: event.clientX, y: event.clientY };
lastTime = now;
}
function startTracking() {
trackingActive = true;
startTime = Date.now();
startBtn.innerHTML = '<i class="fas fa-pause"></i> Pause Tracking';
startBtn.style.backgroundColor = '#ff7e5f';
}
function pauseTracking() {
trackingActive = false;
startBtn.innerHTML = '<i class="fas fa-play"></i> Resume Tracking';
startBtn.style.backgroundColor = '#5d69b2';
}
function resetTracking() {
pauseTracking();
startTime = 0;
currentSpeed = 0;
fixations = 0;
gazeHistory = [];
heatmapPoints = [];
// Reset metrics display
gazeX.textContent = '0';
gazeY.textContent = '0';
movementSpeed.textContent = '0';
timeFocused.textContent = '0';
fixationCount.textContent = '0';
// Reset chart
gazeChart.data.labels = [];
gazeChart.data.datasets[0].data = [];
gazeChart.update();
// Reset pupils to center
leftPupil.style.transform = 'translate(0, 0)';
rightPupil.style.transform = 'translate(0, 0)';
}
function showHeatmap() {
const heatmapOverlay = document.createElement('div');
heatmapOverlay.style.position = 'absolute';
heatmapOverlay.style.top = '0';
heatmapOverlay.style.left = '0';
heatmapOverlay.style.width = '100%';
heatmapOverlay.style.height = '100%';
heatmapOverlay.style.background = 'rgba(255, 126, 95, 0.1)';
heatmapOverlay.style.borderRadius = '50%';
heatmapPoints.forEach(point => {
const heatPoint = document.createElement('div');
heatPoint.style.position = 'absolute';
heatPoint.style.width = '20px';
heatPoint.style.height = '20px';
heatPoint.style.background = 'rgba(255, 126, 95, 0.3)';
heatPoint.style.borderRadius = '50%';
heatPoint.style.left = `${point.x - 10}px`;
heatPoint.style.top = `${point.y - 10}px`;
heatPoint.style.pointerEvents = 'none';
heatmapOverlay.appendChild(heatPoint);
});
faceContainer.appendChild(heatmapOverlay);
setTimeout(() => {
faceContainer.removeChild(heatmapOverlay);
}, 5000);
}
// Event listeners
document.addEventListener('mousemove', function(event) {
// Update gaze position display
gazeX.textContent = event.clientX;
gazeY.textContent = event.clientY;
// Calculate pupil positions
const leftPupilPos = calculatePupilPosition(leftPupil.parentElement, event);
const rightPupilPos = calculatePupilPosition(rightPupil.parentElement, event);
// Apply pupil positions
leftPupil.style.transform = `translate(${leftPupilPos.x}px, ${leftPupilPos.y}px)`;
rightPupil.style.transform = `translate(${rightPupilPos.x}px, ${rightPupilPos.y}px)`;
// Update metrics if tracking is active
if (trackingActive) {
updateMetrics(event);
}
});
startBtn.addEventListener('click', function() {
if (trackingActive) {
pauseTracking();
} else {
startTracking();
}
});
resetBtn.addEventListener('click', resetTracking);
heatmapBtn.addEventListener('click', showHeatmap);
// Initialize
resetTracking();
});
</script>
</body>
</html>