Following Eyes

A pair of eyes tracks the pointer anywhere on the page and blinks now and then, adding a little personality.

  • JS
  • Cursor
  • Pointer
  • Added
They are watching

AI prompt

Create a pair of eyes that follow the pointer in React, no libraries. Each eye is a white circle with overflow hidden and a dark pupil inside. Listen to pointermove on window; for each eye, compute the angle from the eye centre to the pointer with Math.atan2 and a distance clamped to the eye radius minus the pupil radius, then set the pupil translate to (cos × d, sin × d). Smooth it with a 120ms transform transition. Add a blink: a lid that scales on the y axis from 0 to 1 and back in 160ms, triggered by a CSS animation every 4s at the 94% mark so it feels occasional. Remove the listener on unmount. Under prefers-reduced-motion, keep the pupils tracking but drop the blink and the transition. The hint reads "They are watching".

Source

Demo.tsx
'use client'

import { useEffect, useRef } from 'react'
import s from './demo.module.css'

const EYE = 28
const PUPIL = 11

export default function FollowingEyes({ labels }: { labels: Record<string, string> }) {
  const eyes = useRef<(HTMLSpanElement | null)[]>([])

  useEffect(() => {
    const onMove = (e: PointerEvent) => {
      for (const eye of eyes.current) {
        if (!eye) continue
        const r = eye.getBoundingClientRect()
        const dx = e.clientX - (r.left + r.width / 2)
        const dy = e.clientY - (r.top + r.height / 2)
        const a = Math.atan2(dy, dx)
        const d = Math.min(Math.hypot(dx, dy) / 8, EYE - PUPIL - 4)
        ;(eye.firstElementChild as HTMLElement).style.translate = `${Math.cos(a) * d}px ${Math.sin(a) * d}px`
      }
    }
    window.addEventListener('pointermove', onMove)
    return () => window.removeEventListener('pointermove', onMove)
  }, [])

  return (
    <div className={s.wrap}>
      <div className={s.face} aria-hidden="true">
        {[0, 1].map((i) => (
          <span key={i} ref={(el) => void (eyes.current[i] = el)} className={s.eye}>
            <span className={s.pupil} />
            <span className={s.lid} />
          </span>
        ))}
      </div>
      <span className={s.hint}>{labels.hint}</span>
    </div>
  )
}
demo.module.css
.wrap {
  display: grid;
  justify-items: center;
  gap: 16px;
}

.face {
  display: flex;
  gap: 14px;
}

.eye {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  overflow: hidden;
  border-radius: 50%;
  background: var(--stage-ink);
}

.pupil {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--stage-bg);
  box-shadow: inset 4px -4px 0 -1px var(--stage-accent);
  transition: translate 120ms var(--ease-out);
}

.lid {
  position: absolute;
  inset: 0;
  background: var(--stage-accent);
  transform: scaleY(0);
  transform-origin: top;
  animation: blink 4s infinite;
}

@keyframes blink {
  0%, 92%, 100% { transform: scaleY(0); }
  94% { transform: scaleY(1); }
}

.hint {
  font: 500 var(--text-xs) / 1 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--stage-muted);
}

@media (prefers-reduced-motion: reduce) {
  .lid { animation: none; }
  .pupil { transition: none; }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.