跟随的眼睛

一双眼睛在页面任何地方都会跟着指针转动,时不时眨一下,增添一点个性。

  • JS
  • 光标
  • 跟随指针
  • 收录于
它们在看着你

AI 提示词

用 React 制作跟随指针的一双眼睛,不使用第三方库。每只眼睛是一个 overflow hidden 的白色圆,里面有一个深色瞳孔。在 window 上监听 pointermove;对每只眼睛,用 Math.atan2 计算眼睛中心到指针的角度,并把距离限制在“眼睛半径减瞳孔半径”以内,然后把瞳孔的 translate 设为 (cos × d, sin × d)。用 120ms 的 transform 过渡让它平滑。加上眨眼:一个眼皮在 160ms 内沿 y 轴从 0 放大到 1 再缩回,通过每 4s 一次、在 94% 处触发的 CSS 动画实现,让眨眼显得偶尔发生。组件卸载时移除监听。在 prefers-reduced-motion 下保留瞳孔跟随,去掉眨眼和过渡。提示文字为“它们在看着你”。

源代码

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; }
}

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。