Skip to content

Instantly share code, notes, and snippets.

@Scribblerockerz
Created March 20, 2022 19:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Scribblerockerz/e068cba1adab2300d662a5993a5ae2d6 to your computer and use it in GitHub Desktop.
Save Scribblerockerz/e068cba1adab2300d662a5993a5ae2d6 to your computer and use it in GitHub Desktop.
Vue3 Pointer Movement Directive
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);
},
};
<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