useMouse
A hook to track mouse position with optional touch support
Loading...
Installation
npx shadcn@latest add @shadcnhooks/use-mousepnpm dlx shadcn@latest add @shadcnhooks/use-mouseyarn dlx shadcn@latest add @shadcnhooks/use-mousebun x shadcn@latest add @shadcnhooks/use-mouseCopy and paste the following code into your project.
import { useEffectWithTarget } from '@/registry/hooks/use-effect-with-target'
import { useLatest } from '@/registry/hooks/use-latest'
import { getTargetElement } from '@/registry/lib/create-effect-with-target'
import type { BasicTarget } from '@/registry/lib/create-effect-with-target'
type noop = (...p: any) => void
export type Target = BasicTarget<HTMLElement | Element | Window | Document>
interface Options<T extends Target = Target> {
target?: T
capture?: boolean
once?: boolean
passive?: boolean
enable?: boolean
}
function useEventListener<K extends keyof HTMLElementEventMap>(
eventName: K,
handler: (ev: HTMLElementEventMap[K]) => void,
options?: Options<HTMLElement>,
): void
function useEventListener<K extends keyof ElementEventMap>(
eventName: K,
handler: (ev: ElementEventMap[K]) => void,
options?: Options<Element>,
): void
function useEventListener<K extends keyof DocumentEventMap>(
eventName: K,
handler: (ev: DocumentEventMap[K]) => void,
options?: Options<Document>,
): void
function useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (ev: WindowEventMap[K]) => void,
options?: Options<Window>,
): void
function useEventListener(
eventName: string | string[],
handler: (event: Event) => void,
options?: Options<Window>,
): void
function useEventListener(
eventName: string | string[],
handler: noop,
options: Options,
): void
function useEventListener(
eventName: string | string[],
handler: noop,
options: Options = {},
) {
const { enable = true } = options
const handlerRef = useLatest(handler)
useEffectWithTarget(
() => {
if (!enable) {
return
}
const targetElement = getTargetElement(options.target, window)
if (!targetElement?.addEventListener) {
return
}
const eventListener = (event: Event) => {
return handlerRef.current(event)
}
const eventNameArray = Array.isArray(eventName) ? eventName : [eventName]
eventNameArray.forEach((event) => {
targetElement.addEventListener(event, eventListener, {
capture: options.capture,
once: options.once,
passive: options.passive,
})
})
return () => {
eventNameArray.forEach((event) => {
targetElement.removeEventListener(event, eventListener, {
capture: options.capture,
})
})
}
},
[eventName, options.capture, options.once, options.passive, enable],
options.target,
)
}
export { useEventListener }import { useState } from 'react'
import { useEventListener } from '@/registry/hooks/use-event-listener'
export type UseMouseCoordType = 'page' | 'client' | 'screen' | 'movement'
export type UseMouseSourceType = 'mouse' | 'touch' | null
export interface UseMouseInitialValue {
x: number
y: number
}
export interface UseMouseState extends UseMouseInitialValue {
sourceType: UseMouseSourceType
}
export interface UseMouseOptions {
type?: UseMouseCoordType
touch?: boolean
resetOnTouchEnds?: boolean
initialValue?: UseMouseInitialValue
window?: Window
}
type MouseCoordKey =
| 'pageX'
| 'pageY'
| 'clientX'
| 'clientY'
| 'screenX'
| 'screenY'
| 'movementX'
| 'movementY'
type TouchCoordKey =
| 'pageX'
| 'pageY'
| 'clientX'
| 'clientY'
| 'screenX'
| 'screenY'
type TouchCoordType = Exclude<UseMouseCoordType, 'movement'>
const DEFAULT_INITIAL_VALUE: UseMouseInitialValue = {
x: 0,
y: 0,
}
const MOUSE_X_COORD_KEY_BY_TYPE: Record<UseMouseCoordType, MouseCoordKey> = {
page: 'pageX',
client: 'clientX',
screen: 'screenX',
movement: 'movementX',
}
const MOUSE_Y_COORD_KEY_BY_TYPE: Record<UseMouseCoordType, MouseCoordKey> = {
page: 'pageY',
client: 'clientY',
screen: 'screenY',
movement: 'movementY',
}
const TOUCH_X_COORD_KEY_BY_TYPE: Record<TouchCoordType, TouchCoordKey> = {
page: 'pageX',
client: 'clientX',
screen: 'screenX',
}
const TOUCH_Y_COORD_KEY_BY_TYPE: Record<TouchCoordType, TouchCoordKey> = {
page: 'pageY',
client: 'clientY',
screen: 'screenY',
}
function readMousePosition(
event: MouseEvent,
type: UseMouseCoordType,
): UseMouseInitialValue {
const xKey = MOUSE_X_COORD_KEY_BY_TYPE[type]
const yKey = MOUSE_Y_COORD_KEY_BY_TYPE[type]
return {
x: event[xKey],
y: event[yKey],
}
}
function readTouchPosition(
touchPoint: Touch,
type: UseMouseCoordType,
): UseMouseInitialValue {
if (type === 'movement') {
return {
x: touchPoint.clientX,
y: touchPoint.clientY,
}
}
const xKey = TOUCH_X_COORD_KEY_BY_TYPE[type]
const yKey = TOUCH_Y_COORD_KEY_BY_TYPE[type]
return {
x: touchPoint[xKey],
y: touchPoint[yKey],
}
}
/**
* Reactive mouse position with optional touch support.
*
* @see https://shadcn-hooks.com/docs/hooks/use-mouse
*/
export function useMouse(options: UseMouseOptions = {}): UseMouseState {
const {
type = 'page',
touch = true,
resetOnTouchEnds = false,
initialValue = DEFAULT_INITIAL_VALUE,
window: customWindow,
} = options
const [state, setState] = useState<UseMouseState>(() => ({
x: initialValue.x,
y: initialValue.y,
sourceType: null,
}))
const targetWindow =
customWindow ?? (typeof window === 'undefined' ? undefined : window)
const enable = Boolean(targetWindow)
const updateFromMouseEvent = (event: MouseEvent) => {
const nextPosition = readMousePosition(event, type)
setState((prev) => ({
...prev,
...nextPosition,
sourceType: 'mouse',
}))
}
const updateFromTouchEvent = (event: TouchEvent) => {
const touchPoint = event.touches[0] ?? event.changedTouches[0]
if (!touchPoint) {
return
}
const nextPosition = readTouchPosition(touchPoint, type)
setState((prev) => ({
...prev,
...nextPosition,
sourceType: 'touch',
}))
}
const resetPosition = () => {
setState((prev) => ({
...prev,
x: initialValue.x,
y: initialValue.y,
}))
}
const handleTouchEnd = (event: TouchEvent) => {
if (resetOnTouchEnds) {
resetPosition()
return
}
updateFromTouchEvent(event)
}
useEventListener('mousemove', updateFromMouseEvent, {
target: targetWindow,
passive: true,
enable,
})
useEventListener('dragover', updateFromMouseEvent, {
target: targetWindow,
passive: true,
enable,
})
useEventListener('touchstart', updateFromTouchEvent, {
target: targetWindow,
passive: true,
enable: enable && touch,
})
useEventListener('touchmove', updateFromTouchEvent, {
target: targetWindow,
passive: true,
enable: enable && touch,
})
useEventListener('touchend', handleTouchEnd, {
target: targetWindow,
passive: true,
enable: enable && touch,
})
return state
}API
export type UseMouseCoordType = 'page' | 'client' | 'screen' | 'movement'
export type UseMouseSourceType = 'mouse' | 'touch' | null
export interface UseMouseInitialValue {
x: number
y: number
}
export interface UseMouseState extends UseMouseInitialValue {
sourceType: UseMouseSourceType
}
export interface UseMouseOptions {
type?: UseMouseCoordType
touch?: boolean
resetOnTouchEnds?: boolean
initialValue?: UseMouseInitialValue
window?: Window
}
/**
* Reactive mouse position with optional touch support.
*/
export function useMouse(options?: UseMouseOptions): UseMouseStateCredits
Last updated on