UNPKG

collide-motion

Version:

collide-motion --------------

284 lines (242 loc) 7.79 kB
var extend = require('node.extend'); var raf = require('raf'); var motion = require('./motion'); var easingFn = require('./easing-functions'); var time = Date.now || function() { return +new Date(); }; var desiredFrames = 60; var millisecondsPerSecond = 1000; var allowedEvents = [ 'pause', 'cancel', 'play', 'complete', //complete callback is passed boolean didFinish 'start' ]; module.exports = Animation; function Animation(opts) { extend(this, opts); } Animation.prototype = { clone: function() { return new Animation({ easing: this.easing, easingFn: this.easingFn, duration: this.duration, delay: this.delay, repeat: this.repeat, reverse: this.reverse, autoReverse: this.autoReverse, onComplete: this.onComplete, step: this.step }); }, easing: 'linear', easingFn: easingFn.linear, duration: 500, delay: 0, repeat: -1, reverse: false, autoReverse: false, onComplete: function(didComplete, droppedFrames) {}, // Overridable step: function(percent) {}, setPercent: function(percent, doesSetState) { this.pause(); var v = this.easingFn(percent); // Check if we should change any internal saved state (to resume // from this value later on, for example. Defaults to true) if(doesSetState !== false && this._pauseState) { // Not sure yet on this } this.step(v); //var value = easingMethod ? easingMethod(percent) : percent; }, stop: function() { this.isRunning = false; this.shouldEnd = true; }, play: function() { this.isPaused = false; if(this._lastStepFn) { this._unpausedAnimation = true; raf.cancel(this._rafId); this._rafId = raf(this._lastStepFn); } }, pause: function() { this.isPaused = true; }, _saveState: function(now, closure) { this._pauseState = { pausedAt: now, }; this._lastStepFn = closure; raf.cancel(this._rafId); }, restart: function() { var self = this; this.isRunning = false; // TODO: Verify this isn't totally stupid this._rafId = raf(function() { self.start(); }); }, start: function() { var self = this; // Set up the initial animation state var animState = { startPercent: this.reverse === true ? 1 : 0, endPercent: this.reverse === true ? 0 : 1, duration: this.duration, easingMethod: this.easingFn, delay: this.delay, reverse: this.reverse, repeat: this.repeat, autoReverse: this.autoReverse, dynamic: this.dynamic }; motion.animationStarted(this); return this._run(function(percent, now, render) { if(render) { self.step(percent); } }, function(droppedFrames, animationId, finishedAnimation) { motion.animationStopped(self); self.onComplete && self.onComplete(finishedAnimation, droppedFrames); console.log('Finished anim:', droppedFrames, finishedAnimation); }, animState); }, /** * Start the animation. * * @param stepCallback {Function} Pointer to function which is executed on every step. * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }` * @param completedCallback {Function} * Signature of the method should be `function(droppedFrames, finishedAnimation) {}` * @param duration {Integer} Milliseconds to run the animation * @param easingMethod {Function} Pointer to easing function * Signature of the method should be `function(percent) { return modifiedValue; }` * @return {Integer} Identifier of animation. Can be used to stop it any time. */ _run: function(stepCallback, completedCallback, state) { var self = this; var start = time(); var lastFrame = start; var startTime = start + state.delay; var percent = state.startPercent; var startPercent = state.startPercent; var endPercent = state.endPercent; var autoReverse = state.autoReverse; var delay = state.delay; var duration = state.duration; var easingMethod = state.easingMethod; var repeat = state.repeat; var reverse = state.reverse; var dropCounter = 0; var iteration = 0; var perhapsAutoreverse = function() { // Check if we hit the end and should auto reverse if(percent === endPercent && autoReverse) { // Flip the start and end values var sp = endPercent; reverse = !reverse; endPercent = startPercent; startPercent = sp; if(repeat === 0) { autoReverse = false; } } else { // Otherwise, just start over percent = startPercent; } // Start fresh either way start = time(); self._rafId = raf(step); }; // This is the internal step method which is called every few milliseconds var step = function(virtual) { var now = time(); if(self._unpausedAnimation) { // We unpaused. Increase the start time to account // for the gap in playback (to keep timing the same) var t = self._pauseState.pausedAt; start = start + (now - t); lastFrame = now; } // Normalize virtual value var render = virtual !== true; // Get current time var diff = now - start; // Verification is executed before next animation step if(self.isPaused) { self._saveState(now, step);//percent, iteration, reverse); return; } if (!self.isRunning) { completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), self._animationId, false); return; } // For the current rendering to apply let's update omitted steps in memory. // This is important to bring internal state variables up-to-date with progress in time. if (render) { var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1; if(self._unpausedAnimation) { console.log('After pausing', droppedFrames, 'Dropped frames'); } for (var j = 0; j < Math.min(droppedFrames, 4); j++) { console.log('drop step'); step(true); dropCounter++; } } // Compute percent value if (diff > delay && duration) { percent = (diff - delay) / duration; // If we are animating in the opposite direction, // the percentage is 1 minus this perc val if(reverse === true) { percent = 1 - percent; if (percent < 0) { percent = 0; } } else { if (percent > 1) { percent = 1; } } } self._unpausedAnimation = false; // Execute step callback, then... var value; if(state.dynamic) { value = state.dynamic.at(percent); } else { value = easingMethod ? easingMethod(percent) : percent; } if ((stepCallback(value, now, render) === false || percent === endPercent) && render) { if(repeat === -1) { perhapsAutoreverse(); } else if(iteration < repeat) { // Track iterations iteration++; perhapsAutoreverse(); } else if(repeat === 0 && autoReverse) { perhapsAutoreverse(); } else { completedCallback && completedCallback( desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), self._animationId, percent === endPercent || duration === null ); } } else if (render) { lastFrame = now; self._rafId = raf(step); } }; // Init first step self._rafId = raf(step); } };