-
-
Save BlazeIsClone/53cf77e8f81d7fba4144a4fd0f0406ea to your computer and use it in GitHub Desktop.
Interactive Javascript Cursor in React
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
* { | |
cursor: none !important; | |
} | |
.show-cursor { | |
cursor: auto !important; | |
} |
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, { useContext, useState } from "react"; | |
import useMousePosition from "./useMousePosition"; | |
import { CursorContext } from "./CursorContextProvider"; | |
import isTouchDevice from "./isTouchDevice"; | |
const Cursor = () => { | |
if (isTouchDevice) { | |
return null; | |
} | |
const { clientX, clientY } = useMousePosition(); | |
const [cursor] = useContext(CursorContext); | |
const [isVisible, setIsVisible] = useState(false); | |
useEffect(() => { | |
const handleMouseEnter = () => setIsVisible(true); | |
const handleMouseLeave = () => setIsVisible(false); | |
document.body.addEventListener("mouseenter", handleMouseEnter); | |
document.body.addEventListener("mouseleave", handleMouseLeave); | |
return () => { | |
document.body.removeEventListener("mouseenter", handleMouseEnter); | |
document.body.removeEventListener("mouseleave", handleMouseLeave); | |
}; | |
}, []); | |
return ( | |
<div | |
style={{ | |
position: "fixed", | |
top: 0, | |
bottom: 0, | |
left: 0, | |
right: 0, | |
zIndex: 9999, | |
pointerEvents: "none" | |
}} | |
> | |
<svg | |
width={50} | |
height={50} | |
viewBox="0 0 50 50" | |
style={{ | |
position: "absolute", | |
pointerEvents: "none", | |
left: clientX, | |
top: clientY, | |
transform: `translate(-50%, -50%) scale(${cursor.active ? 2.5 : 1})`, | |
stroke: cursor.active ? "black" : "white", | |
strokeWidth: 1, | |
fill: cursor.active ? "rgba(255,255,255,.5)" : "black", | |
transition: "transform .2s ease-in-out", | |
// TODO: extra check on clientX needed here | |
// because mouseleave event not always firing | |
// when slowly exiting left side of browser | |
opacity: isVisible && clientX > 1 ? 1 : 0, | |
}} | |
> | |
<circle | |
cx="25" | |
cy="25" | |
r="8" | |
/> | |
</svg> | |
</div> | |
); | |
}; |
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, { createContext, useState } from "react"; | |
export const CursorContext = createContext(); | |
const CursorContextProvider = () => { | |
const [cursor, setCursor] = useState({ active: false }); | |
return ( | |
<CursorContext.Provider value={[cursor, setCursor]}> | |
{children} | |
</CursorContext.Provider> | |
); | |
}; | |
export default CursorContextProvider; |
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
const Button = () => { | |
const cursorHandlers = useCursorHandlers(); | |
return ( | |
<button type="button" style={{ padding: "1rem" }} {...cursorHandlers}> | |
HOVER ME | |
</button> | |
) | |
}; | |
const Select = () => { | |
return ( | |
<select class="show-cursor"> | |
{Array(5).fill().map((item, idx) => | |
<option value={idx}>Option {idx + 1}</option> | |
)} | |
</select> | |
) | |
}; | |
const App = () => { | |
return ( | |
<CursorContextProvider> | |
<Cursor /> | |
<Button /> | |
<br /> | |
<Select /> | |
</CursorContextProvider> | |
); | |
}; |
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
const isTouchDevice = | |
"ontouchstart" in window | |
|| navigator.MaxTouchPoints > 0 | |
|| navigator.msMaxTouchPoints > 0; | |
export default isTouchDevice; |
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 { useContext, useCallback } from "react"; | |
import { CursorContext } from "./CursorContextProvider"; | |
import isTouchDevice from "./isTouchDevice"; | |
const useCursorHandlers = (options = {}) => { | |
if (isTouchDevice) { | |
return options; | |
} | |
const [, setCursor] = useContext(CursorContext); | |
const toggleCursor = () => { | |
setCursor(({ active }) => ({ active: !active })); | |
}; | |
const onMouseEnter = useCallback(event => { | |
if (options.onMouseEnter) { | |
options.onMouseEnter(event); | |
} | |
toggleCursor(); | |
}); | |
const onMouseLeave = useCallback(event => { | |
if (options.onMouseLeave) { | |
options.onMouseLeave(event); | |
} | |
toggleCursor(); | |
}); | |
return { onMouseEnter, onMouseLeave }; | |
}; |
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 { useState, useEffect } from "react"; | |
const useMousePosition = () => { | |
const [position, setPosition] = useState({ | |
clientX: 0, | |
clientY: 0, | |
}); | |
const updatePosition = event => { | |
const { pageX, pageY, clientX, clientY } = event; | |
setPosition({ | |
clientX, | |
clientY, | |
}); | |
}; | |
useEffect(() => { | |
document.addEventListener("mousemove", updatePosition, false); | |
document.addEventListener("mouseenter", updatePosition, false); | |
return () => { | |
document.removeEventListener("mousemove", updatePosition); | |
document.removeEventListener("mouseenter", updatePosition); | |
}; | |
}, []); | |
return position; | |
}; | |
export default useMousePosition; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment