UNPKG

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
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, } ]); }