UNPKG

sortablejs

Version:

JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery required. Supports Meteor, AngularJS, React, Polymer, Vue, Knockout and any CSS library, e.g. Bootstrap.

176 lines (147 loc) 5.15 kB
import { getRect, css, matrix, isRectEqual, indexOfObject } from './utils.js'; import Sortable from './Sortable.js'; export default function AnimationStateManager() { let animationStates = [], animationCallbackId; return { captureAnimationState() { animationStates = []; if (!this.options.animation) return; let children = [].slice.call(this.el.children); children.forEach(child => { if (css(child, 'display') === 'none' || child === Sortable.ghost) return; animationStates.push({ target: child, rect: getRect(child) }); let fromRect = { ...animationStates[animationStates.length - 1].rect }; // If animating: compensate for current animation if (child.thisAnimationDuration) { let childMatrix = matrix(child, true); if (childMatrix) { fromRect.top -= childMatrix.f; fromRect.left -= childMatrix.e; } } child.fromRect = fromRect; }); }, addAnimationState(state) { animationStates.push(state); }, removeAnimationState(target) { animationStates.splice(indexOfObject(animationStates, { target }), 1); }, animateAll(callback) { if (!this.options.animation) { clearTimeout(animationCallbackId); if (typeof(callback) === 'function') callback(); return; } let animating = false, animationTime = 0; animationStates.forEach((state) => { let time = 0, animatingThis = false, target = state.target, fromRect = target.fromRect, toRect = getRect(target), prevFromRect = target.prevFromRect, prevToRect = target.prevToRect, animatingRect = state.rect, targetMatrix = matrix(target, true); if (targetMatrix) { // Compensate for current animation toRect.top -= targetMatrix.f; toRect.left -= targetMatrix.e; } target.toRect = toRect; if (target.thisAnimationDuration) { // Could also check if animatingRect is between fromRect and toRect if ( isRectEqual(prevFromRect, toRect) && !isRectEqual(fromRect, toRect) && // Make sure animatingRect is on line between toRect & fromRect (animatingRect.top - toRect.top) / (animatingRect.left - toRect.left) === (fromRect.top - toRect.top) / (fromRect.left - toRect.left) ) { // If returning to same place as started from animation and on same axis time = calculateRealTime(animatingRect, prevFromRect, prevToRect, this.options); } } // if fromRect != toRect: animate if (!isRectEqual(toRect, fromRect)) { target.prevFromRect = fromRect; target.prevToRect = toRect; if (!time) { time = this.options.animation; } this.animate( target, animatingRect, toRect, time ); } if (time) { animating = true; animationTime = Math.max(animationTime, time); clearTimeout(target.animationResetTimer); target.animationResetTimer = setTimeout(function() { target.animationTime = 0; target.prevFromRect = null; target.fromRect = null; target.prevToRect = null; target.thisAnimationDuration = null; }, time); target.thisAnimationDuration = time; } }); clearTimeout(animationCallbackId); if (!animating) { if (typeof(callback) === 'function') callback(); } else { animationCallbackId = setTimeout(function() { if (typeof(callback) === 'function') callback(); }, animationTime); } animationStates = []; }, animate(target, currentRect, toRect, duration) { if (duration) { css(target, 'transition', ''); css(target, 'transform', ''); let elMatrix = matrix(this.el), scaleX = elMatrix && elMatrix.a, scaleY = elMatrix && elMatrix.d, translateX = (currentRect.left - toRect.left) / (scaleX || 1), translateY = (currentRect.top - toRect.top) / (scaleY || 1); target.animatingX = !!translateX; target.animatingY = !!translateY; css(target, 'transform', 'translate3d(' + translateX + 'px,' + translateY + 'px,0)'); this.forRepaintDummy = repaint(target); // repaint css(target, 'transition', 'transform ' + duration + 'ms' + (this.options.easing ? ' ' + this.options.easing : '')); css(target, 'transform', 'translate3d(0,0,0)'); (typeof target.animated === 'number') && clearTimeout(target.animated); target.animated = setTimeout(function () { css(target, 'transition', ''); css(target, 'transform', ''); target.animated = false; target.animatingX = false; target.animatingY = false; }, duration); } } }; } function repaint(target) { return target.offsetWidth; } function calculateRealTime(animatingRect, fromRect, toRect, options) { return ( Math.sqrt(Math.pow(fromRect.top - animatingRect.top, 2) + Math.pow(fromRect.left - animatingRect.left, 2)) / Math.sqrt(Math.pow(fromRect.top - toRect.top, 2) + Math.pow(fromRect.left - toRect.left, 2)) ) * options.animation; }