interactjs
Version:
Drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE8+)
289 lines (218 loc) • 8.51 kB
JavaScript
const InteractEvent = require('./InteractEvent');
const Interaction = require('./Interaction');
const modifiers = require('./modifiers');
const utils = require('./utils');
const animationFrame = require('./utils/raf');
Interaction.signals.on('new', function (interaction) {
interaction.inertiaStatus = {
active : false,
smoothEnd : false,
allowResume: false,
startEvent: null,
upCoords : {},
xe: 0, ye: 0,
sx: 0, sy: 0,
t0: 0,
vx0: 0, vys: 0,
duration: 0,
lambda_v0: 0,
one_ve_v0: 0,
i : null,
};
interaction.boundInertiaFrame = () => inertiaFrame .apply(interaction);
interaction.boundSmoothEndFrame = () => smoothEndFrame.apply(interaction);
});
Interaction.signals.on('down', function ({ interaction, event, pointer, eventTarget }) {
const status = interaction.inertiaStatus;
// Check if the down event hits the current inertia target
if (status.active) {
let element = eventTarget;
// climb up the DOM tree from the event target
while (utils.is.element(element)) {
// if interaction element is the current inertia target element
if (element === interaction.element) {
// stop inertia
animationFrame.cancel(status.i);
status.active = false;
interaction.simulation = null;
// update pointers to the down event's coordinates
interaction.updatePointer(pointer);
utils.setCoords(interaction.curCoords, interaction.pointers);
// fire appropriate signals
const signalArg = { interaction };
Interaction.signals.fire('before-action-move', signalArg);
Interaction.signals.fire('action-resume' , signalArg);
// fire a reume event
const resumeEvent = new InteractEvent(interaction,
event,
interaction.prepared.name,
'inertiaresume',
interaction.element);
interaction.target.fire(resumeEvent);
interaction.prevEvent = resumeEvent;
modifiers.resetStatuses(interaction.modifierStatuses);
utils.copyCoords(interaction.prevCoords, interaction.curCoords);
break;
}
element = utils.parentNode(element);
}
}
});
Interaction.signals.on('up', function ({ interaction, event }) {
const status = interaction.inertiaStatus;
if (!interaction.interacting() || status.active) { return; }
const target = interaction.target;
const options = target && target.options;
const inertiaOptions = options && interaction.prepared.name && options[interaction.prepared.name].inertia;
const now = new Date().getTime();
const statuses = {};
const page = utils.extend({}, interaction.curCoords.page);
const pointerSpeed = interaction.pointerDelta.client.speed;
let smoothEnd = false;
let modifierResult;
// check if inertia should be started
const inertiaPossible = (inertiaOptions && inertiaOptions.enabled
&& interaction.prepared.name !== 'gesture'
&& event !== status.startEvent);
const inertia = (inertiaPossible
&& (now - interaction.curCoords.timeStamp) < 50
&& pointerSpeed > inertiaOptions.minSpeed
&& pointerSpeed > inertiaOptions.endSpeed);
const modifierArg = {
interaction,
pageCoords: page,
statuses,
preEnd: true,
requireEndOnly: true,
};
// smoothEnd
if (inertiaPossible && !inertia) {
modifiers.resetStatuses(statuses);
modifierResult = modifiers.setAll(modifierArg);
if (modifierResult.shouldMove && modifierResult.locked) {
smoothEnd = true;
}
}
if (!(inertia || smoothEnd)) { return; }
utils.copyCoords(status.upCoords, interaction.curCoords);
interaction.pointers[0] = status.startEvent =
new InteractEvent(interaction, event, interaction.prepared.name, 'inertiastart', interaction.element);
status.t0 = now;
status.active = true;
status.allowResume = inertiaOptions.allowResume;
interaction.simulation = status;
target.fire(status.startEvent);
if (inertia) {
status.vx0 = interaction.pointerDelta.client.vx;
status.vy0 = interaction.pointerDelta.client.vy;
status.v0 = pointerSpeed;
calcInertia(interaction, status);
utils.extend(page, interaction.curCoords.page);
page.x += status.xe;
page.y += status.ye;
modifiers.resetStatuses(statuses);
modifierResult = modifiers.setAll(modifierArg);
status.modifiedXe += modifierResult.dx;
status.modifiedYe += modifierResult.dy;
status.i = animationFrame.request(interaction.boundInertiaFrame);
}
else {
status.smoothEnd = true;
status.xe = modifierResult.dx;
status.ye = modifierResult.dy;
status.sx = status.sy = 0;
status.i = animationFrame.request(interaction.boundSmoothEndFrame);
}
});
Interaction.signals.on('stop-active', function ({ interaction }) {
const status = interaction.inertiaStatus;
if (status.active) {
animationFrame.cancel(status.i);
status.active = false;
interaction.simulation = null;
}
});
function calcInertia (interaction, status) {
const inertiaOptions = interaction.target.options[interaction.prepared.name].inertia;
const lambda = inertiaOptions.resistance;
const inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda;
status.x0 = interaction.prevEvent.pageX;
status.y0 = interaction.prevEvent.pageY;
status.t0 = status.startEvent.timeStamp / 1000;
status.sx = status.sy = 0;
status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;
status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;
status.te = inertiaDur;
status.lambda_v0 = lambda / status.v0;
status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0;
}
function inertiaFrame () {
updateInertiaCoords(this);
utils.setCoordDeltas(this.pointerDelta, this.prevCoords, this.curCoords);
const status = this.inertiaStatus;
const options = this.target.options[this.prepared.name].inertia;
const lambda = options.resistance;
const t = new Date().getTime() / 1000 - status.t0;
if (t < status.te) {
const progress = 1 - (Math.exp(-lambda * t) - status.lambda_v0) / status.one_ve_v0;
if (status.modifiedXe === status.xe && status.modifiedYe === status.ye) {
status.sx = status.xe * progress;
status.sy = status.ye * progress;
}
else {
const quadPoint = utils.getQuadraticCurvePoint(0, 0,
status.xe,
status.ye,
status.modifiedXe,
status.modifiedYe,
progress);
status.sx = quadPoint.x;
status.sy = quadPoint.y;
}
this.doMove();
status.i = animationFrame.request(this.boundInertiaFrame);
}
else {
status.sx = status.modifiedXe;
status.sy = status.modifiedYe;
this.doMove();
this.end(status.startEvent);
status.active = false;
this.simulation = null;
}
utils.copyCoords(this.prevCoords, this.curCoords);
}
function smoothEndFrame () {
updateInertiaCoords(this);
const status = this.inertiaStatus;
const t = new Date().getTime() - status.t0;
const duration = this.target.options[this.prepared.name].inertia.smoothEndDuration;
if (t < duration) {
status.sx = utils.easeOutQuad(t, 0, status.xe, duration);
status.sy = utils.easeOutQuad(t, 0, status.ye, duration);
this.pointerMove(status.startEvent, status.startEvent);
status.i = animationFrame.request(this.boundSmoothEndFrame);
}
else {
status.sx = status.xe;
status.sy = status.ye;
this.pointerMove(status.startEvent, status.startEvent);
this.end(status.startEvent);
status.smoothEnd =
status.active = false;
this.simulation = null;
}
}
function updateInertiaCoords (interaction) {
const status = interaction.inertiaStatus;
// return if inertia isn't running
if (!status.active) { return; }
const pageUp = status.upCoords.page;
const clientUp = status.upCoords.client;
utils.setCoords(interaction.curCoords, [ {
pageX : pageUp.x + status.sx,
pageY : pageUp.y + status.sy,
clientX: clientUp.x + status.sx,
clientY: clientUp.y + status.sy,
} ]);
}