augen test

Googly Eyes – Ultimate Edition https://cdn.jsdelivr.net/npm/@vladmandic/face-api/dist/face-api.min.js * { margin: 0; padding: 0; box-sizing: border-box; } body { background-color: #000; display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow: hidden; font-family: Arial, sans-serif; } .eyes-container { display: flex; gap: 60px; position: relative; z-index: 10; } .eye { width: 200px; height: 200px; background-color: #fff; border-radius: 50%; position: relative; overflow: hidden; transition: transform 0.15s ease-out; } .pupil { width: 80px; height: 80px; background-color: #000; border-radius: 50%; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); transition: all 0.1s ease-out; } .eye.blinking { animation: blink 0.15s ease-in-out; } @keyframes blink { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.05); } } /* Kamera Preview */ #videoContainer { position: fixed; bottom: 20px; right: 20px; z-index: 100; } #video { width: 320px; height: 240px; border: 3px solid rgba(255, 255, 255, 0.3); border-radius: 10px; transform: scaleX(-1); } #canvas { position: absolute; top: 0; left: 0; transform: scaleX(-1); } /* Kontrollpanel */ .control-panel { position: fixed; top: 20px; left: 20px; background-color: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 10px; color: #fff; backdrop-filter: blur(10px); max-width: 320px; z-index: 100; } .control-panel.collapsed { padding: 10px 15px; } .control-panel.collapsed .panel-content { display: none; } .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; cursor: pointer; } .control-panel.collapsed .panel-header { margin-bottom: 0; } .panel-header h3 { font-size: 16px; } .toggle-icon { font-size: 20px; transition: transform 0.3s; } .control-panel.collapsed .toggle-icon { transform: rotate(180deg); } .mode-selector { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.2); } .mode-option { display: flex; align-items: center; gap: 10px; padding: 10px; background-color: rgba(255, 255, 255, 0.05); border-radius: 5px; cursor: pointer; transition: background-color 0.2s; } .mode-option:hover { background-color: rgba(255, 255, 255, 0.1); } .mode-option.active { background-color: rgba(0, 255, 100, 0.2); border: 2px solid rgba(0, 255, 100, 0.5); } .mode-option input[type=“checkbox“] { width: 18px; height: 18px; cursor: pointer; } .mode-option label { cursor: pointer; flex: 1; font-size: 14px; } .control-group { margin-bottom: 15px; } .control-group label { display: block; margin-bottom: 5px; font-size: 12px; color: #ccc; } .control-group input[type=“range“] { width: 100%; cursor: pointer; } .control-group .value-display { display: inline-block; margin-left: 10px; font-weight: bold; color: #fff; } .status-indicator { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 8px; } .status-indicator.active { background-color: #0f0; box-shadow: 0 0 10px #0f0; } .status-indicator.inactive { background-color: #555; } .button-group { display: flex; gap: 10px; margin-top: 15px; } .control-btn { flex: 1; background-color: rgba(255, 255, 255, 0.2); color: #fff; border: none; padding: 10px; border-radius: 5px; cursor: pointer; font-size: 13px; transition: background-color 0.2s; } .control-btn:hover { background-color: rgba(255, 255, 255, 0.3); } .control-btn:disabled { opacity: 0.5; cursor: not-allowed; } .control-btn.active { background-color: rgba(0, 255, 100, 0.3); } /* Status Meldung */ .status-message { position: fixed; top: 20px; right: 20px; background-color: rgba(255, 255, 255, 0.1); color: #fff; padding: 15px 20px; border-radius: 10px; backdrop-filter: blur(10px); z-index: 150; font-size: 14px; opacity: 0; transform: translateY(-20px); transition: all 0.3s; pointer-events: none; } .status-message.show { opacity: 1; transform: translateY(0); } .status-message.error { background-color: rgba(255, 50, 50, 0.3); } .status-message.success { background-color: rgba(0, 255, 100, 0.2); } .hidden { display: none !important; } /* Loading Spinner */ .spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 50%; border-top-color: #fff; animation: spin 1s linear infinite; margin-right: 8px; vertical-align: middle; } @keyframes spin { to { transform: rotate(360deg); } }

⚙️ Googly Eyes Steuerung

