Animations/Eye Tracking Tilt

Eye Tracking Tilt

Cards avec yeux qui suivent le curseur + tilt 3D interactif. Effet immersif type hero section.

hoverJS3Dherointeractive
Trigger: Mouse move
CSS requis: Non
Difficulte: hard

Preview live

Code

"use client";
import { useCallback, useRef } from "react";

export default function EyeTrackingTilt() {
  const sectionRef = useRef<HTMLElement>(null);
  const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
  const eyeRefs = useRef<(HTMLDivElement | null)[][]>([]);

  const handleMouseMove = useCallback((e: React.MouseEvent) => {
    const section = sectionRef.current;
    if (!section) return;
    const sRect = section.getBoundingClientRect();
    const mouseX = e.clientX - sRect.left;
    const mouseY = e.clientY - sRect.top;

    itemRefs.current.forEach((el, i) => {
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const elCX = rect.left + rect.width / 2 - sRect.left;
      const elCY = rect.top + rect.height / 2 - sRect.top;
      const amp = 30;

      const rotateY = ((mouseX - elCX) / (sRect.width / 2)) * amp;
      const rotateX = ((elCY - mouseY) / (sRect.height / 2)) * amp;
      const tx = ((mouseX - elCX) / sRect.width) * amp * 3;
      const ty = ((mouseY - elCY) / sRect.height) * amp * 3;
      el.style.transform = `perspective(800px) translate(${tx}px, ${ty}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;

      // Pupilles suivent le curseur
      eyeRefs.current[i]?.forEach((pupil) => {
        if (!pupil) return;
        const eyeRect = pupil.parentElement!.getBoundingClientRect();
        const dx = e.clientX - (eyeRect.left + eyeRect.width / 2);
        const dy = e.clientY - (eyeRect.top + eyeRect.height / 2);
        const angle = Math.atan2(dy, dx);
        const dist = Math.min(Math.sqrt(dx * dx + dy * dy), 20) / 4;
        pupil.style.transform = `translate(${Math.cos(angle) * dist}px, ${Math.sin(angle) * dist}px)`;
      });
    });
  }, []);

  return (
    <section ref={sectionRef} onMouseMove={handleMouseMove}>
      {/* Cards: background colore, borderRadius 16, boxShadow */}
      {/* Yeux: div 18x18 blanc rounded-full */}
      {/* Pupille: div 8x8 #1a1a2e rounded-full + glint 3x3 blanc */}
    </section>
  );
}

Instructions

Chaque card a une amplitude differente pour varier l'effet. Les yeux sont des div blanches avec une pupille sombre. Le glint (point blanc) donne du realisme. Utilisez transition: transform 0.08s sur les pupilles et 0.12s sur les cards.