Created
March 16, 2020 19:48
-
-
Save stephenscaff/1cb614dbfd65afe644d584dc7104744f to your computer and use it in GitHub Desktop.
Animated custom cursor in React, using hooks and a functional component.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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