نحوه ردیابی حرکت ماوس و لمس در حالت فشرده با React

✨ در یوتیوب تماشا کنید | 🐙 گیت هاب | 🎮 نسخه ی نمایشی
وقتی رابطهای کاربری تعاملی پیچیدهای مانند لغزنده، ویرایشگر یا انتخابگر رنگ میسازیم، اغلب نیاز داریم که حرکت ماوس یا لمس را در حالت فشرده ردیابی کنیم. اجازه دهید یک جزء انتزاعی با کاربرد آسان را که دقیقاً برای این منظور طراحی شده است به اشتراک بگذارم.
جزء PressTracker
مؤلفه دو ویژگی دریافت می کند:
-
render
– تابعی که جزء مصرف کننده باید از آن برای ارائه محتوا استفاده کند. یک شی را باprops
وposition
خواص اینprops
فیلد حاویref
و دستههای شروع موس و لمسی، باید به عنصر ظرف منتقل شود. اینposition
یک نقطه باx
وy
مختصات در محدوده 0 تا 1. نشان دهنده موقعیت نسبی مکان نما در داخل ظرف است. در تمام موارد عملی من، نیاز به دانستن موقعیت نسبی داشتم، بنابراین تصمیم گرفتم از آن به عنوان پیش فرض استفاده کنم. -
onChange
– تابعی که با تغییر موقعیت فراخوانی می شود. یک شی را با همان دریافت می کندposition
بحث و جدل. توجه داشته باشید زمانی که منشاء پرس خارج از ظرف باشد، موقعیت آن خواهد بودnull
.
import { Point } from "lib/entities/Point"
import { useBoundingBox } from "lib/shared/hooks/useBoundingBox"
import { enforceRange } from "lib/shared/utils/enforceRange"
import {
MouseEvent,
MouseEventHandler,
ReactNode,
TouchEvent,
TouchEventHandler,
useCallback,
useEffect,
useState,
} from "react"
import { useEvent } from "react-use"
interface ContainerProps {
onMouseDown: MouseEventHandler<HTMLElement>
onTouchStart: TouchEventHandler<HTMLElement>
ref: (node: HTMLElement | null) => void
}
interface ChangeParams {
position: Point | null
}
interface RenderParams extends ChangeParams {
props: ContainerProps
}
interface PressTrackerProps {
render: (props: RenderParams) => ReactNode
onChange?: (params: ChangeParams) => void
}
export const PressTracker = ({ render, onChange }: PressTrackerProps) => {
const [container, setContainer] = useState<HTMLElement | null>(null)
const box = useBoundingBox(container)
const [position, setPosition] = useState<Point | null>(null)
const handleMove = useCallback(
({ x, y }: Point) => {
if (!box) return
const { left, top, width, height } = box
setPosition({
x: enforceRange((x - left) / width, 0, 1),
y: enforceRange((y - top) / height, 0, 1),
})
},
[box]
)
const handleMouse = useCallback(
(event: MouseEvent) => {
handleMove({ x: event.clientX, y: event.clientY })
},
[handleMove]
)
const handleTouch = useCallback(
(event: TouchEvent) => {
const touch = event.touches[0]
if (touch) {
handleMove({ x: touch.clientX, y: touch.clientY })
}
},
[handleMove]
)
useEffect(() => {
if (onChange) {
onChange({ position })
}
}, [onChange, position])
const clearPosition = useCallback(() => {
setPosition(null)
}, [])
useEvent("mouseup", position ? clearPosition : undefined)
useEvent("touchend", position ? clearPosition : undefined)
useEvent("mousemove", position ? handleMouse : undefined)
useEvent("touchmove", position ? handleTouch : undefined)
return (
<>
{render({
props: {
ref: setContainer,
onMouseDown: handleMouse,
onTouchStart: handleTouch,
},
position: position,
})}
</>
)
}
این PressTracker
جزء عنصر ظرف را در useState
و از useBoundingBox
قلاب را برای به دست آوردن موقعیت و اندازه آن. برای شروع فرآیند ردیابی، جزء مصرف کننده باید رندر شود PressTracker
جزء و پروپ های لازم را به عنصر ظرف منتقل کنید.
<PressTracker
render={({ props, position }) => (
<Container {...props}>
{position && (
<Highlight
style={{
width: toPercents(position.x),
height: toPercents(position.y),
}}
/>
)}
</Container>
)}
/>
ما موقعیت را روی هر یک از ماوس به پایین در رویدادهای شروع لمسی تنظیم می کنیم. بنابراین هنگامی که کاربر با کانتینر تعامل کرد، ما شروع به ردیابی موقعیت مکان نما می کنیم useEvent
قلاب از react-use
کتابخانه گوش می دهد برای mouseup
و touchend
رویدادها برای توقف ردیابی، و همچنین mousemove
و touchmove
رویدادها برای به روز رسانی موقعیت مکان نما. این handleMove
تابع، مختصات مطلق را از رویدادهای لمسی و ماوس تبدیل می کند، و اطمینان می دهد که مختصات در محدوده باقی می مانند 0
به 1
، حتی زمانی که مکان نما خارج از ظرف است.