Skip to content

Instantly share code, notes, and snippets.

@stephenscaff
Created March 16, 2020 19:48
Show Gist options
  • Save stephenscaff/1cb614dbfd65afe644d584dc7104744f to your computer and use it in GitHub Desktop.
Save stephenscaff/1cb614dbfd65afe644d584dc7104744f to your computer and use it in GitHub Desktop.
Animated custom cursor in React, using hooks and a functional component.
import React, { useState, useEffect, useRef } from "react";
function Cursor() {
const cursorDotOutline = useRef();
const cursorDot = useRef();
const requestRef = useRef();
const previousTimeRef = useRef();
let [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const [width, setWidth] = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);
let cursorVisible = useState(false);
let cursorEnlarged = useState(false);
/**
* Mouse Moves
*/
const onMouseMove = event => {
const { pageX: x, pageY: y } = event;
setMousePosition({ x, y });
positionDot(event);
};
const onMouseEnter = () => {
cursorVisible.current = true;
toggleCursorVisibility();
};
const onMouseLeave = () => {
cursorVisible.current = false;
toggleCursorVisibility();
};
const onMouseDown = () => {
cursorEnlarged.current = true;
toggleCursorSize();
};
const onMouseUp = () => {
cursorEnlarged.current = false;
toggleCursorSize();
};
const onResize = event => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
};
/**
* Hooks
*/
useEffect(() => {
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseenter", onMouseEnter);
document.addEventListener("mouseleave", onMouseLeave);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("mouseup", onMouseUp);
window.addEventListener("resize", onResize);
requestRef.current = requestAnimationFrame(animateDotOutline);
handleLinks();
return () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseenter", onMouseEnter);
document.removeEventListener("mouseleave", onMouseLeave);
document.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("mouseup", onMouseUp);
window.removeEventListener("resize", onResize);
cancelAnimationFrame(requestRef.current);
};
}, []);
let { x, y } = mousePosition;
const winDimensions = { width, height };
let endX = winDimensions.width / 2;
let endY = winDimensions.height / 2;
/**
* Position Dot (cursor)
* @param {event}
*/
function positionDot(e) {
cursorVisible.current = true;
toggleCursorVisibility();
// Position the dot
endX = e.pageX;
endY = e.pageY;
cursorDot.current.style.top = endY + "px";
cursorDot.current.style.left = endX + "px";
}
/**
* Toggle Cursor Visiblity
*/
function toggleCursorVisibility() {
if (cursorVisible.current) {
cursorDot.current.style.opacity = 1;
cursorDotOutline.current.style.opacity = 1;
} else {
cursorDot.current.style.opacity = 0;
cursorDotOutline.current.style.opacity = 0;
}
}
/**
* Toggle Cursor Size
*/
function toggleCursorSize() {
if (cursorEnlarged.current) {
cursorDot.current.style.transform = "translate(-50%, -50%) scale(0.75)";
cursorDotOutline.current.style.transform =
"translate(-50%, -50%) scale(1.5)";
} else {
cursorDot.current.style.transform = "translate(-50%, -50%) scale(1)";
cursorDotOutline.current.style.transform =
"translate(-50%, -50%) scale(1)";
}
}
/**
* Handle LInks
* Applies mouseover/out hooks on all links
* to trigger cursor animation
*/
function handleLinks() {
document.querySelectorAll("a").forEach(el => {
el.addEventListener("mouseover", () => {
cursorEnlarged.current = true;
toggleCursorSize();
});
el.addEventListener("mouseout", () => {
cursorEnlarged.current = false;
toggleCursorSize();
});
});
}
/**
* Animate Dot Outline
* Aniamtes cursor outline with trailing effect.
* @param {number} time
*/
const animateDotOutline = time => {
if (previousTimeRef.current !== undefined) {
x += (endX - x) / 8;
y += (endY - y) / 8;
cursorDotOutline.current.style.top = y + "px";
cursorDotOutline.current.style.left = x + "px";
}
previousTimeRef.current = time;
requestRef.current = requestAnimationFrame(animateDotOutline);
};
return (
<>
<div ref={cursorDotOutline} id="cursor-dot-outline" />
<div ref={cursorDot} id="cursor-dot" />
</>
);
}
export default Cursor;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment