web deceleration
This commit is contained in:
@@ -93,15 +93,19 @@ export function Gallery({
|
|||||||
|
|
||||||
// Attach click-and-drag listeners directly to the scroll DOM node
|
// Attach click-and-drag listeners directly to the scroll DOM node
|
||||||
el.style.cursor = 'grab'
|
el.style.cursor = 'grab'
|
||||||
|
let lastMoveX = 0
|
||||||
|
let lastMoveTime = 0
|
||||||
|
|
||||||
el.addEventListener('mousedown', (e: MouseEvent) => {
|
el.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
isDragging.current = true
|
isDragging.current = true
|
||||||
hasDragged.current = false
|
hasDragged.current = true // assume drag; clear on mouseup if no movement
|
||||||
dragStartX.current = e.clientX
|
dragStartX.current = e.clientX
|
||||||
dragScrollStart.current = el.scrollLeft
|
dragScrollStart.current = el.scrollLeft
|
||||||
el.style.scrollBehavior = 'auto'
|
lastMoveX = e.clientX
|
||||||
|
lastMoveTime = Date.now()
|
||||||
el.style.scrollSnapType = 'none'
|
el.style.scrollSnapType = 'none'
|
||||||
|
el.style.scrollBehavior = 'auto'
|
||||||
el.style.cursor = 'grabbing'
|
el.style.cursor = 'grabbing'
|
||||||
el.style.userSelect = 'none'
|
el.style.userSelect = 'none'
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -109,22 +113,50 @@ export function Gallery({
|
|||||||
|
|
||||||
el.addEventListener('mousemove', (e: MouseEvent) => {
|
el.addEventListener('mousemove', (e: MouseEvent) => {
|
||||||
if (!isDragging.current) return
|
if (!isDragging.current) return
|
||||||
const dx = e.clientX - dragStartX.current
|
lastMoveX = e.clientX
|
||||||
if (Math.abs(dx) > 3) hasDragged.current = true
|
lastMoveTime = Date.now()
|
||||||
el.scrollLeft = dragScrollStart.current - dx
|
el.scrollLeft = dragScrollStart.current - (e.clientX - dragStartX.current)
|
||||||
})
|
})
|
||||||
|
|
||||||
const onUp = () => {
|
const onUp = (e: MouseEvent) => {
|
||||||
if (!isDragging.current) return
|
if (!isDragging.current) return
|
||||||
isDragging.current = false
|
isDragging.current = false
|
||||||
|
// Only allow click-through if mouse barely moved (true click)
|
||||||
|
if (Math.abs(e.clientX - dragStartX.current) <= 3) {
|
||||||
|
hasDragged.current = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply momentum: coast based on release velocity, then re-enable snap
|
||||||
|
const dt = Date.now() - lastMoveTime
|
||||||
|
const velocity =
|
||||||
|
dt < 100 ? (lastMoveX - dragStartX.current) / Math.max(dt, 1) : 0
|
||||||
|
const momentum = velocity * -800 // scale velocity to scroll distance
|
||||||
|
|
||||||
el.style.scrollBehavior = 'smooth'
|
el.style.scrollBehavior = 'smooth'
|
||||||
el.style.scrollSnapType = 'x proximity'
|
el.scrollLeft += momentum
|
||||||
|
// Re-enable snap after momentum scroll settles
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
el.style.scrollSnapType = 'x proximity'
|
||||||
|
})
|
||||||
|
|
||||||
el.style.cursor = 'grab'
|
el.style.cursor = 'grab'
|
||||||
el.style.userSelect = ''
|
el.style.userSelect = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
el.addEventListener('mouseup', onUp)
|
el.addEventListener('mouseup', onUp)
|
||||||
el.addEventListener('mouseleave', onUp)
|
el.addEventListener('mouseleave', onUp as EventListener)
|
||||||
|
|
||||||
|
// Intercept click events during/after drag to prevent Pressable onPress
|
||||||
|
el.addEventListener(
|
||||||
|
'click',
|
||||||
|
(e: MouseEvent) => {
|
||||||
|
if (hasDragged.current) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true, // capture phase — runs before React's synthetic events
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user