Interview Importance: π‘ Important β Cursor tracking appears in 20% of frontend machine coding interviews, testing event handling, performance optimization, debouncing/throttling, and real-time UI updates. Common in companies building interactive experiences or analytics platforms.
Cursor Tracking is the process of monitoring and recording mouse cursor movements, positions, and interactions on a web page. It captures the x and y coordinates of the cursor in real-time and can be used to create interactive visual effects, collect user behavior analytics, implement custom cursors, or build collaborative features.
Visual Representation:
Browser Window (viewport)
+-----------------------------------------+
| (0,0) |
| \ |
| |
| π±οΈ (x: 450, y: 320) |
| Cursor Position |
| |
| |
| (1920,1080)|
+-----------------------------------------+
Coordinate System:
- Origin (0,0) = Top-left corner
- X increases -> right
- Y increases -> down
- clientX/Y = relative to viewport
- pageX/Y = relative to document (includes scroll)
Real-World Analogy:
Think of cursor tracking like GPS tracking on your phone. Just as GPS continuously monitors your location to provide navigation or track your running route, cursor tracking monitors the mouse position to enable features like heatmaps showing where users click most, spotlight effects following the cursor, or custom cursors that change based on what you're hovering over.
| Use Case | Problem | Solution with Cursor Tracking |
|---|---|---|
| Spotlight Effects | Need to highlight area under cursor | Track position and render circular highlight div that follows cursor |
| Custom Cursors | Default cursor doesn't match brand/UX | Hide default cursor, render custom element at cursor coordinates |
| User Analytics | Don't know where users focus attention | Record cursor movements, generate heatmaps, identify UX issues |
| Interactive Parallax | Static backgrounds feel boring | Track cursor position to shift background layers, creating depth |
| Tooltips & Popovers | Need contextual information near cursor | Position tooltip elements relative to current cursor coordinates |
| Drawing/Canvas Apps | Need to capture user drawing input | Track cursor position and state to draw lines on canvas |
| Collaborative Tools | Team members can't see each other's cursors | Broadcast cursor positions via WebSocket, render multiple cursors |
| Accessibility Testing | Need to understand user navigation patterns | Record cursor trails to analyze how users explore the interface |
Performance Benefits:
// Basic cursor tracker that displays x, y coordinates const createCursorTracker = () => { // Create display element const display = document.createElement('div'); display.id = 'cursor-position'; display.style.cssText = ` position: fixed; top: 10px; left: 10px; background: rgba(0, 0, 0, 0.8); color: white; padding: 10px 15px; border-radius: 5px; font-family: monospace; font-size: 14px; z-index: 9999; pointer-events: none; `; document.body.appendChild(display); // Track cursor movement const handleMouseMove = (event) => { const { clientX, clientY, pageX, pageY } = event; // Update display with current position display.innerHTML = ` <div>Client: (${clientX}, ${clientY})</div> <div>Page: (${pageX}, ${pageY})</div> `; }; // Attach event listener document.addEventListener('mousemove', handleMouseMove); // Return cleanup function return () => { document.removeEventListener('mousemove', handleMouseMove); display.remove(); }; }; // Usage const cleanup = createCursorTracker(); // Call cleanup() to remove tracker
Scenario: User moves cursor from (100, 100) to (200, 150)
Step 1: Initialize tracker
---------------------------------------------------------
display element created and appended to body
Event listener attached to document
State: Waiting for mouse movement
Step 2: User moves cursor to (100, 100)
---------------------------------------------------------
Event: mousemove fired
event.clientX = 100
event.clientY = 100
event.pageX = 100 (no scroll)
event.pageY = 100 (no scroll)
Action: display.innerHTML updated
Display shows: "Client: (100, 100), Page: (100, 100)"
Step 3: User moves cursor to (150, 125)
---------------------------------------------------------
Event: mousemove fired again
event.clientX = 150
event.clientY = 125
event.pageX = 150
event.pageY = 125
Action: display.innerHTML updated
Display shows: "Client: (150, 125), Page: (150, 125)"
Step 4: User moves cursor to (200, 150)
---------------------------------------------------------
Event: mousemove fired again
event.clientX = 200
event.clientY = 150
event.pageX = 200
event.pageY = 150
Action: display.innerHTML updated
Display shows: "Client: (200, 150), Page: (200, 150)"
Step 5: User scrolls page down by 500px, cursor at same viewport position
---------------------------------------------------------
Event: mousemove fired (cursor technically moved in document)
event.clientX = 200 (viewport position unchanged)
event.clientY = 150 (viewport position unchanged)
event.pageX = 200 (document X position same - horizontal scroll is 0)
event.pageY = 650 (150 + 500 scroll offset)
Action: display.innerHTML updated
Display shows: "Client: (200, 150), Page: (200, 650)"
Result: Real-time position display tracking both viewport and document coordinates
Key Insight: The mousemove event fires approximately 60-100 times per second during movement, providing smooth real-time tracking.
// β GOOD: Track across entire page document.addEventListener('mousemove', handler); // β LIMITED: Only tracks within specific element element.addEventListener('mousemove', handler);
What breaks without it: If you attach to a specific element, you lose tracking when cursor moves outside that element. Document-level tracking ensures continuous monitoring.
event.clientX // Position relative to viewport (visible window area) event.clientY // Best for: positioning fixed elements, tooltips event.pageX // Position relative to entire document (includes scroll) event.pageY // Best for: absolute positioned elements, analytics event.screenX // Position relative to user's physical screen event.screenY // Best for: multi-monitor setups, rarely used
What breaks without understanding: Using clientX for absolute positioned elements causes misalignment when page is scrolled. Using pageX for fixed elements causes them to jump around.
display.style.pointerEvents = 'none';
What breaks without it: The tracking display itself blocks mouse events, creating a "cursor trap" where the cursor appears to flicker or the display blocks interactions with elements beneath it.
// Always return cleanup function return () => { document.removeEventListener('mousemove', handleMouseMove); display.remove(); };
What breaks without it: Event listeners persist after component unmount, causing memory leaks. Multiple trackers accumulate, degrading performance. Zombie listeners fire for non-existent components.
{ passive: true } for better scroll performanceclass CursorTracker { constructor(options = {}) { // Configuration with defaults this.config = { throttleMs: options.throttleMs || 16, // ~60fps enableAnalytics: options.enableAnalytics || false, enableVisual: options.enableVisual || true, recordHistory: options.recordHistory || false, maxHistorySize: options.maxHistorySize || 1000, onMove: options.onMove || null, // Callback for custom handling ...options }; // State this.position = { x: 0, y: 0, pageX: 0, pageY: 0, timestamp: null }; this.history = []; this.isActive = false; this.visualElement = null; this.throttleTimer = null; this.lastEmitTime = 0; // Bind methods this.handleMouseMove = this.handleMouseMove.bind(this); this.handleMouseLeave = this.handleMouseLeave.bind(this); this.handleMouseEnter = this.handleMouseEnter.bind(this); } // Initialize tracker init() { if (this.isActive) { console.warn('CursorTracker: Already initialized'); return; } // Create visual indicator if enabled if (this.config.enableVisual) { this.createVisualElement(); } // Attach event listeners document.addEventListener('mousemove', this.handleMouseMove); document.addEventListener('mouseleave', this.handleMouseLeave); document.addEventListener('mouseenter', this.handleMouseEnter); this.isActive = true; console.log('CursorTracker: Initialized'); } // Create visual indicator element createVisualElement() { this.visualElement = document.createElement('div'); this.visualElement.id = 'cursor-tracker-visual'; this.visualElement.style.cssText = ` position: fixed; top: 10px; right: 10px; background: rgba(0, 0, 0, 0.85); color: #00ff00; padding: 12px 16px; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 13px; z-index: 999999; pointer-events: none; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); `; document.body.appendChild(this.visualElement); } // Update visual display updateVisual() { if (!this.visualElement) return; const { x, y, pageX, pageY, timestamp } = this.position; const historySize = this.history.length; this.visualElement.innerHTML = ` <div style="margin-bottom: 4px; color: #00ffff;">π CURSOR TRACKER</div> <div>Viewport: (${x}, ${y})</div> <div>Document: (${pageX}, ${pageY})</div> <div style="margin-top: 4px; opacity: 0.7;">History: ${historySize} points</div> `; } // Throttled mouse move handler handleMouseMove(event) { const now = Date.now(); // Throttle: only emit at specified interval if (now - this.lastEmitTime < this.config.throttleMs) { return; } this.lastEmitTime = now; // Update position this.position = { x: event.clientX, y: event.clientY, pageX: event.pageX, pageY: event.pageY, timestamp: now }; // Record to history if enabled if (this.config.recordHistory) { this.addToHistory(this.position); } // Update visual display if (this.config.enableVisual) { this.updateVisual(); } // Send to analytics if enabled if (this.config.enableAnalytics) { this.sendAnalytics(this.position); } // Call custom callback if provided if (typeof this.config.onMove === 'function') { this.config.onMove(this.position); } } // Add position to history with size limit addToHistory(position) { this.history.push({ ...position }); // Trim history if exceeds max size if (this.history.length > this.config.maxHistorySize) { this.history.shift(); } } // Handle cursor leaving window handleMouseLeave() { console.log('CursorTracker: Cursor left window'); } // Handle cursor entering window handleMouseEnter() { console.log('CursorTracker: Cursor entered window'); } // Send analytics data (mock implementation) sendAnalytics(position) { // In production, this would send to your analytics service // Examples: Google Analytics, Mixpanel, custom endpoint console.log('Analytics:', { event: 'cursor_move', x: position.x, y: position.y, timestamp: position.timestamp }); } // Get current position getPosition() { return { ...this.position }; } // Get movement history getHistory() { return [...this.history]; } // Calculate cursor speed (pixels per second) getSpeed() { if (this.history.length < 2) return 0; const recent = this.history.slice(-10); let totalDistance = 0; for (let i = 1; i < recent.length; i++) { const dx = recent[i].x - recent[i-1].x; const dy = recent[i].y - recent[i-1].y; totalDistance += Math.sqrt(dx * dx + dy * dy); } const timeSpan = recent[recent.length - 1].timestamp - recent[0].timestamp; return (totalDistance / timeSpan) * 1000; // pixels per second } // Get heatmap data (simplified) getHeatmapData(gridSize = 50) { const heatmap = {}; this.history.forEach(pos => { const gridX = Math.floor(pos.x / gridSize); const gridY = Math.floor(pos.y / gridSize); const key = `${gridX},${gridY}`; heatmap[key] = (heatmap[key] || 0) + 1; }); return heatmap; } // Clear history clearHistory() { this.history = []; console.log('CursorTracker: History cleared'); } // Destroy tracker and cleanup destroy() { if (!this.isActive) return; // Remove event listeners document.removeEventListener('mousemove', this.handleMouseMove); document.removeEventListener('mouseleave', this.handleMouseLeave); document.removeEventListener('mouseenter', this.handleMouseEnter); // Remove visual element if (this.visualElement) { this.visualElement.remove(); this.visualElement = null; } // Clear timers if (this.throttleTimer) { clearTimeout(this.throttleTimer); } // Reset state this.history = []; this.isActive = false; console.log('CursorTracker: Destroyed and cleaned up'); } } // ------------------------- // π§ͺ Usage Examples // ------------------------- // Basic usage const tracker = new CursorTracker({ throttleMs: 16, // 60fps enableAnalytics: false, enableVisual: true, recordHistory: true }); tracker.init(); // With custom callback const trackerWithCallback = new CursorTracker({ throttleMs: 50, // 20fps for better performance enableVisual: false, onMove: (position) => { console.log(`Cursor at (${position.x}, ${position.y})`); } }); trackerWithCallback.init(); // Get analytics data setTimeout(() => { const speed = tracker.getSpeed(); const heatmap = tracker.getHeatmapData(); console.log('Cursor speed:', speed, 'px/s'); console.log('Heatmap data:', heatmap); }, 5000); // Cleanup when done // tracker.destroy();
class SpotlightCursor { constructor(spotlightRadius = 150) { this.radius = spotlightRadius; this.spotlight = null; this.overlay = null; this.handleMouseMove = this.handleMouseMove.bind(this); } init() { // Create dark overlay this.overlay = document.createElement('div'); this.overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); pointer-events: none; z-index: 999998; `; document.body.appendChild(this.overlay); // Create spotlight circle this.spotlight = document.createElement('div'); this.spotlight.style.cssText = ` position: fixed; width: ${this.radius * 2}px; height: ${this.radius * 2}px; border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 70%); mix-blend-mode: destination-out; pointer-events: none; z-index: 999999; transform: translate(-50%, -50%); `; document.body.appendChild(this.spotlight); document.addEventListener('mousemove', this.handleMouseMove); } handleMouseMove(event) { const { clientX, clientY } = event; this.spotlight.style.left = `${clientX}px`; this.spotlight.style.top = `${clientY}px`; } destroy() { document.removeEventListener('mousemove', this.handleMouseMove); if (this.overlay) this.overlay.remove(); if (this.spotlight) this.spotlight.remove(); } } // Usage const spotlight = new SpotlightCursor(200); spotlight.init(); // spotlight.destroy(); // to remove
import { useState, useEffect, useRef, useCallback } from 'react'; // Custom hook for cursor tracking const useCursorTracking = (options = {}) => { const { throttleMs = 16, enableHistory = false, maxHistorySize = 100 } = options; const [position, setPosition] = useState({ x: 0, y: 0 }); const historyRef = useRef([]); const lastUpdateRef = useRef(0); const handleMouseMove = useCallback((event) => { const now = Date.now(); // Throttle updates if (now - lastUpdateRef.current < throttleMs) { return; } lastUpdateRef.current = now; const newPosition = { x: event.clientX, y: event.clientY, pageX: event.pageX, pageY: event.pageY, timestamp: now }; setPosition(newPosition); // Add to history if enabled if (enableHistory) { historyRef.current.push(newPosition); if (historyRef.current.length > maxHistorySize) { historyRef.current.shift(); } } }, [throttleMs, enableHistory, maxHistorySize]); useEffect(() => { document.addEventListener('mousemove', handleMouseMove); return () => { document.removeEventListener('mousemove', handleMouseMove); }; }, [handleMouseMove]); return { position, history: historyRef.current }; }; // Component using the hook const CursorDisplay = () => { const { position } = useCursorTracking({ throttleMs: 16 }); return ( <div style={{ position: 'fixed', top: 10, left: 10, background: 'rgba(0,0,0,0.8)', color: 'white', padding: '10px 15px', borderRadius: 5, fontFamily: 'monospace', pointerEvents: 'none', zIndex: 9999 }}> <div>X: {position.x}</div> <div>Y: {position.y}</div> </div> ); }; // Custom cursor component const CustomCursor = () => { const { position } = useCursorTracking({ throttleMs: 8 }); const [isHovering, setIsHovering] = useState(false); useEffect(() => { // Hide default cursor document.body.style.cursor = 'none'; return () => { document.body.style.cursor = 'auto'; }; }, []); // Check if hovering over interactive elements useEffect(() => { const checkHover = () => { const elements = document.elementsFromPoint(position.x, position.y); const interactive = elements.some(el => el.tagName === 'A' || el.tagName === 'BUTTON' || el.onclick !== null ); setIsHovering(interactive); }; checkHover(); }, [position]); return ( <div style={{ position: 'fixed', left: position.x, top: position.y, width: isHovering ? 40 : 20, height: isHovering ? 40 : 20, borderRadius: '50%', border: '2px solid #00ffff', transform: 'translate(-50%, -50%)', pointerEvents: 'none', zIndex: 999999, transition: 'width 0.2s, height 0.2s', mixBlendMode: 'difference' }} /> ); }; export { useCursorTracking, CursorDisplay, CustomCursor };
const CursorTrail = () => { const [trail, setTrail] = useState([]); const maxTrailLength = 20; useEffect(() => { const handleMouseMove = (e) => { setTrail(prev => { const newTrail = [ ...prev, { x: e.clientX, y: e.clientY, id: Date.now() } ]; // Keep only last N points return newTrail.slice(-maxTrailLength); }); }; document.addEventListener('mousemove', handleMouseMove); return () => document.removeEventListener('mousemove', handleMouseMove); }, []); return ( <> {trail.map((point, index) => ( <div key={point.id} style={{ position: 'fixed', left: point.x, top: point.y, width: 10, height: 10, borderRadius: '50%', background: `rgba(0, 255, 255, ${(index + 1) / maxTrailLength})`, transform: 'translate(-50%, -50%)', pointerEvents: 'none', zIndex: 999999 - index }} /> ))} </> ); };
Answer: Several optimization strategies:
let lastTime = 0; const throttleMs = 16; // ~60fps document.addEventListener('mousemove', (e) => { const now = Date.now(); if (now - lastTime < throttleMs) return; lastTime = now; // Process event });
document.addEventListener('mousemove', handler, { passive: true });
let rafId = null; let lastPosition = null; document.addEventListener('mousemove', (e) => { lastPosition = { x: e.clientX, y: e.clientY }; if (rafId === null) { rafId = requestAnimationFrame(() => { updateVisual(lastPosition); rafId = null; }); } });
Answer:
Example:
// If viewport is scrolled 100px down event.clientX = 200 // Position in visible window event.pageX = 200 // Same X (no horizontal scroll) event.clientY = 150 // Position in visible window event.pageY = 250 // 150 + 100 scroll offset
Answer: Use unified event handling for both mouse and touch:
const getPosition = (event) => { // Touch event if (event.touches && event.touches.length > 0) { return { x: event.touches[0].clientX, y: event.touches[0].clientY }; } // Mouse event return { x: event.clientX, y: event.clientY }; }; const handleMove = (event) => { const pos = getPosition(event); updatePosition(pos); }; // Listen to both event types document.addEventListener('mousemove', handleMove); document.addEventListener('touchmove', handleMove, { passive: false });
Answer:
class CursorTracker { constructor() { // Bind methods to preserve context this.handleMove = this.handleMove.bind(this); } init() { // Store reference for cleanup document.addEventListener('mousemove', this.handleMove); } destroy() { // Remove event listener using same reference document.removeEventListener('mousemove', this.handleMove); // Clear any timers if (this.throttleTimer) { clearTimeout(this.throttleTimer); } // Remove DOM elements if (this.visualElement) { this.visualElement.remove(); } // Clear history array this.history = []; } }
Key points:
Answer:
class HeatmapGenerator { constructor(gridSize = 50) { this.gridSize = gridSize; this.heatmapData = new Map(); } recordPosition(x, y) { // Convert to grid coordinates const gridX = Math.floor(x / this.gridSize); const gridY = Math.floor(y / this.gridSize); const key = `${gridX},${gridY}`; // Increment count const count = this.heatmapData.get(key) || 0; this.heatmapData.set(key, count + 1); } generateHeatmap() { const maxCount = Math.max(...this.heatmapData.values()); const heatmap = []; this.heatmapData.forEach((count, key) => { const [x, y] = key.split(',').map(Number); const intensity = count / maxCount; // Normalize 0-1 heatmap.push({ x: x * this.gridSize, y: y * this.gridSize, intensity, count }); }); return heatmap; } renderHeatmap(container) { const heatmap = this.generateHeatmap(); heatmap.forEach(cell => { const div = document.createElement('div'); div.style.cssText = ` position: absolute; left: ${cell.x}px; top: ${cell.y}px; width: ${this.gridSize}px; height: ${this.gridSize}px; background: rgba(255, 0, 0, ${cell.intensity * 0.6}); pointer-events: none; `; container.appendChild(div); }); } }
Answer:
class CursorSpeedTracker { constructor() { this.positions = []; this.maxHistorySize = 10; } recordPosition(x, y, timestamp) { this.positions.push({ x, y, timestamp }); if (this.positions.length > this.maxHistorySize) { this.positions.shift(); } } getSpeed() { if (this.positions.length < 2) return 0; let totalDistance = 0; const positions = this.positions; for (let i = 1; i < positions.length; i++) { const dx = positions[i].x - positions[i-1].x; const dy = positions[i].y - positions[i-1].y; const distance = Math.sqrt(dx * dx + dy * dy); totalDistance += distance; } const timeSpan = positions[positions.length - 1].timestamp - positions[0].timestamp; return (totalDistance / timeSpan) * 1000; // pixels per second } getAcceleration() { if (this.positions.length < 3) return 0; const positions = this.positions; const recentSpeeds = []; for (let i = 1; i < positions.length; i++) { const dx = positions[i].x - positions[i-1].x; const dy = positions[i].y - positions[i-1].y; const distance = Math.sqrt(dx * dx + dy * dy); const time = positions[i].timestamp - positions[i-1].timestamp; const speed = (distance / time) * 1000; recentSpeeds.push(speed); } // Calculate acceleration (change in speed) let totalAccel = 0; for (let i = 1; i < recentSpeeds.length; i++) { totalAccel += recentSpeeds[i] - recentSpeeds[i-1]; } return totalAccel / (recentSpeeds.length - 1); } }
β BAD: Processing every mousemove event
// Fires 60-100 times per second! document.addEventListener('mousemove', (e) => { // Heavy DOM manipulation updateComplexUI(e.clientX, e.clientY); // Network request on every move (terrible!) fetch('/api/track', { method: 'POST', body: JSON.stringify({ x: e.clientX, y: e.clientY }) }); });
Problem: Causes severe performance degradation. Can freeze UI, drop frames, drain battery, overload server.
β GOOD: Throttled event processing
let lastTime = 0; const throttleMs = 50; // 20fps, good balance document.addEventListener('mousemove', (e) => { const now = Date.now(); if (now - lastTime < throttleMs) return; lastTime = now; updateComplexUI(e.clientX, e.clientY); }); // Batch API calls every 5 seconds const positionBuffer = []; setInterval(() => { if (positionBuffer.length > 0) { fetch('/api/track', { method: 'POST', body: JSON.stringify({ positions: positionBuffer }) }); positionBuffer.length = 0; } }, 5000);
β BAD: Memory leak in React component
const CursorTracker = () => { const [pos, setPos] = useState({ x: 0, y: 0 }); // β Event listener never removed! useEffect(() => { document.addEventListener('mousemove', (e) => { setPos({ x: e.clientX, y: e.clientY }); }); }, []); // No cleanup function return <div>X: {pos.x}, Y: {pos.y}</div>; };
Problem: Every time component remounts, a new listener is added. After 100 mounts, you have 100 listeners all updating state, causing severe performance issues and memory leaks.
β GOOD: Proper cleanup
const CursorTracker = () => { const [pos, setPos] = useState({ x: 0, y: 0 }); useEffect(() => { const handleMove = (e) => { setPos({ x: e.clientX, y: e.clientY }); }; document.addEventListener('mousemove', handleMove); // β Cleanup function removes listener return () => { document.removeEventListener('mousemove', handleMove); }; }, []); return <div>X: {pos.x}, Y: {pos.y}</div>; };
β BAD: Using clientX for absolute positioned elements
const tooltip = document.createElement('div'); tooltip.style.position = 'absolute'; // Wrong! Should be 'fixed' for clientX document.addEventListener('mousemove', (e) => { // β Breaks when page is scrolled tooltip.style.left = `${e.clientX}px`; tooltip.style.top = `${e.clientY}px`; });
Problem: When user scrolls page down by 500px, tooltip appears 500px above cursor because clientX/Y is viewport-relative but absolute positioning is document-relative.
β GOOD: Match coordinate system to positioning
const tooltip = document.createElement('div'); tooltip.style.position = 'fixed'; // β Fixed with clientX document.addEventListener('mousemove', (e) => { tooltip.style.left = `${e.clientX}px`; tooltip.style.top = `${e.clientY}px`; }); // OR use absolute with pageX const absoluteElement = document.createElement('div'); absoluteElement.style.position = 'absolute'; // β Absolute with pageX document.addEventListener('mousemove', (e) => { absoluteElement.style.left = `${e.pageX}px`; absoluteElement.style.top = `${e.pageY}px`; });
β BAD: Tracker blocks mouse events
const tracker = document.createElement('div'); tracker.style.cssText = ` position: fixed; top: 10px; left: 10px; width: 200px; height: 100px; background: rgba(0, 0, 0, 0.8); z-index: 9999; `; // β No pointer-events: none document.body.appendChild(tracker);
Problem: Tracker blocks clicks and hovers on elements beneath it. Buttons become unclickable, links don't work in that area.
β GOOD: Make tracking elements non-interactive
const tracker = document.createElement('div'); tracker.style.cssText = ` position: fixed; top: 10px; left: 10px; width: 200px; height: 100px; background: rgba(0, 0, 0, 0.8); z-index: 9999; pointer-events: none; /* β Allows events to pass through */ `; document.body.appendChild(tracker);
| Operation | Time Complexity | Space Complexity | Explanation |
|---|---|---|---|
| Track single position | O(1) | O(1) | Simple variable update |
| Update visual element | O(1) | O(1) | Direct DOM property modification |
| Record to history (no limit) | O(1) amortized | O(n) where n = history size | Array push is O(1), grows linearly |
| Record to history (with limit) | O(1) | O(k) where k = max size | Circular buffer or shift when full |
| Calculate speed from history | O(h) where h = recent points | O(1) | Loop through recent history |
| Generate heatmap | O(n) where n = positions | O(g) where g = grid cells | Grid-based bucketing |
| Throttled event handling | O(1) per event | O(1) | Simple timestamp comparison |
| Render trail effect | O(t) where t = trail length | O(t) | Create element for each trail point |
| Cleanup/destroy | O(1) | O(1) | Remove listener, clear references |
Key Optimization Notes:
| Feature | Use Case | Key Technique |
|---|---|---|
| Position display | Debug, dev tools | clientX/Y with fixed positioning |
| Custom cursor | Branding, UX enhancement | Hide default cursor, position element at coordinates |
| Spotlight effect | Focus attention, presentations | Overlay with radial gradient at cursor |
| Trail effect | Visual feedback, drawing apps | Array of recent positions with fade effect |
| Heatmap | Analytics, UX research | Grid-based bucketing of positions |
| Speed tracking | Gesture detection, gaming | Calculate distance between recent points |
| Drawing canvas | Signature, whiteboard | Track mousedown/move/up states |
Always throttle or use RAF: Mousemove fires 60-100x/sec. Throttle to 16ms (60fps) or 50ms (20fps) for complex operations. Use requestAnimationFrame for visual updates.
Clean up event listeners: Memory leaks occur when listeners persist after component unmount. Always return cleanup function in React useEffect or call removeEventListener in destroy methods.
Match coordinates to positioning: Use clientX/Y with fixed positioning, pageX/Y with absolute positioning. Mismatches cause position errors when scrolling.
Optimize for performance: Use pointer-events: none on tracking elements, transform instead of top/left, passive event listeners, and batch API calls instead of sending individually.
Handle edge cases: Account for touch devices (touchmove events), cursor leaving window (mouseleave), scroll offset differences (clientY vs pageY), and high-frequency event floods.
js/general-concepts/event_loop.md) - Understanding event processingjs/utils/debounce_throttle.md) - Performance optimization patternsTest your understanding with 3 quick questions