Automatisch (Zufall)
Maus folgen
Kamera folgen
Bewegungsgeschwindigkeit (ms) 1500
Bewegungsintervall (ms) 2000
Bewegungsradius (px) 40
Maus-Inaktivität Timeout (ms) 2000
// DOM Elemente const video = document.getElementById(‚video‘); const canvas = document.getElementById(‚canvas‘); const videoContainer = document.getElementById(‚videoContainer‘); const leftPupil = document.getElementById(‚leftPupil‘); const rightPupil = document.getElementById(‚rightPupil‘); const leftEye = document.getElementById(‚leftEye‘); const rightEye = document.getElementById(‚rightEye‘); // Konfiguration let config = { movementSpeed: 1500, movementInterval: 2000, movementRadius: 40, mouseTimeout: 2000, blinkFrequency: 3000, blinkDuration: 150, smoothing: 0.15 }; // Status let modes = { auto: true, mouse: false, camera: false }; let state = { modelsLoaded: false, cameraActive: false, showPreview: false, mouseInactivityTimer: null, movementIntervalId: null, detectionIntervalId: null, currentLeftX: 0, currentLeftY: 0, currentRightX: 0, currentRightY: 0, lastFacePosition: null, noFaceDetectedCount: 0, noMovementDetectedCount: 0, faceDetectionThreshold: 5, // Anzahl der Frames ohne Gesicht bevor Rückkehr zur Mitte movementDetectionThreshold: 8, // Anzahl der Frames ohne Bewegung bevor Rückkehr zur Mitte isReturningToCenter: false }; // Panel Toggle function togglePanel() { document.getElementById(‚controlPanel‘).classList.toggle(‚collapsed‘); } // Status-Nachricht anzeigen function showMessage(message, type = ’success‘) { const msg = document.getElementById(’statusMessage‘); msg.textContent = message; msg.className = `status-message ${type} show`; setTimeout(() => msg.classList.remove(’show‘), 3000); } // Status-Indikatoren aktualisieren function updateStatusIndicators() { document.getElementById(’statusAuto‘).className = `status-indicator ${modes.auto ? ‚active‘ : ‚inactive‘}`; document.getElementById(’statusMouse‘).className = `status-indicator ${modes.mouse ? ‚active‘ : ‚inactive‘}`; document.getElementById(’statusCamera‘).className = `status-indicator ${modes.camera ? ‚active‘ : ‚inactive‘}`; } // Modus-Auswahl document.getElementById(‚checkAuto‘).addEventListener(‚change‘, function(e) { modes.auto = e.target.checked; document.getElementById(‚modeAuto‘).classList.toggle(‚active‘, modes.auto); updateStatusIndicators(); if (modes.auto) { restartAutoMovement(); } else { stopAutoMovement(); } }); document.getElementById(‚checkMouse‘).addEventListener(‚change‘, function(e) { modes.mouse = e.target.checked; document.getElementById(‚modeMouse‘).classList.toggle(‚active‘, modes.mouse); updateStatusIndicators(); if (modes.mouse) { showMessage(‚Maus-Verfolgung aktiviert‘); } }); document.getElementById(‚checkCamera‘).addEventListener(‚change‘, async function(e) { modes.camera = e.target.checked; document.getElementById(‚modeCamera‘).classList.toggle(‚active‘, modes.camera); updateStatusIndicators(); if (modes.camera) { if (!state.modelsLoaded) { document.getElementById(‚cameraLoading‘).classList.remove(‚hidden‘); await loadModels(); document.getElementById(‚cameraLoading‘).classList.add(‚hidden‘); } await startCamera(); } else { stopCamera(); } }); // Slider Event Listeners document.getElementById(‚movementSpeed‘).addEventListener(‚input‘, function(e) { config.movementSpeed = parseInt(e.target.value); document.getElementById(’speedValue‘).textContent = e.target.value; updatePupilTransition(); }); document.getElementById(‚movementInterval‘).addEventListener(‚input‘, function(e) { config.movementInterval = parseInt(e.target.value); document.getElementById(‚intervalValue‘).textContent = e.target.value; if (modes.auto) restartAutoMovement(); }); document.getElementById(‚movementRadius‘).addEventListener(‚input‘, function(e) { config.movementRadius = parseInt(e.target.value); document.getElementById(‚radiusValue‘).textContent = e.target.value; }); document.getElementById(‚mouseTimeout‘).addEventListener(‚input‘, function(e) { config.mouseTimeout = parseInt(e.target.value); document.getElementById(‚mouseTimeoutValue‘).textContent = e.target.value; }); // Buttons document.getElementById(‚togglePreview‘).addEventListener(‚click‘, function() { state.showPreview = !state.showPreview; videoContainer.classList.toggle(‚hidden‘, !state.showPreview); this.classList.toggle(‚active‘, state.showPreview); this.textContent = state.showPreview ? ‚👁️ Preview ✓‘ : ‚👁️ Preview‘; }); document.getElementById(‚resetBtn‘).addEventListener(‚click‘, function() { resetPupils(); showMessage(‚Position zurückgesetzt‘); }); // Pupillen-Transition aktualisieren function updatePupilTransition() { const speed = config.movementSpeed / 1000; leftPupil.style.transition = `all ${speed}s ease-in-out`; rightPupil.style.transition = `all ${speed}s ease-in-out`; } // Auto-Bewegung function moveAutomatically() { const angle = Math.random() * Math.PI * 2; const distance = Math.random() * config.movementRadius; const x = Math.cos(angle) * distance; const y = Math.sin(angle) * distance; leftPupil.style.transition = `all ${config.movementSpeed / 1000}s ease-in-out`; rightPupil.style.transition = `all ${config.movementSpeed / 1000}s ease-in-out`; leftPupil.style.transform = `translate(calc(-50% + ${x}px), calc(-50% + ${y}px))`; rightPupil.style.transform = `translate(calc(-50% + ${x}px), calc(-50% + ${y}px))`; } function restartAutoMovement() { stopAutoMovement(); if (modes.auto) { moveAutomatically(); state.movementIntervalId = setInterval(moveAutomatically, config.movementInterval); } } function stopAutoMovement() { if (state.movementIntervalId) { clearInterval(state.movementIntervalId); state.movementIntervalId = null; } } // Maus-Verfolgung document.addEventListener(‚mousemove‘, function(e) { if (!modes.mouse && !modes.camera) return; // Bei Kamera-Modus: Maus hat niedrigere Priorität if (modes.camera && state.cameraActive) return; if (!modes.mouse) return; // Reset Rückkehr-Flag wenn Maus bewegt wird state.isReturningToCenter = false; trackPosition(e.clientX, e.clientY, ‚mouse‘); // Auto-Bewegung pausieren if (modes.auto) { stopAutoMovement(); if (state.mouseInactivityTimer) { clearTimeout(state.mouseInactivityTimer); } state.mouseInactivityTimer = setTimeout(() => { if (modes.auto && !state.isReturningToCenter) { returnToCenter(() => { if (modes.auto) { restartAutoMovement(); } }); } }, config.mouseTimeout); } }); // Position tracken (für Maus und Kamera) function trackPosition(targetX, targetY, source = ‚mouse‘) { const leftEyeRect = leftEye.getBoundingClientRect(); const rightEyeRect = rightEye.getBoundingClientRect(); const leftEyeCenterX = leftEyeRect.left + leftEyeRect.width / 2; const leftEyeCenterY = leftEyeRect.top + leftEyeRect.height / 2; const rightEyeCenterX = rightEyeRect.left + rightEyeRect.width / 2; const rightEyeCenterY = rightEyeRect.top + rightEyeRect.height / 2; const leftAngle = Math.atan2(targetY – leftEyeCenterY, targetX – leftEyeCenterX); const leftDistance = Math.sqrt( Math.pow(targetX – leftEyeCenterX, 2) + Math.pow(targetY – leftEyeCenterY, 2) ); const rightAngle = Math.atan2(targetY – rightEyeCenterY, targetX – rightEyeCenterX); const rightDistance = Math.sqrt( Math.pow(targetX – rightEyeCenterX, 2) + Math.pow(targetY – rightEyeCenterY, 2) ); const factor = source === ‚mouse‘ ? 0.8 : 0.5; const maxPupilMovement = config.movementRadius; const targetLeftX = Math.cos(leftAngle) * Math.min(leftDistance * factor, maxPupilMovement); const targetLeftY = Math.sin(leftAngle) * Math.min(leftDistance * factor, maxPupilMovement); const targetRightX = Math.cos(rightAngle) * Math.min(rightDistance * factor, maxPupilMovement); const targetRightY = Math.sin(rightAngle) * Math.min(rightDistance * factor, maxPupilMovement); if (source === ‚camera‘) { // Smoothing für Kamera state.currentLeftX += (targetLeftX – state.currentLeftX) * config.smoothing; state.currentLeftY += (targetLeftY – state.currentLeftY) * config.smoothing; state.currentRightX += (targetRightX – state.currentRightX) * config.smoothing; state.currentRightY += (targetRightY – state.currentRightY) * config.smoothing; leftPupil.style.transition = ‚all 0.1s ease-out‘; rightPupil.style.transition = ‚all 0.1s ease-out‘; leftPupil.style.transform = `translate(calc(-50% + ${state.currentLeftX}px), calc(-50% + ${state.currentLeftY}px))`; rightPupil.style.transform = `translate(calc(-50% + ${state.currentRightX}px), calc(-50% + ${state.currentRightY}px))`; } else { // Direkt für Maus leftPupil.style.transition = ‚all 0.1s ease-out‘; rightPupil.style.transition = ‚all 0.1s ease-out‘; leftPupil.style.transform = `translate(calc(-50% + ${targetLeftX}px), calc(-50% + ${targetLeftY}px))`; rightPupil.style.transform = `translate(calc(-50% + ${targetRightX}px), calc(-50% + ${targetRightY}px))`; } } // Pupillen zurücksetzen function resetPupils() { leftPupil.style.transform = ‚translate(-50%, -50%)‘; rightPupil.style.transform = ‚translate(-50%, -50%)‘; state.currentLeftX = state.currentLeftY = state.currentRightX = state.currentRightY = 0; } // Sanfte Rückkehr zur Mitte function returnToCenter(callback) { state.isReturningToCenter = true; // Setze sanfte Transition für Rückkehr leftPupil.style.transition = ‚all 0.8s ease-in-out‘; rightPupil.style.transition = ‚all 0.8s ease-in-out‘; // Bewege zur Mitte leftPupil.style.transform = ‚translate(-50%, -50%)‘; rightPupil.style.transform = ‚translate(-50%, -50%)‘; state.currentLeftX = state.currentLeftY = state.currentRightX = state.currentRightY = 0; // Warte bis Transition fertig ist, dann callback setTimeout(() => { state.isReturningToCenter = false; if (callback) callback(); }, 800); } // Face-API Modelle laden async function loadModels() { if (state.modelsLoaded) return; try { const MODEL_URL = ‚https://cdn.jsdelivr.net/npm/@vladmandic/face-api@1.7.12/model/‘; await Promise.all([ faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL), faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL) ]); state.modelsLoaded = true; showMessage(‚Face-API Modelle geladen‘); } catch (error) { console.error(‚Fehler beim Laden der Modelle:‘, error); showMessage(‚Fehler beim Laden der Face-API‘, ‚error‘); modes.camera = false; document.getElementById(‚checkCamera‘).checked = false; document.getElementById(‚modeCamera‘).classList.remove(‚active‘); updateStatusIndicators(); } } // Kamera starten async function startCamera() { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480, facingMode: ‚user‘ } }); video.srcObject = stream; video.addEventListener(‚loadeddata‘, () => { canvas.width = video.videoWidth; canvas.height = video.videoHeight; state.cameraActive = true; showMessage(‚Kamera-Tracking aktiviert‘); startDetection(); }); } catch (error) { console.error(‚Kamera-Fehler:‘, error); showMessage(‚Kamera-Zugriff verweigert‘, ‚error‘); modes.camera = false; document.getElementById(‚checkCamera‘).checked = false; document.getElementById(‚modeCamera‘).classList.remove(‚active‘); updateStatusIndicators(); } } // Kamera stoppen function stopCamera() { if (video.srcObject) { video.srcObject.getTracks().forEach(track => track.stop()); video.srcObject = null; } if (state.detectionIntervalId) { clearInterval(state.detectionIntervalId); state.detectionIntervalId = null; } state.cameraActive = false; showMessage(‚Kamera-Tracking deaktiviert‘); } // Gesichtserkennung function startDetection() { state.detectionIntervalId = setInterval(async () => { if (!state.cameraActive || !modes.camera) return; const detections = await faceapi .detectSingleFace(video, new faceapi.TinyFaceDetectorOptions()) .withFaceLandmarks(); if (detections) { // Gesicht erkannt – reset counter state.noFaceDetectedCount = 0; if (state.showPreview) { const ctx = canvas.getContext(‚2d‘); ctx.clearRect(0, 0, canvas.width, canvas.height); faceapi.draw.drawFaceLandmarks(canvas, detections); } // Mehrere Gesichtspunkte kombinieren für stabileres Tracking const landmarks = detections.landmarks; // Hole verschiedene Referenzpunkte const nose = landmarks.getNose()[3]; // Nasenspitze const leftEyePoints = landmarks.getLeftEye(); const rightEyePoints = landmarks.getRightEye(); const jawline = landmarks.getJawOutline(); // Berechne Augenmittelpunkte const leftEyeCenter = { x: leftEyePoints.reduce((sum, p) => sum + p.x, 0) / leftEyePoints.length, y: leftEyePoints.reduce((sum, p) => sum + p.y, 0) / leftEyePoints.length }; const rightEyeCenter = { x: rightEyePoints.reduce((sum, p) => sum + p.x, 0) / rightEyePoints.length, y: rightEyePoints.reduce((sum, p) => sum + p.y, 0) / rightEyePoints.length }; // Berechne Punkt zwischen den Augen const eyesMidpoint = { x: (leftEyeCenter.x + rightEyeCenter.x) / 2, y: (leftEyeCenter.y + rightEyeCenter.y) / 2 }; // Berechne Gesichtszentrum (Kinn-Mitte) const chinCenter = jawline[8]; // Mittlerer Punkt der Kinnlinie // Gewichteter Durchschnitt mehrerer Punkte für stabileres Tracking // 40% Augenmitte, 30% Nase, 20% Kinn, 10% Gesamtgesicht const allPoints = landmarks.positions; const faceCenter = { x: allPoints.reduce((sum, p) => sum + p.x, 0) / allPoints.length, y: allPoints.reduce((sum, p) => sum + p.y, 0) / allPoints.length }; const combinedX = ( eyesMidpoint.x * 0.4 + nose.x * 0.3 + chinCenter.x * 0.2 + faceCenter.x * 0.1 ); const combinedY = ( eyesMidpoint.y * 0.4 + nose.y * 0.3 + chinCenter.y * 0.2 + faceCenter.y * 0.1 ); // Speichere aktuelle Position const currentPosition = { x: combinedX, y: combinedY }; // Prüfe ob signifikante Bewegung erkannt wurde let hasMovement = true; if (state.lastFacePosition) { const movementThreshold = 8; // Erhöhter Schwellwert für deutlichere Bewegungen const deltaX = Math.abs(currentPosition.x – state.lastFacePosition.x); const deltaY = Math.abs(currentPosition.y – state.lastFacePosition.y); hasMovement = (deltaX > movementThreshold || deltaY > movementThreshold); } state.lastFacePosition = currentPosition; // Wenn Bewegung erkannt if (hasMovement) { // Reset Bewegungs-Counter state.noMovementDetectedCount = 0; // Stoppe Auto-Bewegung wenn sie läuft if (state.movementIntervalId) { stopAutoMovement(); } // Reset Rückkehr-Flag state.isReturningToCenter = false; // Konvertiere zu Bildschirmkoordinaten const videoRect = video.getBoundingClientRect(); const relX = 1 – (combinedX / video.videoWidth); // Gespiegelt const relY = combinedY / video.videoHeight; const screenX = relX * window.innerWidth; const screenY = relY * window.innerHeight; trackPosition(screenX, screenY, ‚camera‘); } else { // Keine signifikante Bewegung mehr state.noMovementDetectedCount++; // Nach mehreren Frames ohne Bewegung – zurück zur Mitte if (state.noMovementDetectedCount >= state.movementDetectionThreshold) { if (!state.isReturningToCenter && !state.movementIntervalId) { returnToCenter(() => { if (modes.auto) { restartAutoMovement(); } }); } } } } else { // Kein Gesicht erkannt state.noFaceDetectedCount++; state.noMovementDetectedCount = 0; // Reset, da kein Gesicht = automatisch keine Bewegung // Schnellere Rückkehr wenn Gesicht komplett weg ist if (state.noFaceDetectedCount >= state.faceDetectionThreshold) { if (!state.isReturningToCenter && !state.movementIntervalId) { returnToCenter(() => { if (modes.auto) { restartAutoMovement(); } }); } state.lastFacePosition = null; // Reset Position } } }, 100); } // Zwinkern function blink() { const blinkBoth = Math.random() > 0.4; if (blinkBoth) { leftEye.classList.add(‚blinking‘); rightEye.classList.add(‚blinking‘); setTimeout(() => { leftEye.classList.remove(‚blinking‘); rightEye.classList.remove(‚blinking‘); }, config.blinkDuration); } else { const eyeToBlink = Math.random() > 0.5 ? leftEye : rightEye; eyeToBlink.classList.add(‚blinking‘); setTimeout(() => eyeToBlink.classList.remove(‚blinking‘), config.blinkDuration); } } // Zwinkern-Intervall setInterval(() => { const variation = (Math.random() – 0.5) * config.blinkFrequency * 0.4; setTimeout(blink, variation); }, config.blinkFrequency); // Initialisierung function initializeWhenReady() { if (typeof faceapi !== ‚undefined‘) { // Face-API geladen, aber noch nicht die Modelle laden // (werden nur geladen wenn Kamera-Modus aktiviert wird) updatePupilTransition(); restartAutoMovement(); setTimeout(blink, 1000); } else { setTimeout(initializeWhenReady, 100); } } if (document.readyState === ‚loading‘) { document.addEventListener(‚DOMContentLoaded‘, initializeWhenReady); } else { initializeWhenReady(); }