十字准星光标

两条细线横跨整个区域并在指针处交汇,同时实时显示坐标,像设计工具里的标尺。

  • JS
  • 光标
  • 跟随指针
  • 收录于
移入区域

AI 提示词

用 React 在限定区域内制作十字准星光标,不使用第三方库。区域内用 cursor: none 隐藏原生光标。渲染一条满宽的水平细线和一条满高的垂直细线(1px,60% 不透明度的主色),以及一个小的等宽字体坐标标签。pointermove 时读取指针相对区域的位置,在 requestAnimationFrame 中通过 ref 直接设置细线和标签的 translate,并把整数像素的“x, y”写入标签。pointerenter 时显示,pointerleave 时以 150ms 的 opacity 过渡淡出。标签靠近右侧和底部边缘时翻转偏移方向,避免溢出。细线设为 aria-hidden。指针在区域外时显示居中的提示文字。提示文字为“移入区域”。

源代码

Demo.tsx
'use client'

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

export default function CrosshairCursor({ labels }: { labels: Record<string, string> }) {
  const area = useRef<HTMLDivElement>(null)
  const h = useRef<HTMLSpanElement>(null)
  const v = useRef<HTMLSpanElement>(null)
  const tag = useRef<HTMLSpanElement>(null)
  const raf = useRef(0)

  const onMove = (e: React.PointerEvent<HTMLDivElement>) => {
    const r = e.currentTarget.getBoundingClientRect()
    const x = Math.round(e.clientX - r.left)
    const y = Math.round(e.clientY - r.top)
    cancelAnimationFrame(raf.current)
    raf.current = requestAnimationFrame(() => {
      h.current!.style.translate = `0 ${y}px`
      v.current!.style.translate = `${x}px 0`
      const t = tag.current!
      const dx = x > r.width - 80 ? -t.offsetWidth - 8 : 8
      const dy = y > r.height - 30 ? -t.offsetHeight - 8 : 8
      t.style.translate = `${x + dx}px ${y + dy}px`
      t.textContent = `${x}, ${y}`
    })
  }

  return (
    <div
      ref={area}
      className={s.area}
      onPointerMove={onMove}
      onPointerEnter={() => area.current?.setAttribute('data-on', '')}
      onPointerLeave={() => area.current?.removeAttribute('data-on')}
    >
      <span className={s.hint}>{labels.hint}</span>
      <span ref={h} className={s.h} aria-hidden="true" />
      <span ref={v} className={s.v} aria-hidden="true" />
      <span ref={tag} className={s.tag} aria-hidden="true" />
    </div>
  )
}
demo.module.css
.area {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  cursor: none;
  background:
    repeating-linear-gradient(90deg, var(--stage-surface) 0 1px, transparent 1px 24px),
    repeating-linear-gradient(0deg, var(--stage-surface) 0 1px, transparent 1px 24px),
    var(--stage-bg);
}

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

.h, .v, .tag {
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 150ms;
}

.h { width: 100%; height: 1px; background: color-mix(in oklch, var(--stage-accent) 60%, transparent); }
.v { width: 1px; height: 100%; background: color-mix(in oklch, var(--stage-accent) 60%, transparent); }

.tag {
  padding: 3px 6px;
  border-radius: 4px;
  background: var(--stage-accent);
  color: var(--stage-accent-ink);
  font: 600 0.6875rem / 1 var(--font-mono);
  white-space: nowrap;
}

.area[data-on] .h,
.area[data-on] .v,
.area[data-on] .tag { opacity: 1; }
.area[data-on] .hint { opacity: 0; }

投稿创意

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

你想投稿的是?

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