ArctisDocs
Search (Ctrl+K)

Flair Cursor Follower

Hover your Cursor

Installation

bash
pnpm add gsap

Usage

tsx
1import FlairCursorFollower from './FlairCursorFollower'
2
3// usage
4<FlairCursorFollower />

Code

tsx
1"use client";
2
3import React, { useEffect, useMemo, useRef } from "react";
4import { gsap } from "gsap";
5
6export const FlairCursorFollower: React.FC = () => {
7 const indexRef = useRef(0);
8 const nodesRef = useRef<HTMLImageElement[]>([]);
9
10 // βœ… gate distance
11 const lastSpawnPosRef = useRef<{ x: number; y: number } | null>(null);
12 const gapRef = useRef(100); // πŸ‘ˆ atur: 80–140 biasanya enak
13
14 const imgList = useMemo(
15 () => [
16 "https://assets.codepen.io/16327/Revised+Flair.png",
17 "https://assets.codepen.io/16327/Revised+Flair-1.png",
18 "https://assets.codepen.io/16327/Revised+Flair-2.png",
19 "https://assets.codepen.io/16327/Revised+Flair-3.png",
20 "https://assets.codepen.io/16327/Revised+Flair-4.png",
21 "https://assets.codepen.io/16327/Revised+Flair-5.png",
22 "https://assets.codepen.io/16327/Revised+Flair-6.png",
23 "https://assets.codepen.io/16327/Revised+Flair-7.png",
24 "https://assets.codepen.io/16327/Revised+Flair-8.png",
25 "https://assets.codepen.io/16327/Revised+Flair.png",
26 "https://assets.codepen.io/16327/Revised+Flair-1.png",
27 "https://assets.codepen.io/16327/Revised+Flair-2.png",
28 "https://assets.codepen.io/16327/Revised+Flair-3.png",
29 "https://assets.codepen.io/16327/Revised+Flair-4.png",
30 "https://assets.codepen.io/16327/Revised+Flair-5.png",
31 "https://assets.codepen.io/16327/Revised+Flair-6.png",
32 "https://assets.codepen.io/16327/Revised+Flair-7.png",
33 "https://assets.codepen.io/16327/Revised+Flair-8.png",
34 ],
35 []
36 );
37
38 useEffect(() => {
39 nodesRef.current = Array.from(
40 document.querySelectorAll<HTMLImageElement>(".flair")
41 );
42 if (nodesRef.current.length === 0) return;
43
44 gsap.set(nodesRef.current, {
45 opacity: 0,
46 xPercent: -50,
47 yPercent: -50,
48 });
49
50 const spawn = (x: number, y: number) => {
51 const nodes = nodesRef.current;
52 if (nodes.length === 0) return;
53
54 const i = indexRef.current % nodes.length;
55 const img = nodes[i];
56 if (!img) return;
57 indexRef.current += 1;
58
59 gsap.killTweensOf(img);
60
61 gsap.set(img, {
62 opacity: 1,
63 left: x,
64 top: y,
65 rotation: 0,
66 scale: 1,
67 y: 0,
68 position: "fixed",
69 });
70
71 gsap
72 .timeline()
73 .fromTo(
74 img,
75 { scale: 0.6 },
76 { scale: 1, duration: 0.15, ease: "power2.out" }
77 )
78 .to(img, { rotation: gsap.utils.random(-360, 360), duration: 0.6 }, "<")
79 .to(
80 img,
81 { y: window.innerHeight * 1.2, duration: 1, ease: "power2.in" },
82 0
83 )
84 .to(img, { opacity: 0, duration: 0.4 }, 0.6);
85 };
86
87 const onMove = (e: MouseEvent) => {
88 const x = e.clientX;
89 const y = e.clientY;
90
91 // init pos pertama kali
92 if (!lastSpawnPosRef.current) {
93 lastSpawnPosRef.current = { x, y };
94 return;
95 }
96
97 const dx = x - lastSpawnPosRef.current.x;
98 const dy = y - lastSpawnPosRef.current.y;
99 const dist = Math.hypot(dx, dy);
100
101 // βœ… hanya spawn kalau gerak sudah cukup jauh
102 if (dist < gapRef.current) return;
103
104 spawn(x, y);
105 lastSpawnPosRef.current = { x, y };
106 };
107
108 window.addEventListener("mousemove", onMove);
109
110 return () => {
111 window.removeEventListener("mousemove", onMove);
112 for (const el of nodesRef.current) gsap.killTweensOf(el);
113 };
114 }, []);
115
116 return (
117 <div
118 className="pointer-events-none fixed inset-0 z-50 overflow-hidden"
119 aria-hidden="true"
120 >
121 {imgList.map((src, i) => (
122 <img
123 key={i}
124 src={src}
125 alt=""
126 className="flair"
127 style={{
128 position: "fixed",
129 opacity: 0,
130 width: "50px",
131 left: 0,
132 top: 0,
133 pointerEvents: "none",
134 }}
135 />
136 ))}
137 </div>
138 );
139};
140
141/**
142 Arctis UI Component β€” <FlairCursorFollower>
143
144 Created with πŸ’› by Ranaufal Muha
145 https://ranaufalmuha.com
146
147 Hi! Thank you for using this component.
148 You’re free to copy, modify, or use it in any project you like.
149
150 If possible, please keep this small header as appreciation.
151 It helps others know where the component came from ❀️
152
153 Usage:
154 import { FlairCursorFollower } from "@arctis/ui";
155
156 // usage
157 <FlairCursorFollower />
158
159 */