Created
March 20, 2022 19:57
-
-
Save Scribblerockerz/e068cba1adab2300d662a5993a5ae2d6 to your computer and use it in GitHub Desktop.
Vue3 Pointer Movement Directive
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
import { ObjectDirective } from 'vue'; | |
const noop = () => { | |
// noop | |
}; | |
type Position = { | |
x: number; | |
y: number; | |
}; | |
type PointerMovementInstance = { | |
isActive: boolean; | |
isDragging: boolean; | |
lastMovement: Position; | |
handlePointerDown: (e: PointerEvent) => void; | |
handlePointerMove: (e: PointerEvent) => void; | |
}; | |
interface PointerMovementAwareHTMLElement extends HTMLElement { | |
pointerMovementInstance: PointerMovementInstance; | |
} | |
function createPointerMovementInstance() { | |
return { | |
isActive: false, | |
isDragging: false, | |
lastMovement: { x: 0, y: 0 }, | |
handlePointerDown: noop, | |
handlePointerMove: noop, | |
}; | |
} | |
function moveElement(el: HTMLElement, distance: Position, callback?: CallableFunction) { | |
window.requestAnimationFrame(() => { | |
el.scrollTop = el.scrollTop - distance.y; | |
el.scrollLeft = el.scrollLeft - distance.x; | |
callback && callback(); | |
}); | |
} | |
function moveElementDecremental(el: HTMLElement, distance: Position, factor: number, pmi: PointerMovementInstance) { | |
// Stop scrolling, we're moving again | |
if (pmi.isDragging) return; | |
// Break if the number is too small | |
if (Math.abs(distance.x) + Math.abs(distance.y) < 0.1) return; | |
moveElement(el, distance, () => { | |
setTimeout(() => { | |
moveElementDecremental( | |
el, | |
{ | |
x: distance.x * factor, | |
y: distance.y * factor, | |
}, | |
factor, | |
pmi | |
); | |
}, 5); | |
}); | |
} | |
function getPointerMoveHandler(el: PointerMovementAwareHTMLElement, pmi: PointerMovementInstance) { | |
return (e: PointerEvent) => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
if (!pmi.isActive) return; | |
pmi.lastMovement = { | |
x: e.movementX, | |
y: e.movementY, | |
}; | |
moveElement(el, pmi.lastMovement); | |
}; | |
} | |
function getPointerDownHandler(el: PointerMovementAwareHTMLElement, pmi: PointerMovementInstance) { | |
return () => { | |
// Not active, skip execution | |
if (!pmi.isActive) return; | |
pmi.isDragging = true; | |
pmi.handlePointerMove = getPointerMoveHandler(el, pmi); | |
document.addEventListener('pointermove', pmi.handlePointerMove); | |
document.addEventListener( | |
'pointerup', | |
() => { | |
pmi.isDragging = false; | |
document.removeEventListener('pointermove', pmi.handlePointerMove); | |
// Continue smooth scrolling in last direction | |
moveElementDecremental( | |
el, | |
{ | |
...pmi.lastMovement, | |
}, | |
0.92, | |
pmi | |
); | |
pmi.lastMovement = { | |
x: 0, | |
y: 0, | |
}; | |
}, | |
{ once: true } | |
); | |
}; | |
} | |
export const pointerMovement: ObjectDirective<PointerMovementAwareHTMLElement, boolean> = { | |
mounted(el: PointerMovementAwareHTMLElement, binding) { | |
el.pointerMovementInstance = createPointerMovementInstance(); | |
const pmi = el.pointerMovementInstance; | |
pmi.isActive = binding.value !== false; | |
pmi.handlePointerDown = getPointerDownHandler(el, pmi); | |
el.addEventListener('pointerdown', pmi.handlePointerDown); | |
}, | |
updated(el: PointerMovementAwareHTMLElement, binding) { | |
const pmi = el.pointerMovementInstance; | |
pmi.isActive = binding.value !== false; | |
}, | |
unmounted(el: PointerMovementAwareHTMLElement) { | |
const pmi = el.pointerMovementInstance; | |
el.removeEventListener('pointerdown', pmi.handlePointerDown); | |
}, | |
}; |
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
<template> | |
<!-- default usage --> | |
<div v-pointer-movement /> | |
<!-- Toogle pointer movement with a boolean var --> | |
<div v-pointer-movement="allowPointerMovement" /> | |
</template> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment