UNPKG

draggable-circular-menu

Version:

A customizable React circular menu component that supports dragging.

147 lines (124 loc) 4.59 kB
import React from "react"; export const useDraggable = ({ initialAngle }: { initialAngle: number }) => { const [node, setNode] = React.useState<HTMLElement>(); const [angle, setAngle] = React.useState<number>(initialAngle); const [{ dx, dy }, setOffset] = React.useState({ dx: 0, dy: 0, }); const ref = React.useCallback((node: HTMLElement) => { setNode(node); }, []); // Initial position React.useEffect(() => { if (!node) { return; } const width = node.getBoundingClientRect().width; const containerWidth = node.parentElement?.getBoundingClientRect().width; const radius = containerWidth ? containerWidth / 2 : 0; const center = radius - width / 2; const radian = ((angle + 0.25) % 1) * Math.PI * 2 - Math.PI; const dx = center + radius * Math.cos(radian); const dy = center + radius * Math.sin(radian); setOffset({ dx, dy }); }, [node]); const handleMouseDown = React.useCallback( (e: MouseEvent) => { if (!node) { return; } const startPos = { x: e.clientX - dx, y: e.clientY - dy, }; const width = node.getBoundingClientRect().width; const containerWidth = node.parentElement?.getBoundingClientRect().width; const radius = containerWidth ? containerWidth / 2 : 0; const center = radius - width / 2; const handleMouseMove = (e: MouseEvent) => { let dx = e.clientX - startPos.x; let dy = e.clientY - startPos.y; const centerDistance = Math.sqrt( Math.pow(dx - center, 2) + Math.pow(dy - center, 2) ); const sinValue = (dy - center) / centerDistance; const cosValue = (dx - center) / centerDistance; dx = center + radius * cosValue; dy = center + radius * sinValue; const radians = Math.atan2(dy - center, dx - center); const angle = (radians + Math.PI) / (Math.PI * 2); setAngle((angle + 0.75) % 1); setOffset({ dx, dy }); updateCursor(); }; const handleMouseUp = () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); resetCursor(); }; document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); }, [node, dx, dy] ); const handleTouchStart = React.useCallback( (e: TouchEvent) => { if (!node) { return; } const touch = e.touches[0]; const startPos = { x: touch.clientX - dx, y: touch.clientY - dy, }; const width = node.getBoundingClientRect().width; const containerWidth = node.parentElement?.getBoundingClientRect().width; const radius = containerWidth ? containerWidth / 2 : 0; const center = radius - width / 2; const handleTouchMove = (e: TouchEvent) => { const touch = e.touches[0]; let dx = touch.clientX - startPos.x; let dy = touch.clientY - startPos.y; const centerDistance = Math.sqrt( Math.pow(dx - center, 2) + Math.pow(dy - center, 2) ); const sinValue = (dy - center) / centerDistance; const cosValue = (dx - center) / centerDistance; dx = center + radius * cosValue; dy = center + radius * sinValue; const radians = Math.atan2(dy - center, dx - center); const angle = (radians + Math.PI) / (Math.PI * 2); setAngle((angle + 0.75) % 1); setOffset({ dx, dy }); updateCursor(); }; const handleTouchEnd = () => { document.removeEventListener("touchmove", handleTouchMove); document.removeEventListener("touchend", handleTouchEnd); resetCursor(); }; document.addEventListener("touchmove", handleTouchMove); document.addEventListener("touchend", handleTouchEnd); }, [node, dx, dy] ); const updateCursor = () => { document.body.style.cursor = "move"; document.body.style.userSelect = "none"; }; const resetCursor = () => { document.body.style.removeProperty("cursor"); document.body.style.removeProperty("user-select"); }; React.useEffect(() => { if (!node) return; node.addEventListener("mousedown", handleMouseDown); node.addEventListener("touchstart", handleTouchStart); return () => { node.removeEventListener("mousedown", handleMouseDown); node.removeEventListener("touchstart", handleTouchStart); }; }, [node, dx, dy]); return [ref, dx, dy, angle]; };