feat(animation): Javascript Animation Service

This commit is contained in:
Max Lynch
2014-05-12 13:07:58 -05:00
parent da00f72ca3
commit 73d2eabc69
12 changed files with 7327 additions and 1 deletions

View File

@@ -64,7 +64,11 @@ module.exports = {
'js/controllers/sideMenuController.js',
// Animation
'js/animation/*.js'
'js/animation/animation.js',
'js/animation/bezier.js',
'js/animation/dynamics.js',
'js/animation/timing-functions.js',
'js/animation/instance.js'
],
angularIonicFiles: [

65
js/angular/service/animation.js vendored Normal file
View File

@@ -0,0 +1,65 @@
/**
* @ngdoc service
* @name $ionicAnimation
* @module ionic
* @description
*
* A powerful animation and transition system for Ionic apps.
*
* @usage
*
* ```js
* angular.module('mySuperApp', ['ionic'])
* .controller(function($scope, $ionicAnimation) {
* var anim = $ionicAnimate({
* // A unique, reusable name
* name: 'popIn',
*
* // The duration of an auto playthrough
* duration: 0.5,
*
* // How long to wait before running the animation
* delay: 0,
*
* // Whether to reverse after doing one run through
* autoReverse: false,
*
* // How many times to repeat? -1 or null for infinite
* repeat: -1,
*
* // Timing curve to use (same as CSS timing functions), or a function of time "t" to handle it yourself
* curve: 'ease-in-out'
*
* onStart: function() {
* // Callback on start
* },
* onEnd: function() {
* // Callback on end
* },
* step: function(amt) {
*
* }
* })
* });
* ```
*
*/
IonicModule
.provider('$ionicAnimation', function() {
var useSlowAnimations = false;
this.setSlowAnimations = function(isSlow) {
useSlowAnimations = isSlow;
};
this.create = function(animation) {
return ionic.Animation.create(animation);
};
this.$get = [function() {
return function(opts) {
opts.useSlowAnimations = useSlowAnimations;
return ionic.Animation.create(opts);
}
}]
});

117
js/animation/animation.js Normal file
View File

@@ -0,0 +1,117 @@
(function(window) {
var counter = 1;
var running = {};
// Namespace
ionic.Animation = ionic.Animation || {};
/**
* The main animation system manager. Treated as a singleton.
*/
ionic.Animation = {
create: function(opts) {
var tf;
if(typeof opts.curve === 'string') {
tf = ionic.Animation.TimingFn[opts.curve] || ionic.Animation.TimingFn['linear'];
if(opts.curve.indexOf('cubic-bezier(') >= 0) {
var parts = opts.curve.replace('cubic-bezier(', '').replace(')', '').split(',');
tf = ionic.Animation.TimingFn['cubic-bezier'];
tf = tf(parts[0], parts[1], parts[2], parts[3], opts.duration);
} else {
tf = tf(opts.duration);
}
} else {
tf = opts.curve;
tf = tf(opts.duration);
}
opts.curveFn = tf;
if(opts.dynamicsType) {
opts.dynamic = new opts.dynamicsType(opts);
}
return new ionic.Animation.Animation(opts);
},
animationStarted: function(instance) {
var id = counter++;
// Compacting running db automatically every few new animations
if (id % 20 === 0) {
var newRunning = {};
for (var usedId in running) {
newRunning[usedId] = true;
}
running = newRunning;
}
// Mark as running
running[id] = true;
instance.isRunning = true;
instance._animationId = id;
// Return unique animation ID
return id;
},
animationStopped: function(instance) {
instance.isRunning = false;
}
/* TODO: Move animation set management here instead of instance
anims: [],
add: function(animation) {
this.anims.push(animation);
},
remove: function(animation) {
var i, j;
for(i = 0, j = this.anims.length; i < j; i++) {
if(this.anims[i] === animation) {
return this.anims.splice(i, 1);
}
}
},
clear: function(shouldStop) {
while(this.anims.length) {
var anim = this.anims.pop();
if(shouldStop === true) {
anim.stop();
}
}
},
*/
/**
* Stops the given animation.
*
* @param id {Integer} Unique animation ID
* @return {Boolean} Whether the animation was stopped (aka, was running before)
* TODO: Requires above fix
stop: function(id) {
var cleared = running[id] != null;
if (cleared) {
running[id] = null;
}
return cleared;
},
*/
/**
* Whether the given animation is still running.
*
* @param id {Integer} Unique animation ID
* @return {Boolean} Whether the animation is still running
isRunning: function(id) {
return running[id] != null;
},
*/
};
})(window);

429
js/animation/bezier.js Normal file
View File

@@ -0,0 +1,429 @@
/*
* Copyright (C) 2008 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function(ionic) {
var bezierCoord = function (x,y) {
if(!x) x=0;
if(!y) y=0;
return {x: x, y: y};
};
function B1(t) { return t*t*t; }
function B2(t) { return 3*t*t*(1-t); }
function B3(t) { return 3*t*(1-t)*(1-t); }
function B4(t) { return (1-t)*(1-t)*(1-t); }
ionic.Animation = ionic.Animation || {}
/**
* JavaScript port of Webkit implementation of CSS cubic-bezier(p1x.p1y,p2x,p2y) by http://mck.me
* http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h
*/
ionic.Animation.Bezier = (function(){
'use strict';
/**
* Duration value to use when one is not specified (400ms is a common value).
* @const
* @type {number}
*/
var DEFAULT_DURATION = 400;//ms
/**
* The epsilon value we pass to UnitBezier::solve given that the animation is going to run over |dur| seconds.
* The longer the animation, the more precision we need in the timing function result to avoid ugly discontinuities.
* http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/page/animation/AnimationBase.cpp
*/
var solveEpsilon = function(duration) {
return 1.0 / (200.0 * duration);
};
/**
* Defines a cubic-bezier curve given the middle two control points.
* NOTE: first and last control points are implicitly (0,0) and (1,1).
* @param p1x {number} X component of control point 1
* @param p1y {number} Y component of control point 1
* @param p2x {number} X component of control point 2
* @param p2y {number} Y component of control point 2
*/
var unitBezier = function(p1x, p1y, p2x, p2y) {
// private members --------------------------------------------
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
/**
* X component of Bezier coefficient C
* @const
* @type {number}
*/
var cx = 3.0 * p1x;
/**
* X component of Bezier coefficient B
* @const
* @type {number}
*/
var bx = 3.0 * (p2x - p1x) - cx;
/**
* X component of Bezier coefficient A
* @const
* @type {number}
*/
var ax = 1.0 - cx -bx;
/**
* Y component of Bezier coefficient C
* @const
* @type {number}
*/
var cy = 3.0 * p1y;
/**
* Y component of Bezier coefficient B
* @const
* @type {number}
*/
var by = 3.0 * (p2y - p1y) - cy;
/**
* Y component of Bezier coefficient A
* @const
* @type {number}
*/
var ay = 1.0 - cy - by;
/**
* @param t {number} parametric timing value
* @return {number}
*/
var sampleCurveX = function(t) {
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((ax * t + bx) * t + cx) * t;
};
/**
* @param t {number} parametric timing value
* @return {number}
*/
var sampleCurveY = function(t) {
return ((ay * t + by) * t + cy) * t;
};
/**
* @param t {number} parametric timing value
* @return {number}
*/
var sampleCurveDerivativeX = function(t) {
return (3.0 * ax * t + 2.0 * bx) * t + cx;
};
/**
* Given an x value, find a parametric value it came from.
* @param x {number} value of x along the bezier curve, 0.0 <= x <= 1.0
* @param epsilon {number} accuracy limit of t for the given x
* @return {number} the t value corresponding to x
*/
var solveCurveX = function(x, epsilon) {
var t0;
var t1;
var t2;
var x2;
var d2;
var i;
// First try a few iterations of Newton's method -- normally very fast.
for (t2 = x, i = 0; i < 8; i++) {
x2 = sampleCurveX(t2) - x;
if (Math.abs (x2) < epsilon) {
return t2;
}
d2 = sampleCurveDerivativeX(t2);
if (Math.abs(d2) < 1e-6) {
break;
}
t2 = t2 - x2 / d2;
}
// Fall back to the bisection method for reliability.
t0 = 0.0;
t1 = 1.0;
t2 = x;
if (t2 < t0) {
return t0;
}
if (t2 > t1) {
return t1;
}
while (t0 < t1) {
x2 = sampleCurveX(t2);
if (Math.abs(x2 - x) < epsilon) {
return t2;
}
if (x > x2) {
t0 = t2;
} else {
t1 = t2;
}
t2 = (t1 - t0) * 0.5 + t0;
}
// Failure.
return t2;
};
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param epsilon {number} the accuracy of t for the given x
* @return {number} the y value along the bezier curve
*/
var solve = function(x, epsilon) {
return sampleCurveY(solveCurveX(x, epsilon));
};
// public interface --------------------------------------------
/**
* Find the y of the cubic-bezier for a given x with accuracy determined by the animation duration.
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
return function(x, duration) {
return solve(x, solveEpsilon(+duration || DEFAULT_DURATION));
};
};
// http://www.w3.org/TR/css3-transitions/#transition-timing-function
return {
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
linear: unitBezier(0.0, 0.0, 1.0, 1.0),
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
ease: unitBezier(0.25, 0.1, 0.25, 1.0),
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
easeIn: unitBezier(0.42, 0, 1.0, 1.0),
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
easeOut: unitBezier(0, 0, 0.58, 1.0),
/**
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
easeInOut: unitBezier(0.42, 0, 0.58, 1.0),
/**
* @param p1x {number} X component of control point 1
* @param p1y {number} Y component of control point 1
* @param p2x {number} X component of control point 2
* @param p2y {number} Y component of control point 2
* @param x {number} the value of x along the bezier curve, 0.0 <= x <= 1.0
* @param duration {number} the duration of the animation in milliseconds
* @return {number} the y value along the bezier curve
*/
cubicBezier: function(p1x, p1y, p2x, p2y) {
return unitBezier(p1x, p1y, p2x, p2y);
}
};
})();
/**
* Various fast approximations and alternates to cubic-bezier easing functions.
* http://www.w3.org/TR/css3-transitions/#transition-timing-function
*/
var Easing = (function(){
'use strict';
/**
* @const
*/
var EASE_IN_OUT_CONST = 0.5 * Math.pow(0.5, 1.925);
return {
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
linear: function(x) {
return x;
},
// /**
// * @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
// * @return {number} the y value along the curve
// */
// ease: function(x) {
// // TODO: find fast approximations
// return x;
// },
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInApprox: function(x) {
// very close approximation to cubic-bezier(0.42, 0, 1.0, 1.0)
return Math.pow(x, 1.685);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInQuadratic: function(x) {
return (x * x);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInCubic: function(x) {
return (x * x * x);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeOutApprox: function(x) {
// very close approximation to cubic-bezier(0, 0, 0.58, 1.0)
return 1 - Math.pow(1-x, 1.685);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeOutQuadratic: function(x) {
x -= 1;
return 1 - (x * x);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeOutCubic: function(x) {
x -= 1;
return 1 + (x * x * x);
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutApprox: function(x) {
// very close approximation to cubic-bezier(0.42, 0, 0.58, 1.0)
if (x < 0.5) {
return EASE_IN_OUT_CONST * Math.pow(x, 1.925);
} else {
return 1 - EASE_IN_OUT_CONST * Math.pow(1-x, 1.925);
}
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutQuadratic: function(x) {
if (x < 0.5) {
return (2 * x * x);
} else {
x -= 1;
return 1 - (2 * x * x);
}
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutCubic: function(x) {
if (x < 0.5) {
return (4 * x * x * x);
} else {
x -= 1;
return 1 + (4 * x * x * x);
}
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutQuartic: function(x) {
if (x < 0.5) {
return (8 * x * x * x * x);
} else {
x -= 1;
return 1 + (8 * x * x * x * x);
}
},
/**
* @param x {number} the value of x along the curve, 0.0 <= x <= 1.0
* @return {number} the y value along the curve
*/
easeInOutQuintic: function(x) {
if (x < 0.5) {
return (16 * x * x * x * x * x);
} else {
x -= 1;
return 1 + (16 * x * x * x * x * x);
}
}
};
})();
})(ionic);

180
js/animation/dynamics.js Normal file
View File

@@ -0,0 +1,180 @@
(function(window) {
/**
* A HUGE thank you to dynamics.js which inspired these dynamics simulations.
* https://github.com/michaelvillar/dynamics.js
*
* Also licensed under MIT
*/
// Namespace
ionic.Animation = ionic.Animation || {};
ionic.Animation.Dynamics = {};
ionic.Animation.Dynamics.Spring = function(opts) {
var defaults = {
frequency: 15,
friction: 200,
anticipationStrength: 0,
anticipationSize: 0
};
ionic.extend(this, defaults);
var maxs = {
frequency: 100,
friction: 1000,
anticipationStrength: 1000,
anticipationSize: 99
};
var mins = {
frequency: 0,
friction: 1,
anticipationStrength: 0,
anticipationSize: 0
};
ionic.extend(this, opts);
};
ionic.Animation.Dynamics.Spring.prototype = {
at: function(t) {
var A, At, a, angle, b, decal, frequency, friction, frictionT, s, v, y0, yS,
_this = this;
frequency = Math.max(1, this.frequency);
friction = Math.pow(20, this.friction / 100);
s = this.anticipationSize / 100;
decal = Math.max(0, s);
frictionT = (t / (1 - s)) - (s / (1 - s));
if (t < s) {
A = function(t) {
var M, a, b, x0, x1;
M = 0.8;
x0 = s / (1 - s);
x1 = 0;
b = (x0 - (M * x1)) / (x0 - x1);
a = (M - b) / x0;
return (a * t * _this.anticipationStrength / 100) + b;
};
yS = (s / (1 - s)) - (s / (1 - s));
y0 = (0 / (1 - s)) - (s / (1 - s));
b = Math.acos(1 / A(yS));
a = (Math.acos(1 / A(y0)) - b) / (frequency * (-s));
} else {
A = function(t) {
return Math.pow(friction / 10, -t) * (1 - t);
};
b = 0;
a = 1;
}
At = A(frictionT);
angle = frequency * (t - s) * a + b;
v = 1 - (At * Math.cos(angle));
//return [t, v, At, frictionT, angle];
return v;
}
}
ionic.Animation.Dynamics.Gravity = function(opts) {
this.options = {
bounce: 40,
gravity: 1000,
initialForce: false
};
ionic.extend(this.options, opts);
this.curves = [];
this.init();
};
ionic.Animation.Dynamics.Gravity.prototype = {
length: function() {
var L, b, bounce, curve, gravity;
bounce = Math.min(this.options.bounce / 100, 80);
gravity = this.options.gravity / 100;
b = Math.sqrt(2 / gravity);
curve = {
a: -b,
b: b,
H: 1
};
if (this.options.initialForce) {
curve.a = 0;
curve.b = curve.b * 2;
}
while (curve.H > 0.001) {
L = curve.b - curve.a;
curve = {
a: curve.b,
b: curve.b + L * bounce,
H: curve.H * bounce * bounce
};
}
return curve.b;
},
init: function() {
var L, b, bounce, curve, gravity, _results;
L = this.length();
gravity = (this.options.gravity / 100) * L * L;
bounce = Math.min(this.options.bounce / 100, 80);
b = Math.sqrt(2 / gravity);
this.curves = [];
curve = {
a: -b,
b: b,
H: 1
};
if (this.options.initialForce) {
curve.a = 0;
curve.b = curve.b * 2;
}
this.curves.push(curve);
_results = [];
while (curve.b < 1 && curve.H > 0.001) {
L = curve.b - curve.a;
curve = {
a: curve.b,
b: curve.b + L * bounce,
H: curve.H * bounce * bounce
};
_results.push(this.curves.push(curve));
}
return _results;
},
curve: function(a, b, H, t){
var L, c, t2;
L = b - a;
t2 = (2 / L) * t - 1 - (a * 2 / L);
c = t2 * t2 * H - H + 1;
if (this.initialForce) {
c = 1 - c;
}
return c;
},
at: function(t) {
var bounce, curve, gravity, i, v;
bounce = this.options.bounce / 100;
gravity = this.options.gravity;
i = 0;
curve = this.curves[i];
while (!(t >= curve.a && t <= curve.b)) {
i += 1;
curve = this.curves[i];
if (!curve) {
break;
}
}
if (!curve) {
v = this.options.initialForce ? 0 : 1;
} else {
v = this.curve(curve.a, curve.b, curve.H, t);
}
//return [t, v];
return v;
}
}
})(window);

4248
js/animation/gl-matrix.js Normal file
View File

File diff suppressed because it is too large Load Diff

277
js/animation/instance.js Normal file
View File

@@ -0,0 +1,277 @@
(function(window) {
var time = Date.now || function() {
return +new Date();
};
var desiredFrames = 60;
var millisecondsPerSecond = 1000;
// Namespace
ionic.Animation = ionic.Animation || {};
/**
* Animation instance
*/
ionic.Animation.Animation = function(opts) {
ionic.extend(this, opts);
if(opts.useSlowAnimations) {
console.warn('Running animation', opts.name, 'with SLOW animations (duration and delay increased by 3x)');
this.delay *= 3;
this.duration *= 3;
}
};
ionic.Animation.Animation.prototype = {
clone: function() {
return new ionic.Animation.Animation({
curve: this.curve,
curveFn: this.curveFn,
duration: this.duration,
delay: this.delay,
repeat: this.repeat,
reverse: this.reverse,
autoReverse: this.autoReverse,
onComplete: this.onComplete,
step: this.step
});
},
curve: 'linear',
curveFn: ionic.Animation.TimingFn['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.curveFn(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;
ionic.cancelAnimationFrame(this._lastStepFn);
ionic.requestAnimationFrame(this._lastStepFn);
}
},
pause: function() {
this.isPaused = true;
},
_saveState: function(now, closure) {
this._pauseState = {
pausedAt: now,
}
this._lastStepFn = closure;
window.cancelAnimationFrame(closure);
},
restart: function() {
var self = this;
this.isRunning = false;
// TODO: Verify this isn't totally stupid
ionic.requestAnimationFrame(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.curveFn,
delay: this.delay,
reverse: this.reverse,
repeat: this.repeat,
autoReverse: this.autoReverse,
dynamic: this.dynamic
}
ionic.Animation.animationStarted(this);
return this._run(function(percent, now, render) {
if(render) {
self.step(percent);
}
}, function(droppedFrames, finishedAnimation) {
ionic.Animation.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();
ionic.requestAnimationFrame(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) {// || (verifyCallback && !verifyCallback(id))) {
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;
ionic.requestAnimationFrame(step);
}
};
// Init first step
ionic.requestAnimationFrame(step);
}
};
})(window);

View File

@@ -0,0 +1,50 @@
(function(window) {
// Namespace
ionic.Animation = ionic.Animation || {};
ionic.Animation.TimingFn = {
'spring': function(duration) {
return function(t) {
return ionic.Animation.Dynamics.Spring(t, duration);
}
},
'gravity': function(duration) {
return function(t) {
return ionic.Animation.Dynamics.Gravity(t, duration);
}
},
'linear': function(duration) {
return function(t) {
return ionic.Animation.Bezier.linear(t, duration);
}
},
'ease': function(duration) {
return function(t) {
return ionic.Animation.Bezier.ease(t, duration);
}
},
'ease-in': function(duration) {
return function(t) {
return ionic.Animation.Bezier.easeIn(t, duration);
}
},
'ease-out': function(duration) {
return function(t) {
return ionic.Animation.Bezier.easeOut(t, duration);
}
},
'ease-in-out': function(duration) {
return function(t) {
return ionic.Animation.Bezier.easeInOut(t, duration);
}
},
'cubic-bezier': function(x1, y1, x2, y2, duration) {
var bz = ionic.Animation.Bezier.cubicBezier(x1, y1, x2, y2);//, t, duration);
return function(t) {
return bz(t, duration);
}
}
};
})(window);

View File

@@ -26,6 +26,15 @@
};
})();
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
/**
* @ngdoc utility
* @name ionic.DomUtil
@@ -45,6 +54,9 @@
window._rAF(cb);
},
cancelAnimationFrame: function(cb) {
},
/**
* @ngdoc method
* @name ionic.DomUtil#animationFrameThrottle
@@ -256,5 +268,6 @@
//Shortcuts
ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame;
ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame;
ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle;
})(window, document, ionic);

220
test/html/animation.html Normal file
View File

@@ -0,0 +1,220 @@
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title></title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="stylesheet" href="../../../../dist/css/ionic.css" />
<script src="../../../../dist/js/ionic.bundle.js"></script>
<script src="dynamics.js"></script>
<style>
.box {
position: absolute;
width: 100px;
height: 100px;
background-color: black;
}
#draggable {
top: 200px;
}
#opts {
position: absolute;
top: 400px;
left: 10px;
}
</style>
</head>
<body>
<div ng-controller="MyCtrl">
<div class="box"></div>
<div class="box" id="draggable"></div>
<div id="opts">
<label>
Duration:
<input type="number" ng-model="anim.duration">
</label>
<label>
Repeat
<input type="number" ng-model="anim.repeat">
</label>
<br>
<label>
Delay
<input type="number" ng-model="anim.delay">
</label>
<br>
<label>
Auto reverse
<input type="checkbox" ng-model="anim.autoReverse">
</label>
<br>
<label>
Reverse
<input type="checkbox" ng-model="anim.reverse">
</label>
<br>
<button ng-click="do('{{v}}')" ng-repeat="v in fns">{{v}}</button>
<button ng-click="do('spring')">Spring</button>
<button ng-click="do('gravity')">Gravity</button>
<p>
<input type="text" ng-model="anim.bx1">
<input type="text" ng-model="anim.by1">
<input type="text" ng-model="anim.bx2">
<input type="text" ng-model="anim.by2">
<button ng-click="do('cubic-bezier')">cubic-bezier</button>
</p>
<p>
<button ng-click="togglePause()">Toggle Pause/Play</button>
<button ng-click="stop()">Stop</button>
<button ng-click="restart()">Restart</button>
</p>
</div>
{{animationResult}}
</div>
<script>
angular.module('myApp', ['ionic'])
.config(['$ionicAnimationProvider', function($ionicAnimationProvider) {
var animOne = $ionicAnimationProvider.create({
name: 'fadeIn',
duration: 2000,
delay: 500,
autoReverse: false,
repeat: 0,
curve: 'ease',
onComplete: function(didComplete, droppedFrames) {
console.log('Done. Dropped ' + droppedFrames + '. Did complete? ' + didComplete);
},
step: function(v) {
console.log('Anim stepping', v);
//el[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + (v * 400) + 'px, 0,0)';
},
complete: function() {
console.log('Animation complete!');
}
});
animOne.start();
var ot = animOne.clone();
ot.step = function(v) {
console.log('A2', v);
};
ot.start();
}])
.controller('MyCtrl', function($scope, $ionicAnimation, $ionicGesture) {
var gestureEl = angular.element(document.getElementById('draggable'));
var dragAnim = $ionicAnimation({
curve: 'linear',
step: function(v) {
console.log(v);
gestureEl[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + (v * 400) + 'px, 0,0)';
}
});
$ionicGesture.on('drag', function(e) {
var x = e.gesture.touches[0].pageX;
dragAnim.setPercent(Math.max(0, Math.min(x, 400) / 400));
}, gestureEl);
var currentAnimation;
$scope.fns = [
'linear',
'ease',
'ease-in',
'ease-out',
'ease-in-out'
];
$scope.anim = {
duration: 2000,
delay: 0,
repeat: 0,
reverse: false,
autoReverse: false,
bx1: 0.17,
by1: 0.67,
bx2: 0.83,
by2: 0.67
}
var el = angular.element(document.querySelector('.box'));
$scope.do = function(fn) {
console.log(fn);
if(fn == 'cubic-bezier') {
fn = 'cubic-bezier(' +
$scope.anim.bx1 + ',' +
$scope.anim.by1 + ',' +
$scope.anim.bx2 + ',' +
$scope.anim.by2 + ')';
}
currentAnimation = angular.extend({
name: 'fadeIn',
duration: 2000,
delay: 500,
autoReverse: false,
repeat: 0,
curve: fn,
onComplete: function(didComplete, droppedFrames) {
console.log('Done. Dropped ' + droppedFrames + '. Did complete? ' + didComplete);
},
step: function(v) {
el[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + (v * 400) + 'px, 0,0)';
},
complete: function() {
console.log('Animation complete!');
}
}, $scope.anim);
if(fn == 'spring') {
currentAnimation = angular.extend(currentAnimation, {
dynamicsType: ionic.Animation.Dynamics.Spring,
frequency: 15,
friction: 200,
initialForce: false,
});
} else if (fn == 'gravity') {
currentAnimation = angular.extend(currentAnimation, {
dynamicsType: ionic.Animation.Dynamics.Gravity,
frequency: 15,
friction: 200,
initialForce: false,
});
}
currentAnimation = $ionicAnimation(currentAnimation);
$ionicGesture.on('drag', function(e) {
var x = e.gesture.touches[0].pageX;
currentAnimation.setPercent(Math.max(0, Math.min(x, 400) / 400));
}, angular.element(el));
$ionicGesture.on('release', function(e) {
currentAnimation.play();
}, angular.element(el));
currentAnimation.start();
}
$scope.togglePause = function() {
if(currentAnimation.isPaused) {
currentAnimation.play();
} else {
currentAnimation.pause();
}
};
$scope.stop = function() {
currentAnimation.stop();
};
$scope.restart = function() {
currentAnimation.restart();
};
});
</script>
</body>
</html>

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title></title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="stylesheet" href="../../../../dist/css/ionic.css" />
<script src="../../../../dist/js/ionic.bundle.js"></script>
<style>
#v1 {
background-color: blue;
}
#v2 {
background-color: red;
}
</style>
</head>
<body>
<div ng-controller="MyCtrl">
<ion-view id="v1">
<h2>Page 1</h2>
</ion-view>
<ion-view id="v2">
<h2>Page 2</h2>
</ion-view>
</div>
<script>
angular.module('myApp', ['ionic'])
.config(['$ionicAnimationProvider', function($ionicAnimationProvider) {
//$ionicAnimationProvider.setSlowAnimations(true);
}])
.controller('MyCtrl', function($scope, $ionicAnimation, $ionicGesture) {
var v1 = angular.element(document.getElementById('v1'));
var v2 = angular.element(document.getElementById('v2'));
var dragAnim = $ionicAnimation({
curve: 'ease-in-out',
duration: 1,
step: function(v) {
console.log(v);
v2[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + (v * 400) + 'px, 0,0)';
}
});
$ionicGesture.on('drag', function(e) {
var x = e.gesture.touches[0].pageX;
dragAnim.setPercent(Math.max(0, Math.min(x, 400) / 400));
}, v2);
});
</script>
</body>
</html>

1667
test/html/dynamics.js Normal file
View File

File diff suppressed because it is too large Load Diff