Animation-heavy personal portfolio on Next.js: a custom cursor, a page-transition loader, and canvas backgrounds.
canvas-animationscss-modulesdark-themehandcodednextjspersonal-websiteportfolioreact
1import React, { useEffect, useRef } from "react";
2import styles from "@styles/components/mouse.module.css";
3
4const Mouse = () => {
5 const cursorRef = useRef(null);
6 const circleRef = useRef(null);
7 const mousePos = useRef({ x: 0, y: 0 });
8
9 const isInteractiveRef = useRef(false);
10
11 useEffect(() => {
12 const onMouseMove = (e) => {
13 mousePos.current = { x: e.clientX, y: e.clientY };
14 };
15
16 const onMouseOver = (e) => {
17 const target = e.target;
18 isInteractiveRef.current =
19 target.tagName === "BUTTON" ||
20 target.tagName === "A" ||
21 !!target.closest("a, button") ||
22 (target.classList && target.classList.contains("mouse-activate"));
23 if (circleRef.current) {
24 circleRef.current.classList.toggle(
25 "activated",
26 isInteractiveRef.current
27 );
28 }
29 };
30
31 document.addEventListener("mousemove", onMouseMove, { passive: true });
32 document.addEventListener("mouseover", onMouseOver, { passive: true });
33
34 let animationFrameId;
35
36 const animate = () => {
37 const { x, y } = mousePos.current;
38
39 if (cursorRef.current)
40 cursorRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`;
41 if (circleRef.current)
42 circleRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`;
43
44 animationFrameId = requestAnimationFrame(animate);
45 };
46
47 animate();
48
49 return () => {
50 document.removeEventListener("mousemove", onMouseMove);
51 document.removeEventListener("mouseover", onMouseOver);
52 cancelAnimationFrame(animationFrameId);
53 };
54 }, []);
55
56 return (
57 <>
58 <div ref={cursorRef} className={styles.pageCursor}>
59 <svg
60 width="16"
61 height="16"
62 viewBox="0 0 16 16"
63 fill="#ff2d2d"
64 fillOpacity="0.9"
65 xmlns="http://www.w3.org/2000/svg"
66 >
67 <circle cx="8" cy="8" r="8" />
68 </svg>
69 </div>
70 <div ref={circleRef} className={styles.pageCursorCircle} />
71 </>
72 );
73};
74
75export default Mouse;