MousePosition

Example

hover: HYDRATE
hoverPixelX: HYDRATE
hoverPixelY: HYDRATE
hoverPercentX: HI MOM
hoverPercentY: HYDRATE
active: HYDRATE
activePixelX: HYDRATE
activePixelY: HYDRATE
activePercentX: HYDRATE
activePercentY: HYDRATE
target
touch-action: none; // to disable scroll on touch screen device

Example Source Code

Code

import { useEffect, useRef, useState } from "react";

export function useMousePosition<TElement extends HTMLElement>(
	touch?: boolean,
) {
	const refTargetElement = useRef<TElement>(null);
	const _local = {
		active: false,
		hover: {
			pixelX: 0,
			pixelY: 0,
			percentX: 0,
			percentY: 0,
		},
	};
	const [hover, setHover] = useState(false);
	const [hoverPixelX, setHoverPixelX] = useState(0);
	const [hoverPixelY, setHoverPixelY] = useState(0);
	const [hoverPercentX, setHoverPercentX] = useState(0);
	const [hoverPercentY, setHoverPercentY] = useState(0);
	const [active, setActive] = useState(false);
	const [activePixelX, setActivePixelX] = useState(0);
	const [activePixelY, setActivePixelY] = useState(0);
	const [activePercentX, setActivePercentX] = useState(0);
	const [activePercentY, setActivePercentY] = useState(0);

	useEffect(() => {
		const onMove = (clientX: number, clientY: number) => {
			if (refTargetElement.current) {
				const { left, top, width, height } =
					refTargetElement.current.getBoundingClientRect();
				const x = clientX - left;
				const y = clientY - top;
				const pX = Math.max(Math.min((x / width) * 100, 100), 0);
				const pY = Math.max(Math.min((y / height) * 100, 100), 0);
				setHoverPixelX(x);
				setHoverPixelY(y);
				setHoverPercentX(pX);
				setHoverPercentY(pY);
				_local.hover = {
					pixelX: x,
					pixelY: y,
					percentX: pX,
					percentY: pY,
				};
				if (_local.active) {
					setActivePixelX(x);
					setActivePixelY(y);
					setActivePercentX(pX);
					setActivePercentY(pY);
				}
			} else {
				const { innerWidth, innerHeight } = window;
				const pX = Math.max(
					Math.min((clientX / innerWidth) * 100, 100),
					0,
				);
				const pY = Math.max(
					Math.min((clientY / innerHeight) * 100, 100),
					0,
				);
				setHoverPixelX(clientX);
				setHoverPixelY(clientY);
				setHoverPercentX(pX);
				setHoverPercentY(pY);
				_local.hover = {
					pixelX: clientX,
					pixelY: clientY,
					percentX: pX,
					percentY: pY,
				};
				if (_local.active) {
					setActivePixelX(clientX);
					setActivePixelY(clientY);
					setActivePercentX(pX);
					setActivePercentY(pY);
				}
			}
		};

		const mouseEnter = () => {
			setHover(true);
		};
		const mouseLeave = () => {
			setHover(false);
			setActive(false);
			_local.active = false;
		};
		const mouseMove = (event: MouseEvent) => {
			const { clientX, clientY } = event;
			onMove(clientX, clientY);
		};
		const mouseDown = (event: MouseEvent) => {
			if (event.button === 0) {
				setActive(true);
				_local.active = true;
				setActivePixelX(_local.hover.pixelX);
				setActivePixelY(_local.hover.pixelY);
				setActivePercentX(_local.hover.percentX);
				setActivePercentY(_local.hover.percentY);
			}
		};
		const mouseUp = (event: MouseEvent) => {
			if (event.button === 0) {
				setActive(false);
				_local.active = false;
			}
		};

		const touchStart = () => {
			setHover(true);
			setActive(true);
			_local.active = true;
		};
		const touchEnd = () => {
			setHover(false);
			setActive(false);
			_local.active = false;
		};
		const touchMove = (event: TouchEvent) => {
			const { clientX, clientY } = event.touches[0];
			onMove(clientX, clientY);
		};

		const target = refTargetElement.current ?? document.documentElement;

		target.addEventListener("mouseenter", mouseEnter);
		target.addEventListener("mouseleave", mouseLeave);
		target.addEventListener("mousemove", mouseMove);
		target.addEventListener("mousedown", mouseDown);
		target.addEventListener("mouseup", mouseUp);

		if (touch) {
			target.addEventListener("touchstart", touchStart);
			target.addEventListener("touchend", touchEnd);
			target.addEventListener("touchmove", touchMove);
		}
		return () => {
			target.removeEventListener("mouseenter", mouseEnter);
			target.removeEventListener("mouseleave", mouseLeave);
			target.removeEventListener("mousemove", mouseMove);
			target.removeEventListener("mousedown", mouseDown);
			target.removeEventListener("mouseup", mouseUp);

			if (touch) {
				target.removeEventListener("touchstart", touchStart);
				target.removeEventListener("touchend", touchEnd);
				target.removeEventListener("touchmove", touchMove);
			}
		};
	}, []);

	return {
		refTargetElement,
		hover,
		hoverPixelX,
		hoverPixelY,
		hoverPercentX,
		hoverPercentY,
		active,
		activePixelX,
		activePixelY,
		activePercentX,
		activePercentY,
	};
}

Usage

import { useMousePosition } from "@hooks/ReactMousePosition";

export function MousePosition() {
	const {
		refTargetElement,
		hover,
		hoverPixelX,
		hoverPixelY,
		hoverPercentX,
		hoverPercentY,
		active,
		activePixelX,
		activePixelY,
		activePercentX,
		activePercentY,
	} = useMousePosition<HTMLDivElement>(true);

	return (
		<div>
			<div>
				<div>hover: {`${hover}`}</div>
				<div>hoverPixelX: {hoverPixelX.toFixed(0)}px</div>
				<div>hoverPixelY: {hoverPixelY.toFixed(0)}px</div>
				<div>hoverPercentX: {hoverPercentX.toFixed(0)}%</div>
				<div>hoverPercentY: {hoverPercentY.toFixed(0)}%</div>
				<div>active: {`${active}`}</div>
				<div>activePixelX: {activePixelX.toFixed(0)}px</div>
				<div>activePixelY: {activePixelY.toFixed(0)}px</div>
				<div>activePercentX: {activePercentX.toFixed(0)}%</div>
				<div>activePercentY: {activePercentY.toFixed(0)}%</div>
			</div>
			<div ref={refTargetElement}>content</div>
		</div>
	);
}