@studiometa/js-toolkit
Version:
A set of useful little bits of JavaScript to boost your project! 🚀
228 lines (227 loc) • 5.74 kB
JavaScript
import { AbstractService } from "./AbstractService.js";
import { useRaf } from "./RafService.js";
import { isDefined } from "../utils/is.js";
import { inertiaFinalValue } from "../utils/math/index.js";
import { PASSIVE_EVENT_OPTIONS, CAPTURE_EVENT_OPTIONS } from "./utils.js";
let count = 0;
class DragService extends AbstractService {
static MODES = {
START: "start",
DRAG: "drag",
DROP: "drop",
INERTIA: "inertia",
STOP: "stop"
};
static config = [
[
(instance) => instance.props.target,
[
["dragstart", CAPTURE_EVENT_OPTIONS],
["click", CAPTURE_EVENT_OPTIONS],
["pointerdown", PASSIVE_EVENT_OPTIONS]
]
],
[
() => window,
[
["pointerup", PASSIVE_EVENT_OPTIONS],
["touchend", PASSIVE_EVENT_OPTIONS]
]
]
];
id;
dampFactor = 0.85;
dragTreshold = 10;
previousEvent = null;
props = {
target: null,
mode: void 0,
MODES: DragService.MODES,
isGrabbing: false,
hasInertia: false,
x: 0,
y: 0,
delta: {
x: 0,
y: 0
},
origin: {
x: 0,
y: 0
},
distance: {
x: 0,
y: 0
},
final: {
x: 0,
y: 0
}
};
/**
* Test if we should allow click on links and buttons.
*/
get shouldPreventClick() {
return Math.abs(this.props.distance.x) > this.dragTreshold || Math.abs(this.props.distance.y) > this.dragTreshold;
}
constructor(target, { dampFactor = 0.85, dragTreshold = 10 } = {}) {
super();
count += 1;
this.id = `drag-${count}`;
this.dampFactor = dampFactor;
this.dragTreshold = dragTreshold;
this.props.target = target;
}
/**
* Get the client value for the given axis.
*/
getEventPosition(event) {
const eventOrTouch = isDefined(event.touches) ? event.touches[0] : event;
return {
x: eventOrTouch.clientX,
y: eventOrTouch.clientY
};
}
/**
* Start the drag.
*
* @param {number} x The initial horizontal position.
* @param {number} y The initial vertical position.
*/
start(x, y) {
const { props } = this;
if (props.isGrabbing) {
return;
}
props.x = props.origin.x = props.final.x = x;
props.y = props.origin.y = props.final.y = y;
props.delta.x = props.distance.x = 0;
props.delta.y = props.distance.y = 0;
props.mode = props.MODES.START;
props.isGrabbing = true;
this.trigger(props);
document.addEventListener("touchmove", this, PASSIVE_EVENT_OPTIONS);
document.addEventListener("mousemove", this, PASSIVE_EVENT_OPTIONS);
}
/**
* Stop the drag, or drop.
*/
drop() {
const { props, dampFactor, id } = this;
if (!props.isGrabbing) {
return;
}
document.removeEventListener("touchmove", this);
document.removeEventListener("mousemove", this);
props.isGrabbing = false;
props.mode = props.MODES.DROP;
props.hasInertia = true;
props.final.x = inertiaFinalValue(props.x, props.delta.x, dampFactor);
props.final.y = inertiaFinalValue(props.y, props.delta.y, dampFactor);
this.previousEvent = null;
this.trigger(props);
setTimeout(() => {
const raf = useRaf();
raf.remove(id);
raf.add(id, () => this.rafHandler());
});
}
/**
* Stop the drag.
*/
stop() {
const { props, id } = this;
useRaf().remove(id);
props.isGrabbing = false;
props.hasInertia = false;
props.mode = props.MODES.STOP;
this.trigger(props);
}
/**
* Raf service handler.
*/
rafHandler() {
const { props, dampFactor } = this;
if (!props.isGrabbing) {
props.x += props.delta.x;
props.y += props.delta.y;
props.distance.x = props.x - props.origin.x;
props.distance.y = props.y - props.origin.y;
props.delta.x *= dampFactor;
props.delta.y *= dampFactor;
props.mode = props.MODES.INERTIA;
this.trigger(props);
if (Math.abs(props.delta.x) < 0.1 && Math.abs(props.delta.y) < 0.1) {
this.stop();
}
}
}
/**
* Pointer service handler.
*/
drag(event) {
const { props } = this;
if (props.isGrabbing) {
const position = this.getEventPosition(event);
props.x = position.x;
props.y = position.y;
if (this.previousEvent) {
const previousPosition = this.getEventPosition(this.previousEvent);
props.delta.x = position.x - previousPosition.x;
props.delta.y = position.y - previousPosition.y;
}
props.final.x = position.x;
props.final.y = position.y;
props.distance.x = props.x - props.origin.x;
props.distance.y = props.y - props.origin.y;
props.mode = props.MODES.DRAG;
this.trigger(props);
this.previousEvent = event;
}
}
/**
* Handle any event.
*/
handleEvent(event) {
switch (event.type) {
case "dragstart":
event.preventDefault();
break;
case "click":
if (this.shouldPreventClick) {
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
}
break;
case "pointerup":
case "touchend":
this.drop();
break;
case "touchmove":
case "mousemove":
if (event.buttons === 1 || event.touches?.length === 1) {
this.drag(event);
} else {
this.drop();
}
break;
default:
if (event.button === 0) {
this.start(event.x, event.y);
}
}
}
}
function useDrag(target, options) {
return DragService.getInstance(
[target, JSON.stringify(options)],
target,
options
);
}
export {
DragService,
useDrag
};
//# sourceMappingURL=DragService.js.map