cursor-tracking
ui
移动光标就可以玩了
src
cursor-tracking.tsx
TSX
"use client"
import { delay, wrap, MotionValue } from "motion";
import { usePointerPosition, useCursorState } from "motion-plus/react";
import { useTransform, useMotionValueEvent, AnimatePresence, motion } from "motion/react";
import { useRef, useState } from "react"
import { cn } from "@/lib/tailwind";
interface Target {
id: number;
index: number;
x: number;
y: number;
}
interface CursorTrackingProps {
trigger?: number;
width?: number;
height?: number;
className?: string;
images: string[];
}
export function CursorTracking({
trigger = 120,
width = 280,
height = 210,
className,
images,
}: CursorTrackingProps) {
const pointer = usePointerPosition();
const { zone } = useCursorState();
const sum = useRef<number>(0);
const id = useRef<number>(0);
const ix = useRef<number>(0);
const [ targets, setTargets ] = useState<Target[]>([]);
const distance: MotionValue<number> = useTransform(() => {
const x = pointer.x.get();
const y = pointer.y.get();
const deltaX = x - (pointer.x.getPrevious() ?? x);
const deltaY = y - (pointer.y.getPrevious() ?? y);
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
});
useMotionValueEvent(distance, "change", (latest) => {
sum.current += latest;
if (sum.current >= trigger) {
getImage(pointer.x.get(), pointer.y.get());
sum.current = 0;
}
})
function getImage(x: number, y: number) {
const one: Target = {
id: id.current++,
index: ix.current,
x: x - width / 2,
y: y - height / 2,
}
setTargets((prev) => [ ...prev, one ]);
ix.current = wrap(0, images.length, ix.current + 1);
delay(() => {
setTargets((prev) => prev.filter(img => img.id !== one.id));
}, 1.2);
}
return (
<div className={ cn("overflow-hidden relative", className) } data-cursor-zone="zone">
<AnimatePresence>
{
zone === "zone" && targets.map((img) => (
<motion.img
style={ {
position: "absolute",
left: `${ img.x }px`,
top: `${ img.y }px`,
width: width,
height: height,
willChange: "opacity, transform",
objectPosition: "center"
} }
initial={ { opacity: 0, scale: 0.5 } }
animate={ { opacity: 1, scale: 1 } }
transition={ { duration: 0.1 } }
exit={ { opacity: 0, scale: 0.5, transition: { duration: 0.3 } } }
src={ images[img.index] }
alt=""
key={ img.id }/>
))
}
</AnimatePresence>
</div>
)
}
page.tsx
TSX
import { CursorTracking } from "@/examples/cursor-tracking/cursor-tracking";
export default function Page() {
const images = [
"https://cdn.pixabay.com/photo/2016/05/03/16/10/morning-1369446_1280.jpg",
"https://cdn.pixabay.com/photo/2016/11/29/06/46/adult-1867889_1280.jpg",
"https://cdn.pixabay.com/photo/2019/03/01/14/54/girl-4028008_1280.jpg",
"https://cdn.pixabay.com/photo/2015/03/01/04/11/beauty-654309_1280.jpg",
"https://cdn.pixabay.com/photo/2015/03/19/02/54/woman-680300_1280.jpg",
"https://cdn.pixabay.com/photo/2016/04/10/12/27/nature-1319767_1280.jpg",
];
return (
<div className="h-screen relative bg-slate-100">
<CursorTracking className="h-full cursor-pointer"
images={ images }/>
<h1 className="absolute z-50 size-fit inset-0 m-auto pointer-events-none">
<span className="text-3xl md:text-6xl font-bold duration-300">
移动光标就可以玩了
</span>
</h1>
</div>
)
}