Last active
March 7, 2024 15:08
-
-
Save alfonsusac/e699034b8f6ef1085600e19c24f89bb5 to your computer and use it in GitHub Desktop.
React useMouseEventListener -> useMouse -> useMouseDrag
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
// Usage | |
export function Model( | |
props: { | |
data: NodeData | |
onDragEnd: (newPos: Pos) => void | |
} | |
) { | |
const [position, setPosition] = useState(props.data.position) | |
const ref = useRef<HTMLDivElement>(null) | |
const { | |
dragging, | |
} = useMouseDrag( | |
ref, | |
new Pos(0,0), | |
(delta) => { | |
setPosition(position.add(delta)) | |
}, | |
() => { | |
props.onDragEnd(position) | |
} | |
) | |
return <div | |
id={props.data.id} | |
ref={ref} | |
className="w-40 h-40 bg-orange-300 select-none shadow-xl" | |
style={{ | |
transform: `translateX(${ position.x }px) translateY(${ position.y }px)` | |
}} | |
> | |
{position + ""}<br /> | |
{dragging + ""}<br /> | |
</div> | |
} | |
// #1 | |
export function useMouseEventListener( | |
mouseEv: (data: { | |
position: Pos, | |
scroll: number, | |
leftClick: boolean, | |
rightClick: boolean, | |
middleClick: boolean, | |
}) => void | |
) { | |
useEffect(() => { | |
function mouseEventHandler(e: MouseEvent) { | |
// console.log(e.button, e.buttons) | |
mouseEv({ | |
position: new Pos(e.clientX, e.clientY), | |
scroll: 0, | |
leftClick: e.buttons === 1, | |
rightClick: e.buttons === 2, | |
middleClick: e.buttons === 4, | |
}) | |
} | |
function wheelEventHandler(e: WheelEvent) { | |
// console.log(e.button, e.buttons) | |
mouseEv({ | |
position: new Pos(e.clientX, e.clientY), | |
scroll: e.deltaZ, | |
leftClick: e.buttons === 1, | |
rightClick: e.buttons === 2, | |
middleClick: e.buttons === 4, | |
}) | |
} | |
window.addEventListener('mousemove', mouseEventHandler) | |
window.addEventListener('mousedown', mouseEventHandler) | |
window.addEventListener('mouseup', mouseEventHandler) | |
window.addEventListener('wheel', wheelEventHandler) | |
return () => { | |
window.removeEventListener('mousemove', mouseEventHandler) | |
window.removeEventListener('mousedown', mouseEventHandler) | |
window.removeEventListener('mouseup', mouseEventHandler) | |
window.removeEventListener('wheel', wheelEventHandler) | |
} | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, []) | |
} | |
// #2 | |
export function useMouse(mouseEv: ( | |
data: CustomMouseEventPayload) => void, | |
) { | |
const [mouseBasicEvent, setMouseBasicEvent] = useState<{ | |
position: Pos, | |
scroll: number, | |
leftClick: boolean, | |
rightClick: boolean, | |
middleClick: boolean, | |
}>() | |
const [prevPosition, setPrevPosition] = useState<null | Pos>(null) | |
const [positionDelta, setPositionDelta] = useState(new Pos(0, 0)) | |
useMouseEventListener((event) => { | |
setMouseBasicEvent(event) | |
}) | |
useEffect(() => { | |
if (mouseBasicEvent) { | |
const delta = mouseBasicEvent.position.subtract(prevPosition ?? mouseBasicEvent.position) | |
setPrevPosition(mouseBasicEvent.position) | |
setPositionDelta(delta) | |
// toast(delta + '') | |
mouseEv({ | |
...mouseBasicEvent, | |
positionDelta: delta, | |
}) | |
} | |
}, [mouseBasicEvent]) | |
return { | |
...mouseBasicEvent, | |
positionDelta | |
} | |
} | |
// #3 | |
export function useMouseDrag<T extends HTMLElement>( | |
target: RefObject<T>, | |
initialPosition: Pos, // todo: remove this | |
onDrag: (delta: Pos) => void, | |
onEnd: () => void, | |
scale: number = 1, | |
) { | |
const [dragging, setDragging] = useState(false) | |
const { dragRef, setDragRef } = useContext(GlobalDragContext) | |
const mouse = useMouse(() => { }) | |
const { | |
leftClick, | |
positionDelta, | |
position: mousePos, | |
} = mouse | |
useEffect(() => { | |
if (leftClick && !positionDelta.isZero && !dragging && mousePos && !dragRef) { | |
const el = document.elementFromPoint(mousePos.x, mousePos.y) | |
if (!el) return | |
if (!target.current) return | |
if (el.id === target.current.id) { | |
setDragging(true) | |
setDragRef(el.id) | |
} | |
} | |
}, [leftClick, positionDelta, dragging, mousePos, target, dragRef, setDragRef]) | |
useEffect(() => { | |
if (leftClick === false && dragging) { | |
setDragging(false) | |
setDragRef(null) | |
onEnd() | |
} | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, [leftClick, dragging, dragRef, setDragRef]) | |
useEffect(() => { | |
if (dragging && positionDelta) { | |
onDrag(positionDelta) | |
} | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, [dragging, positionDelta]) | |
return { | |
dragging, | |
...mouse, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment