let there be web animations

This commit is contained in:
Adam Bradley
2015-05-19 16:24:11 -05:00
parent d91c4e75c8
commit b0e48d1709
20 changed files with 370 additions and 3309 deletions

View File

@ -25,7 +25,8 @@ gulp.task('build', function() {
'ionic.copy.js', 'ionic.copy.js',
'ionic.examples', 'ionic.examples',
'sass', 'sass',
'fonts'); 'fonts',
'polyfills');
}) })
gulp.task('watch', function() { gulp.task('watch', function() {
@ -36,6 +37,7 @@ gulp.task('watch', function() {
'ionic.examples', 'ionic.examples',
'sass', 'sass',
'fonts', 'fonts',
'polyfills',
function() { function() {
watch('ionic/**/*.js', function() { watch('ionic/**/*.js', function() {
@ -111,6 +113,12 @@ gulp.task('fonts', function() {
}); });
gulp.task('polyfills', function() {
return gulp.src('ionic/animations/web-animations*')
.pipe(gulp.dest('../angular-ionic/dist/js/dev/es5/polyfills'));
});
gulp.task('update.angular', function(done) { gulp.task('update.angular', function(done) {
if (!fs.existsSync('../angular-ionic')) { if (!fs.existsSync('../angular-ionic')) {

View File

@ -0,0 +1,268 @@
import * as util from 'ionic/util/util';
export class Animation {
constructor(el) {
this._el = [];
this._parent = null;
this._children = [];
this._players = [];
this._from = null;
this._to = null;
this._duration = null;
this._easing = null;
this._beforeAddCls = [];
this._beforeRmvCls = [];
this._afterAddCls = [];
this._afterRmvCls = [];
this.elements(el);
}
elements(el) {
if (el) {
if (typeof el === 'string') {
el = document.querySelectorAll(ele);
}
if (el.length) {
for (let i = 0; i < el.length; i++) {
this._el.push(el[i]);
}
} else if (el.nodeType) {
this._el.push(el);
}
}
return this;
}
parent(parentAnimation) {
this._parent = parentAnimation;
return this;
}
addChild(childAnimation) {
if (childAnimation) {
childAnimation.parent(this);
this._children.push(childAnimation);
}
return this;
}
children(arr) {
arr = Array.isArray(arr) ? arr : arguments;
for (let i = 0; i < arr.length; i++) {
this.addChild(arr[i]);
}
return this;
}
duration(value) {
if (arguments.length) {
this._duration = value;
return this;
}
return this._duration || (this._parent && this._parent.duration());
}
easing(value) {
if (arguments.length) {
this._easing = value;
return this;
}
return this._easing || (this._parent && this._parent.easing());
}
from(property, value) {
if (!this._from) {
this._from = {}
}
this._from[property] = value;
return this;
}
to(property, value) {
if (!this._to) {
this._to = {}
}
this._to[property] = value;
return this;
}
get beforePlay() {
return {
addClass: (className) => {
this._beforeAddCls.push(className);
return this;
},
removeClass: (className) => {
this._beforeRmvCls.push(className);
return this;
}
}
}
get afterFinish() {
return {
addClass: (className) => {
this._afterAddCls.push(className);
return this;
},
removeClass: (className) => {
this._afterRmvCls.push(className);
return this;
}
}
}
play() {
let promises = [];
for (let i = 0; i < this._children.length; i++) {
promises.push( this._children[i].play() );
}
if (!this._to) {
// probably just add/removing classes
// create bogus transition
this._from = this._to = {'opacity': 1};
}
if (!this._players.length) {
for (let i = 0; i < this._el.length; i++) {
var ele = this._el[i];
for (let j = 0; j < this._beforeAddCls.length; j++) {
ele.classList.add(this._beforeAddCls[j]);
}
for (let j = 0; j < this._beforeRmvCls.length; j++) {
ele.classList.remove(this._beforeRmvCls[j]);
}
var player = new Animate(ele, this._from, this._to, this.duration(), this.easing());
this._players.push(player);
promises.push(player.promise);
}
} else {
for (let i = 0; i < this._players.length; i++) {
this._players[i].play();
}
}
var promise = Promise.all(promises);
promise.then(() => {
for (let i = 0; i < this._el.length; i++) {
var ele = this._el[i];
for (let j = 0; j < this._afterAddCls.length; j++) {
ele.classList.add(this._afterAddCls[j]);
}
for (let j = 0; j < this._afterRmvCls.length; j++) {
ele.classList.remove(this._afterRmvCls[j]);
}
}
});
return promise;
}
pause() {
for (let i = 0; i < this._children.length; i++) {
this._children[i].pause();
}
for (let i = 0; i < this._players.length; i++) {
this._players[i].pause();
}
}
progress(value) {
for (let i = 0; i < this._children.length; i++) {
this._children[i].progress(value);
}
if (!this._players.length) {
this.play();
this.pause();
}
for (let i = 0; i < this._players.length; i++) {
this._players[i].progress(value);
}
}
}
class Animate {
constructor(ele, fromEffect, toEffect, duration, easing) {
// https://w3c.github.io/web-animations/
// not using the direct API methods because they're still in flux
// however, element.animate() seems locked in and uses the latest
// and correct API methods under the hood, so really doesn't matter
// fromEffect must be manually computed if it wasn't provided
// https://github.com/web-animations/web-animations-js/issues/14
fromEffect = fromEffect || {};
let style = null;
for (let prop in toEffect) {
if (!fromEffect[prop]) {
style = style || window.getComputedStyle(ele);
fromEffect[prop] = style[prop];
}
}
this._duration = duration;
this._easing = easing;
this.player = ele.animate([fromEffect, toEffect], {
duration: duration,
easing: easing,
fill: 'both'
});
this.promise = new Promise(resolve => {
this.player.onfinish = () => {
resolve();
};
});
}
play() {
this.player.play();
}
pause() {
this.player.pause();
}
progress(value) {
let player = this.player;
// passed a number between 0 and 1
value = Math.max(0, Math.min(1, parseFloat(value)));
if (value === 1) {
player.currentTime = (this._duration * 0.9999);
player.play();
return;
}
if (player.playState !== 'paused') {
player.pause();
}
player.currentTime = (this._duration * value);
}
}

17
ionic/animations/web-animations.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,135 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
import {Collide} from './collide'
import {completeCall} from './complete-call'
export function animationStop(elements, action) {
var customQueue = undefined;
var elementsLength = elements.length;
/******************
Action: Stop
*******************/
/* Clear the currently-active delay on each targeted element. */
for (var i = 0; i < elementsLength; i++) {
var eleData = Collide.data(elements[i]);
if (eleData && eleData.delayTimer) {
/* Stop the timer from triggering its cached next() function. */
clearTimeout(eleData.delayTimer.setTimeout);
/* Manually call the next() function so that the subsequent queue items can progress. */
if (eleData.delayTimer.next) {
eleData.delayTimer.next();
}
delete eleData.delayTimer;
}
}
var callsToStop = [];
/* When the stop action is triggered, the elements' currently active call is immediately stopped. The active call might have
been applied to multiple elements, in which case all of the call's elements will be stopped. When an element
is stopped, the next item in its animation queue is immediately triggered. */
/* An additional argument may be passed in to clear an element's remaining queued calls. Either true (which defaults to the 'fx' queue)
or a custom queue string can be passed in. */
/* Note: The stop command runs prior to Collide's Queueing phase since its behavior is intended to take effect *immediately*,
regardless of the element's current queue state. */
/* Iterate through every active call. */
for (var i = 0, callLength = Collide.State.calls.length; i < callLength; i++) {
/* Inactive calls are set to false by the logic inside completeCall(). Skip them. */
var activeCall = Collide.State.calls[i];
if (activeCall) {
/* Iterate through the active call's targeted elements. */
var activeElements = activeCall[1];
for (var j = 0, activeElementsLength = activeElements.length; j < activeElementsLength; j++) {
/* If true was passed in as a secondary argument, clear absolutely all calls on this element. Otherwise, only
clear calls associated with the relevant queue. */
/* Call stopping logic works as follows:
- customQueue === true --> stop current default queue calls (and queue:false calls), including remaining queued ones.
- customQueue === undefined --> stop current queue:'' call and all queue:false calls.
- customQueue === false --> stop only queue:false calls.
- customQueue === 'custom' --> stop current queue:'custom' call, including remaining queued ones (there is no functionality to only clear the currently-running queue:'custom' call). */
var queueName = (customQueue === undefined) ? '' : customQueue;
if (queueName !== true && (activeCall[2].queue !== queueName) && !(customQueue === undefined && activeCall[2].queue === false)) {
return true;
}
var activeElement = activeElements[j];
/* Iterate through the calls targeted by the stop command. */
for (var k = 0; k < elementsLength; k++) {
var element = elements[k];
/* Check that this call was applied to the target element. */
if (element === activeElement) {
/* Optionally clear the remaining queued calls. */
if (customQueue === true || typeof customQueue === 'string') {
/* Iterate through the items in the element's queue. */
var eleQueueName = typeof customQueue === 'string' ? customQueue : '';
var eleQueue = Collide.queue(element, eleQueueName);
for (var l = 0; l < eleQueue.length; l++) {
/* The queue array can contain an 'inprogress' string, which we skip. */
if (typeof eleQueue[l] === 'function') {
/* Pass the item's callback a flag indicating that we want to abort from the queue call.
(Specifically, the queue will resolve the call's associated promise then abort.) */
eleQueue[l](null, true);
}
}
/* Clearing the queue() array is achieved by resetting it to []. */
Collide.queue(element, eleQueueName, []);
}
if (action === 'stop') {
/* Since 'reverse' uses cached start values (the previous call's endValues), these values must be
changed to reflect the final value that the elements were actually tweened to. */
/* Note: If only queue:false animations are currently running on an element, it won't have a tweensContainer
object. Also, queue:false animations can't be reversed. */
var eleData = Collide.data(element);
if (eleData && eleData.tweensContainer && queueName !== false) {
for (var tweenName in eleData.tweensContainer) {
eleData.tweensContainer[tweenName].endValue = eleData.tweensContainer[tweenName].currentValue;
}
}
callsToStop.push(i);
} else if (action === 'finish') {
/* To get active tweens to finish immediately, we forcefully shorten their durations to 1ms so that
they finish upon the next rAf tick then proceed with normal call completion logic. */
activeCall[2].duration = 1;
}
}
}
}
}
} // END: for (var i = 0, l = Collide.State.calls.length; x < l; x++) {
/* Prematurely call completeCall() on each matched active call. Pass an additional flag for 'stop' to indicate
that the complete callback and display:none setting should be skipped since we're completing prematurely. */
if (action === 'stop') {
for (var i = 0; i < callsToStop.length; i++) {
completeCall(i, true);
}
}
};

View File

@ -1,340 +0,0 @@
import * as util from 'ionic/util/util'
import {Collide} from './collide'
import {processElement} from './process-element'
import {animationStop} from './animation-stop'
import {startTick} from './tick'
import {dom} from 'ionic/util'
const data = Collide.data;
export class Animation {
constructor(ele) {
this.parent = null;
this._options = {};
this._properties = {};
this._resolve = null;
this._call = null;
this.children = [];
this._addStartClasses = [];
this._removeStartClasses = [];
this._addEndClasses = [];
this._removeEndClasses = [];
this.elements(ele);
}
addChild(childAnimation) {
childAnimation.parent = this;
this.children.push(childAnimation);
return this;
}
setChildren(childAnimations) {
for (let i = 0; i < childAnimations.length; i++) {
this.addChild(childAnimations[i]);
}
return this;
}
elements(ele) {
this._ele = [];
if (ele) {
if (typeof ele === 'string') {
ele = document.querySelectorAll(ele);
}
if (ele.length) {
this._ele = ele;
} else if (ele.nodeType) {
this._ele.push(ele);
}
}
return this;
}
_prepare(clearCache, onNextFrame) {
// ensure another animation wasnt about to start
if (this._nextAF) {
dom.rafCancel(this._nextAF);
}
if (this.isAnimating()) {
this.stop();
}
if (this._ele.length) {
let promise = new Promise(res => {
this._resolve = res;
});
this._call = null;
// in the next frame, do all the DOM GETs to load element info
this._nextAF = dom.raf(() => {
/**********************************
Animation Call-Wide Variables
**********************************/
/* A container for CSS unit conversion ratios (e.g. %, rem, and em ==> px) that is used to cache ratios across all elements
being animated in a single Collide call. Calculating unit ratios necessitates DOM querying and updating, and is therefore
avoided (via caching) wherever possible. This container is call-wide instead of page-wide to avoid the risk of using stale
conversion metrics across Collide animations that are not immediately consecutively chained. */
this._unitConversion = {
lastParent: null,
lastPosition: null,
lastFontSize: null,
lastPercentToPxWidth: null,
lastPercentToPxHeight: null,
lastEmToPx: null,
remToPx: null,
vwToPx: null,
vhToPx: null
};
this._call = [];
var opts = util.extend({}, Collide.defaults);
if (this.parent) {
opts = util.extend(opts, this.parent._options);
}
this._options = util.extend(opts, this._options);
// get the elements ready
for (let i = 0, ii = this._ele.length; i < ii; i++) {
processElement('start', this, i, clearCache);
}
onNextFrame();
});
return promise;
}
return Promise.resolve();
}
_queue() {
if (this._ele.length) {
if (this._call === null) {
return;
}
var eleData;
for (var i = 0, ii = this._ele.length, element; i < ii && (element = this._ele[i]); i++) {
if (element) {
eleData = data(element);
if (eleData) {
eleData.isAnimating = true;
}
/*********************
Auto-Dequeuing
*********************/
/* To fire the first non-custom-queue entry on an element, the element
must be dequeued if its queue stack consists *solely* of the current call. (This can be determined by checking
for the 'inprogress' item that is prepended to active queue stack arrays.) Regardless, whenever the element's
queue is further appended with additional items -- including delay()'s calls, the queue's
first entry is automatically fired. This behavior contrasts that of custom queues, which never auto-fire. */
/* Note: When an element set is being subjected to a non-parallel Collide call, the animation will not begin until
each one of the elements in the set has reached the end of its individually pre-existing queue chain. */
if (this._options.queue === '' && Collide.queue(element)[0] !== 'inprogress') {
Collide.dequeue(element);
}
}
}
/* Push the call array onto Collide.State.calls for the animation tick to immediately begin processing. */
/* Add the current call plus its associated metadata (the element set and the call's options) onto the global call container.
Anything on this call container is subjected to tick() processing. */
Collide.State.calls.push([ this._call,
this._ele,
this._options,
null,
this._resolve ]);
}
startTick();
}
start() {
var promises = [];
for (var i = 0; i < this.children.length; i++) {
promises.push(
this.children[i].start()
);
}
var clearCache = (this._aniType !== 'start');
var promise = this._prepare(clearCache, () => {
this._aniType = 'start';
this._queue();
});
promises.push(promise);
return Promise.all(promises);
}
ready() {
var promises = [];
for (var i = 0; i < this.children.length; i++) {
promises.push(
this.children[i].ready()
);
}
var clearCache = (this._aniType !== 'progress');
var promise = this._prepare(clearCache, () => {
this._aniType = 'progress';
});
promises.push(promise);
return Promise.all(promises);
}
progress(value) {
// go to and stop at a specific point in the animation
if (this._aniType = 'progress') {
this._options.percentComplete = value;
for (var i = 0; i < this.children.length; i++) {
this.children[i].progress(value);
}
this._queue();
}
}
stop() {
// immediately stop where it's at
animationStop(this._ele, 'stop');
return this;
}
finish() {
// immediately go to the end of the animation
animationStop(this._ele, 'finish');
return this;
}
addStartClass(className) {
this._addStartClasses.push(className);
return this;
}
removeStartClass(className) {
this._removeStartClasses.push(className);
return this;
}
addEndClass(className) {
this._addEndClasses.push(className);
return this;
}
removeEndClass(className) {
this._removeEndClasses.push(className);
return this;
}
isAnimating() {
var eleData;
if (this._ele) {
for (var i = 0, ii = this._ele.length; i < ii; i++) {
eleData = data(this._ele[i]);
if (eleData && eleData.isAnimating) {
return true;
}
}
}
return false;
}
/************
Options
************/
options(val) {
this._options = val || {};
return this;
}
option(key, val) {
this._options[key] = val;
return this;
}
duration(val) {
this._options.duration = val;
return this;
}
easing(val) {
this._options.easing = val;
return this;
}
display(val) {
this._options.display = val;
return this;
}
/***************
Properties
***************/
from(propertyName, value) {
// [endValue, easing, startValue]
let prop = this.getProperty(propertyName);
prop[2] = value;
return this;
}
to() {
// [endValue, easing, startValue]
if (arguments.length > 1) {
let prop = this.getProperty(arguments[0]);
prop[0] = arguments[1];
} else {
this._properties = arguments[0] || {};
}
return this;
}
getProperty(propertyName) {
// [endValue, easing, startValue]
if (!this._properties[propertyName]) {
this._properties[propertyName] = [null, null, null];
}
return this._properties[propertyName];
}
/*********
Misc
*********/
debug(val) {
Collide.debug = !!val;
return this;
}
}

View File

@ -1,127 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
/***************************
Unit Ratio Calculation
***************************/
/* When queried, the browser returns (most) CSS property values in pixels. Therefore, if an endValue with a unit type of
%, em, or rem is animated toward, startValue must be converted from pixels into the same unit type as endValue in order
for value manipulation logic (increment/decrement) to proceed. Further, if the startValue was forcefed or transferred
from a previous call, startValue may also not be in pixels. Unit conversion logic therefore consists of two steps:
1) Calculating the ratio of %/em/rem/vh/vw relative to pixels
2) Converting startValue into the same unit of measurement as endValue based on these ratios. */
/* Unit conversion ratios are calculated by inserting a sibling node next to the target node, copying over its position property,
setting values with the target unit type then comparing the returned pixel value. */
/* Note: Even if only one of these unit types is being animated, all unit ratios are calculated at once since the overhead
of batching the SETs and GETs together upfront outweights the potential overhead
of layout thrashing caused by re-querying for uncalculated ratios for subsequently-processed properties. */
/* Todo: Shift this logic into the calls' first tick instance so that it's synced with RAF. */
export function calculateUnitRatios(element, callUnitConversionData) {
/**************************************************************
parsePropertyValue(), calculateUnitRatios(), Same Ratio Checks
**************************************************************/
/* The properties below are used to determine whether the element differs sufficiently from this call's
previously iterated element to also differ in its unit conversion ratios. If the properties match up with those
of the prior element, the prior element's conversion ratios are used. Like most optimizations in Collide,
this is done to minimize DOM querying. */
var sameRatioIndicators = {
myParent: element.parentNode || document.body, /* GET */
position: CSS.getPropertyValue(element, 'position'), /* GET */
fontSize: CSS.getPropertyValue(element, 'fontSize') /* GET */
};
/* Determine if the same % ratio can be used. % is based on the element's position value and its parent's width and height dimensions. */
var samePercentRatio = ((sameRatioIndicators.position === callUnitConversionData.lastPosition) && (sameRatioIndicators.myParent === callUnitConversionData.lastParent));
/* Determine if the same em ratio can be used. em is relative to the element's fontSize. */
var sameEmRatio = (sameRatioIndicators.fontSize === callUnitConversionData.lastFontSize);
/* Store these ratio indicators call-wide for the next element to compare against. */
callUnitConversionData.lastParent = sameRatioIndicators.myParent;
callUnitConversionData.lastPosition = sameRatioIndicators.position;
callUnitConversionData.lastFontSize = sameRatioIndicators.fontSize;
/**********************************************************************
parsePropertyValue(), calculateUnitRatios(), Element-Specific Units
**********************************************************************/
var measurement = 100,
unitRatios = {};
if (!sameEmRatio || !samePercentRatio) {
var dummy = data(element).isSVG ? document.createElementNS('http://www.w3.org/2000/svg', 'rect') : document.createElement('div');
Collide.init(dummy);
sameRatioIndicators.myParent.appendChild(dummy);
/* To accurately and consistently calculate conversion ratios, the element's cascaded overflow and box-sizing are stripped.
Similarly, since width/height can be artificially constrained by their min-/max- equivalents, these are controlled for as well. */
/* Note: Overflow must be also be controlled for per-axis since the overflow property overwrites its per-axis values. */
var cssPropNames = [ 'overflow', 'overflowX', 'overflowY' ];
for (var i = 0; i < overflows.length; i++) {
Collide.CSS.setPropertyValue(dummy, cssPropNames[i], 'hidden');
}
Collide.CSS.setPropertyValue(dummy, 'position', sameRatioIndicators.position);
Collide.CSS.setPropertyValue(dummy, 'fontSize', sameRatioIndicators.fontSize);
Collide.CSS.setPropertyValue(dummy, 'boxSizing', 'content-box');
/* width and height act as our proxy properties for measuring the horizontal and vertical % ratios. */
cssPropNames = [ 'minWidth', 'maxWidth', 'width', 'minHeight', 'maxHeight', 'height' ];
for (var i = 0; i < overflows.length; i++) {
Collide.CSS.setPropertyValue(dummy, cssPropNames[i], measurement + '%');
}
/* paddingLeft arbitrarily acts as our proxy property for the em ratio. */
Collide.CSS.setPropertyValue(dummy, 'paddingLeft', measurement + 'em');
/* Divide the returned value by the measurement to get the ratio between 1% and 1px. Default to 1 since working with 0 can produce Infinite. */
unitRatios.percentToPxWidth = callUnitConversionData.lastPercentToPxWidth = (parseFloat(CSS.getPropertyValue(dummy, 'width', null, true)) || 1) / measurement; /* GET */
unitRatios.percentToPxHeight = callUnitConversionData.lastPercentToPxHeight = (parseFloat(CSS.getPropertyValue(dummy, 'height', null, true)) || 1) / measurement; /* GET */
unitRatios.emToPx = callUnitConversionData.lastEmToPx = (parseFloat(CSS.getPropertyValue(dummy, 'paddingLeft')) || 1) / measurement; /* GET */
sameRatioIndicators.myParent.removeChild(dummy);
} else {
unitRatios.emToPx = callUnitConversionData.lastEmToPx;
unitRatios.percentToPxWidth = callUnitConversionData.lastPercentToPxWidth;
unitRatios.percentToPxHeight = callUnitConversionData.lastPercentToPxHeight;
}
/**********************************************************************
parsePropertyValue(), calculateUnitRatios(), Element-Agnostic Units
***********************************************************************/
/* Whereas % and em ratios are determined on a per-element basis, the rem unit only needs to be checked
once per call since it's exclusively dependant upon document.body's fontSize. If this is the first time
that calculateUnitRatios() is being run during this call, remToPx will still be set to its default value of null,
so we calculate it now. */
if (callUnitConversionData.remToPx === null) {
/* Default to browsers' default fontSize of 16px in the case of 0. */
if (!remToPx) {
remToPx = parseFloat(CSS.getPropertyValue(document.body, 'fontSize')) || 16; /* GET */
}
callUnitConversionData.remToPx = remToPx
}
/* Similarly, viewport units are %-relative to the window's inner dimensions. */
if (callUnitConversionData.vwToPx === null) {
callUnitConversionData.vwToPx = parseFloat(window.innerWidth) / 100; /* GET */
callUnitConversionData.vhToPx = parseFloat(window.innerHeight) / 100; /* GET */
}
unitRatios.remToPx = callUnitConversionData.remToPx;
unitRatios.vwToPx = callUnitConversionData.vwToPx;
unitRatios.vhToPx = callUnitConversionData.vhToPx;
if (Collide.debug >= 1) console.log('Unit ratios: ' + JSON.stringify(unitRatios), element);
return unitRatios;
};
let remToPx = null;

View File

@ -1,173 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
import {dom} from 'ionic/util'
export let Collide = {
/* Container for page-wide Collide state data. */
State: {
/* Create a cached element for re-use when checking for CSS property prefixes. */
prefixElement: document.createElement('div'),
/* Cache every prefix match to avoid repeating lookups. */
prefixMatches: {},
/* Cache the anchor used for animating window scrolling. */
scrollAnchor: null,
/* Cache the browser-specific property names associated with the scroll anchor. */
scrollPropertyLeft: null,
scrollPropertyTop: null,
/* Keep track of whether our RAF tick is running. */
isTicking: false,
/* Container for every in-progress call to Collide. */
calls: []
},
CSS: {},
Easings: {},
/* Collide option defaults, which can be overriden by the user. */
defaults: {
queue: '',
duration: 400,
easing: 'swing',
begin: undefined,
complete: undefined,
percentComplete: undefined,
percent: undefined,
display: undefined,
visibility: undefined,
loop: false,
delay: false,
/* Advanced: Set to false to prevent property values from being cached between consecutive Collide-initiated chain calls. */
_cacheValues: true
},
/* Used for getting/setting Collide's hooked CSS properties. */
hook: null, /* Defined below. */
/* Collide-wide animation time remapping for testing purposes. */
mock: false,
/* Set to 1 or 2 (most verbose) to output debug info to console. */
debug: false,
/* initialize element data */
initData: function(element) {
element.$collide = {
/* Store whether this is an SVG element, since its properties are retrieved and updated differently than standard HTML elements. */
isSVG: dom.isSVG(element),
/* Keep track of whether the element is currently being animated by Collide.
This is used to ensure that property values are not transferred between non-consecutive (stale) calls. */
isAnimating: false,
/* A reference to the element's live computedStyle object. Learn more here: https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle */
computedStyle: null,
/* Tween data is cached for each animation on the element so that data can be passed across calls --
in particular, end values are used as subsequent start values in consecutive Collide calls. */
tweensContainer: null,
/* The full root property values of each CSS hook being animated on this element are cached so that:
1) Concurrently-animating hooks sharing the same root can have their root values' merged into one while tweening.
2) Post-hook-injection root values can be transferred over to consecutively chained Collide calls as starting root values. */
rootPropertyValueCache: {},
/* A cache for transform updates, which must be manually flushed via CSS.flushTransformCache(). */
transformCache: {},
startAddCls: null,
startRmvCls: null,
endAddCls: null,
endRmvCls: null
};
return element.$collide;
},
/* get/set element data */
data: function(element, key, value) {
if (value === undefined) {
if (key === undefined) {
// get data object: Data(element)
return element.$collide;
}
if (element.$collide) {
// get data by key: Data(element, key)
return element.$collide[key];
}
} else if (key !== undefined) {
// set data: Data(element, key, value)
if (!element.$collide) {
element.$collide = {};
}
element.$collide[key] = value;
}
},
/* get/set element queue data */
queue: function (element, type, data) {
function $makeArray (arr, results) {
let ret = results || [];
if (arr != null) {
[].push.call(ret, arr);
}
return ret;
}
if (!element) {
return;
}
type = (type || 'collide') + 'queue';
var q = this.data(element, type);
if (!data) {
return q || [];
}
if (!q || Array.isArray(data)) {
q = this.data(element, type, $makeArray(data));
} else {
q.push(data);
}
return q;
},
/* dequeue element */
dequeue: function (element, type) {
type = type || 'collide';
let queue = this.queue(element, type);
let fn = queue.shift();
if (fn === 'inprogress') {
fn = queue.shift();
}
if (fn) {
if (type === 'collide') {
queue.unshift('inprogress');
}
fn.call(element, () => {
this.dequeue(element, type);
});
}
}
};

View File

@ -1,186 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
import {Collide} from './collide'
import {CSS} from './css'
/***************************
Call Completion
***************************/
/* Note: Unlike tick(), which processes all active calls at once, call completion is handled on a per-call basis. */
export function completeCall(callIndex, isStopped) {
/* Ensure the call exists. */
if (!Collide.State.calls[callIndex]) {
return false;
}
/* Pull the metadata from the call. */
var call = Collide.State.calls[callIndex][0],
elements = Collide.State.calls[callIndex][1],
opts = Collide.State.calls[callIndex][2],
resolve = Collide.State.calls[callIndex][4];
var remainingCallsExist = false;
/*************************
Element Finalization
*************************/
for (var i = 0, callLength = call.length; i < callLength; i++) {
var element = call[i].element;
var eleData = Collide.data(element);
/* If the user set display to 'none' (intending to hide the element), set it now that the animation has completed. */
/* Note: display:none isn't set when calls are manually stopped (via Collide('stop'). */
/* Note: Display gets ignored with 'reverse' calls and infinite loops, since this behavior would be undesirable. */
if (!isStopped && !opts.loop) {
if (opts.display === 'none') {
CSS.setPropertyValue(element, 'display', opts.display);
}
if (opts.visibility === 'hidden') {
CSS.setPropertyValue(element, 'visibility', opts.visibility);
}
}
if (eleData.endAddCls) {
for (var k = 0; k < eleData.endAddCls.length; k++) {
element.classList.add(eleData.endAddCls[k]);
}
eleData.endAddCls = null;
}
if (eleData.endRmvCls) {
for (var k = 0; k < eleData.endRmvCls.length; k++) {
element.classList.remove(eleData.endRmvCls[k]);
}
eleData.endRmvCls = null;
}
/* If the element's queue is empty (if only the 'inprogress' item is left at position 0) or if its queue is about to run
a non-Collide-initiated entry, turn off the isAnimating flag. A non-Collide-initiatied queue entry's logic might alter
an element's CSS values and thereby cause Collide's cached value data to go stale. To detect if a queue entry was initiated by Collide,
we check for the existence of our special Collide.queueEntryFlag declaration, which minifiers won't rename since the flag
is assigned to's global object and thus exists out of Collide's own scope. */
if (opts.loop !== true && (Collide.queue(element)[1] === undefined || !/\.collideQueueEntryFlag/i.test(Collide.queue(element)[1]))) {
/* The element may have been deleted. Ensure that its data cache still exists before acting on it. */
if (eleData) {
eleData.isAnimating = false;
/* Clear the element's rootPropertyValueCache, which will become stale. */
eleData.rootPropertyValueCache = {};
/* If any 3D transform subproperty is at its default value (regardless of unit type), remove it. */
for (var j = 0, jj = CSS.Lists.transforms3D.length; j < jj; j++) {
var transformName = CSS.Lists.transforms3D[j];
var defaultValue = /^scale/.test(transformName) ? 1 : 0;
var currentValue = eleData.transformCache[transformName];
if (currentValue !== undefined && new RegExp('^\\(' + defaultValue + '[^.]').test(currentValue)) {
delete eleData.transformCache[transformName];
}
}
/* Flush the subproperty removals to the DOM. */
CSS.flushTransformCache(element);
}
}
/*********************
Option: Complete
*********************/
/* Complete is fired once per call (not once per element) and is passed the full raw DOM element set as both its context and its first argument. */
/* Note: Callbacks aren't fired when calls are manually stopped (via Collide('stop'). */
if (!isStopped && opts.complete && !opts.loop && (i === callLength - 1)) {
/* We throw callbacks in a setTimeout so that thrown errors don't halt the execution of Collide itself. */
try {
opts.complete.call(elements, elements);
} catch (error) {
setTimeout(function() { throw error; });
}
}
/**********************
Promise Resolving
**********************/
/* Note: Infinite loops don't return promises. */
if (resolve && opts.loop !== true) {
resolve(elements);
}
/****************************
Option: Loop (Infinite)
****************************/
if (eleData && opts.loop === true && !isStopped) {
/* If a rotateX/Y/Z property is being animated to 360 deg with loop:true, swap tween start/end values to enable
continuous iterative rotation looping. (Otherise, the element would just rotate back and forth.) */
for (var propertyName in eleData.tweensContainer) {
var tweenContainer = eleData.tweensContainer[propertyName]
if (/^rotate/.test(propertyName) && parseFloat(tweenContainer.endValue) === 360) {
tweenContainer.endValue = 0;
tweenContainer.startValue = 360;
}
if (/^backgroundPosition/.test(propertyName) && parseFloat(tweenContainer.endValue) === 100 && tweenContainer.unitType === '%') {
tweenContainer.endValue = 0;
tweenContainer.startValue = 100;
}
}
//TODO!!! FIXME!!!
//Collide(element, 'reverse', { loop: true, delay: opts.delay });
}
/***************
Dequeueing
***************/
/* Fire the next call in the queue so long as this call's queue wasn't set to false (to trigger a parallel animation),
which would have already caused the next call to fire. Note: Even if the end of the animation queue has been reached,
Collide.dequeue() must still be called in order to completely clear animation queue. */
if (opts.queue !== false) {
Collide.dequeue(element, opts.queue);
}
} // END: for (var i = 0, callLength = call.length; i < callLength; i++)
/***********************
Calls Array Cleanup
************************/
/* Since this call is complete, set it to false so that the rAF tick skips it. This array is later compacted via compactSparseArray().
(For performance reasons, the call is set to false instead of being deleted from the array: http://www.html5rocks.com/en/tutorials/speed/v8/) */
Collide.State.calls[callIndex] = false;
/* Iterate through the calls array to determine if this was the final in-progress animation.
If so, set a flag to end ticking and clear the calls array. */
for (var j = 0, jj = Collide.State.calls.length; j < jj; j++) {
if (Collide.State.calls[j] !== false && Collide.State.calls[j][0]) {
remainingCallsExist = true;
break;
}
}
if (remainingCallsExist === false) {
/* tick() will detect this flag upon its next iteration and subsequently turn itself off. */
Collide.State.isTicking = false;
/* Clear the calls array so that its length is reset. */
delete Collide.State.calls;
Collide.State.calls = [];
}
}

View File

@ -1,915 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
import {Collide} from './collide'
const data = Collide.data;
/*****************
CSS Stack
*****************/
/* The CSS object handles the validation, getting, and setting of both standard
CSS properties and CSS property hooks. */
export var CSS = {
/*************
CSS RegEx
*************/
RegEx: {
isHex: /^#([A-f\d]{3}){1,2}$/i,
/* Unwrap a property value's surrounding text, e.g. 'rgba(4, 3, 2, 1)' ==> '4, 3, 2, 1' and 'rect(4px 3px 2px 1px)' ==> '4px 3px 2px 1px'. */
valueUnwrap: /^[A-z]+\((.*)\)$/i,
wrappedValueAlreadyExtracted: /[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,
/* Split a multi-value property into an array of subvalues, e.g. 'rgba(4, 3, 2, 1) 4px 3px 2px 1px' ==> [ 'rgba(4, 3, 2, 1)', '4px', '3px', '2px', '1px' ]. */
valueSplit: /([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/ig
},
/************
CSS Lists
************/
Lists: {
colors: [ 'fill', 'stroke', 'stopColor', 'color', 'backgroundColor', 'borderColor', 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor', 'outlineColor' ],
transformsBase: [ 'translateX', 'translateY', 'scale', 'scaleX', 'scaleY', 'skewX', 'skewY', 'rotateZ' ],
transforms3D: [ 'transformPerspective', 'translateZ', 'scaleZ', 'rotateX', 'rotateY' ]
},
/************
CSS Hooks
************/
/* Hooks allow a subproperty (e.g. 'boxShadowBlur') of a compound-value CSS property
(e.g. 'boxShadow: X Y Blur Spread Color') to be animated as if it were a discrete property. */
/* Note: Beyond enabling fine-grained property animation, hooking is necessary since Collide only
tweens properties with single numeric values; unlike CSS transitions, Collide does not interpolate compound-values. */
Hooks: {
/********************
CSS Hook Registration
********************/
/* Templates are a concise way of indicating which subproperties must be individually registered for each compound-value CSS property. */
/* Each template consists of the compound-value's base name, its constituent subproperty names, and those subproperties' default values. */
templates: {
textShadow: [ 'Color X Y Blur', 'black 0px 0px 0px' ],
boxShadow: [ 'Color X Y Blur Spread', 'black 0px 0px 0px 0px' ],
clip: [ 'Top Right Bottom Left', '0px 0px 0px 0px' ],
backgroundPosition: [ 'X Y', '0% 0%' ],
transformOrigin: [ 'X Y Z', '50% 50% 0px' ],
perspectiveOrigin: [ 'X Y', '50% 50%' ]
},
/* A 'registered' hook is one that has been converted from its template form into a live,
tweenable property. It contains data to associate it with its root property. */
registered: {
/* Note: A registered hook looks like this ==> textShadowBlur: [ 'textShadow', 3 ],
which consists of the subproperty's name, the associated root property's name,
and the subproperty's position in the root's value. */
},
/* Convert the templates into individual hooks then append them to the registered object above. */
register: function() {
/* Color hooks registration: Colors are defaulted to white -- as opposed to black -- since colors that are
currently set to 'transparent' default to their respective template below when color-animated,
and white is typically a closer match to transparent than black is. An exception is made for text ('color'),
which is almost always set closer to black than white. */
for (var i = 0; i < CSS.Lists.colors.length; i++) {
var rgbComponents = (CSS.Lists.colors[i] === 'color') ? '0 0 0 1' : '255 255 255 1';
CSS.Hooks.templates[CSS.Lists.colors[i]] = [ 'Red Green Blue Alpha', rgbComponents ];
}
var rootProperty,
hookTemplate,
hookNames;
/* Hook registration. */
for (rootProperty in CSS.Hooks.templates) {
hookTemplate = CSS.Hooks.templates[rootProperty];
hookNames = hookTemplate[0].split(' ');
for (var i in hookNames) {
var fullHookName = rootProperty + hookNames[i],
hookPosition = i;
/* For each hook, register its full name (e.g. textShadowBlur) with its root property (e.g. textShadow)
and the hook's position in its template's default value string. */
CSS.Hooks.registered[fullHookName] = [ rootProperty, hookPosition ];
}
}
},
/*****************************
CSS Hook Injection and Extraction
*****************************/
/* Look up the root property associated with the hook (e.g. return 'textShadow' for 'textShadowBlur'). */
/* Since a hook cannot be set directly (the browser won't recognize it), style updating for hooks is routed through the hook's root property. */
getRoot: function(property) {
var hookData = CSS.Hooks.registered[property];
if (hookData) {
return hookData[0];
}
/* If there was no hook match, return the property name untouched. */
return property;
},
/* Convert any rootPropertyValue, null or otherwise, into a space-delimited list of hook values so that
the targeted hook can be injected or extracted at its standard position. */
cleanRootPropertyValue: function(rootProperty, rootPropertyValue) {
/* If the rootPropertyValue is wrapped with 'rgb()', 'clip()', etc., remove the wrapping to normalize the value before manipulation. */
if (CSS.RegEx.valueUnwrap.test(rootPropertyValue)) {
rootPropertyValue = rootPropertyValue.match(CSS.RegEx.valueUnwrap)[1];
}
/* If rootPropertyValue is a CSS null-value (from which there's inherently no hook value to extract),
default to the root's default value as defined in CSS.Hooks.templates. */
/* Note: CSS null-values include 'none', 'auto', and 'transparent'. They must be converted into their
zero-values (e.g. textShadow: 'none' ==> textShadow: '0px 0px 0px black') for hook manipulation to proceed. */
if (CSS.Values.isCSSNullValue(rootPropertyValue)) {
rootPropertyValue = CSS.Hooks.templates[rootProperty][1];
}
return rootPropertyValue;
},
/* Extracted the hook's value from its root property's value. This is used to get the starting value of an animating hook. */
extractValue: function(fullHookName, rootPropertyValue) {
var hookData = CSS.Hooks.registered[fullHookName];
if (hookData) {
var hookRoot = hookData[0],
hookPosition = hookData[1];
rootPropertyValue = CSS.Hooks.cleanRootPropertyValue(hookRoot, rootPropertyValue);
/* Split rootPropertyValue into its constituent hook values then grab the desired hook at its standard position. */
return rootPropertyValue.toString().match(CSS.RegEx.valueSplit)[hookPosition];
}
/* If the provided fullHookName isn't a registered hook, return the rootPropertyValue that was passed in. */
return rootPropertyValue;
},
/* Inject the hook's value into its root property's value. This is used to piece back together the root property
once Collide has updated one of its individually hooked values through tweening. */
injectValue: function(fullHookName, hookValue, rootPropertyValue) {
var hookData = CSS.Hooks.registered[fullHookName];
if (hookData) {
var hookRoot = hookData[0],
hookPosition = hookData[1],
rootPropertyValueParts,
rootPropertyValueUpdated;
rootPropertyValue = CSS.Hooks.cleanRootPropertyValue(hookRoot, rootPropertyValue);
/* Split rootPropertyValue into its individual hook values, replace the targeted value with hookValue,
then reconstruct the rootPropertyValue string. */
rootPropertyValueParts = rootPropertyValue.toString().match(CSS.RegEx.valueSplit);
rootPropertyValueParts[hookPosition] = hookValue;
rootPropertyValueUpdated = rootPropertyValueParts.join(' ');
return rootPropertyValueUpdated;
}
/* If the provided fullHookName isn't a registered hook, return the rootPropertyValue that was passed in. */
return rootPropertyValue;
}
},
/*******************
CSS Normalizations
*******************/
/* Normalizations standardize CSS property manipulation by pollyfilling browser-specific implementations (e.g. opacity)
and reformatting special properties (e.g. clip, rgba) to look like standard ones. */
Normalizations: {
/* Normalizations are passed a normalization target (either the property's name, its extracted value, or its injected value),
the targeted element (which may need to be queried), and the targeted property value. */
registered: {
clip: function(type, element, propertyValue) {
switch (type) {
case 'name':
return 'clip';
/* Clip needs to be unwrapped and stripped of its commas during extraction. */
case 'extract':
var extracted;
/* If Collide also extracted this value, skip extraction. */
if (CSS.RegEx.wrappedValueAlreadyExtracted.test(propertyValue)) {
extracted = propertyValue;
} else {
/* Remove the 'rect()' wrapper. */
extracted = propertyValue.toString().match(CSS.RegEx.valueUnwrap);
/* Strip off commas. */
extracted = extracted ? extracted[1].replace(/,(\s+)?/g, ' ') : propertyValue;
}
return extracted;
/* Clip needs to be re-wrapped during injection. */
case 'inject':
return 'rect(' + propertyValue + ')';
}
},
blur: function(type, element, propertyValue) {
switch (type) {
case 'name':
return '-webkit-filter';
case 'extract':
var extracted = parseFloat(propertyValue);
/* If extracted is NaN, meaning the value isn't already extracted. */
if (!(extracted || extracted === 0)) {
var blurComponent = propertyValue.toString().match(/blur\(([0-9]+[A-z]+)\)/i);
/* If the filter string had a blur component, return just the blur value and unit type. */
if (blurComponent) {
extracted = blurComponent[1];
/* If the component doesn't exist, default blur to 0. */
} else {
extracted = 0;
}
}
return extracted;
/* Blur needs to be re-wrapped during injection. */
case 'inject':
/* For the blur effect to be fully de-applied, it needs to be set to 'none' instead of 0. */
if (!parseFloat(propertyValue)) {
return 'none';
}
return 'blur(' + propertyValue + ')';
}
},
opacity: function(type, element, propertyValue) {
switch (type) {
case 'name':
return 'opacity';
case 'extract':
return propertyValue;
case 'inject':
return propertyValue;
}
}
},
/*****************************
CSS Batched Registrations
*****************************/
/* Note: Batched normalizations extend the CSS.Normalizations.registered object. */
register: function() {
/****************************************
CSS Batched Registration Transforms
****************************************/
/* Transforms are the subproperties contained by the CSS 'transform' property. Transforms must undergo normalization
so that they can be referenced in a properties map by their individual names. */
/* Note: When transforms are 'set', they are actually assigned to a per-element transformCache. When all transform
setting is complete complete, CSS.flushTransformCache() must be manually called to flush the values to the DOM.
Transform setting is batched in this way to improve performance: the transform style only needs to be updated
once when multiple transform subproperties are being animated simultaneously. */
for (var i = 0; i < CSS.Lists.transformsBase.length; i++) {
/* Wrap the dynamically generated normalization function in a new scope so that transformName's value is
paired with its respective function. (Otherwise, all functions would take the final for loop's transformName.) */
(function() {
var transformName = CSS.Lists.transformsBase[i];
CSS.Normalizations.registered[transformName] = function(type, element, propertyValue) {
var eleData = data(element);
switch (type) {
/* The normalized property name is the parent 'transform' property -- the property that is actually set in CSS. */
case 'name':
return 'transform';
/* Transform values are cached onto a per-element transformCache object. */
case 'extract':
/* If this transform has yet to be assigned a value, return its null value. */
if (eleData === undefined || eleData.transformCache[transformName] === undefined) {
/* Scale CSS.Lists.transformsBase default to 1 whereas all other transform properties default to 0. */
return /^scale/i.test(transformName) ? 1 : 0;
}
/* When transform values are set, they are wrapped in parentheses as per the CSS spec.
Thus, when extracting their values (for tween calculations), we strip off the parentheses. */
return eleData.transformCache[transformName].replace(/[()]/g, '');
case 'inject':
var invalid = false;
/* If an individual transform property contains an unsupported unit type, the browser ignores the *entire* transform property.
Thus, protect users from themselves by skipping setting for transform values supplied with invalid unit types. */
/* Switch on the base transform type; ignore the axis by removing the last letter from the transform's name. */
switch (transformName.substr(0, transformName.length - 1)) {
/* Whitelist unit types for each transform. */
case 'translate':
invalid = !/(%|px|em|rem|vw|vh|\d)$/i.test(propertyValue);
break;
/* Since an axis-free 'scale' property is supported as well, a little hack is used here to detect it by chopping off its last letter. */
case 'scal':
case 'scale':
invalid = !/(\d)$/i.test(propertyValue);
break;
case 'skew':
invalid = !/(deg|\d)$/i.test(propertyValue);
break;
case 'rotate':
invalid = !/(deg|\d)$/i.test(propertyValue);
break;
}
if (!invalid) {
/* As per the CSS spec, wrap the value in parentheses. */
eleData.transformCache[transformName] = '(' + propertyValue + ')';
}
/* Although the value is set on the transformCache object, return the newly-updated value for the calling code to process as normal. */
return eleData.transformCache[transformName];
}
};
})();
}
/************************************
CSS Batched Registration Colors
************************************/
/* Since Collide only animates a single numeric value per property, color animation is achieved by hooking the individual RGBA components of CSS color properties.
Accordingly, color values must be normalized (e.g. '#ff0000', 'red', and 'rgb(255, 0, 0)' ==> '255 0 0 1') so that their components can be injected/extracted by CSS.Hooks logic. */
for (var i = 0; i < CSS.Lists.colors.length; i++) {
/* Wrap the dynamically generated normalization function in a new scope so that colorName's value is paired with its respective function.
(Otherwise, all functions would take the final for loop's colorName.) */
(function() {
var colorName = CSS.Lists.colors[i];
/* Note: In IE<=8, which support rgb but not rgba, color properties are reverted to rgb by stripping off the alpha component. */
CSS.Normalizations.registered[colorName] = function(type, element, propertyValue) {
switch (type) {
case 'name':
return colorName;
/* Convert all color values into the rgb format. (Old IE can return hex values and color names instead of rgb/rgba.) */
case 'extract':
var extracted;
/* If the color is already in its hookable form (e.g. '255 255 255 1') due to having been previously extracted, skip extraction. */
if (CSS.RegEx.wrappedValueAlreadyExtracted.test(propertyValue)) {
extracted = propertyValue;
} else {
var converted,
colorNames = {
black: 'rgb(0, 0, 0)',
blue: 'rgb(0, 0, 255)',
gray: 'rgb(128, 128, 128)',
green: 'rgb(0, 128, 0)',
red: 'rgb(255, 0, 0)',
white: 'rgb(255, 255, 255)'
};
/* Convert color names to rgb. */
if (/^[A-z]+$/i.test(propertyValue)) {
if (colorNames[propertyValue] !== undefined) {
converted = colorNames[propertyValue]
} else {
/* If an unmatched color name is provided, default to black. */
converted = colorNames.black;
}
/* Convert hex values to rgb. */
} else if (CSS.RegEx.isHex.test(propertyValue)) {
converted = 'rgb(' + CSS.Values.hexToRgb(propertyValue).join(' ') + ')';
/* If the provided color doesn't match any of the accepted color formats, default to black. */
} else if (!(/^rgba?\(/i.test(propertyValue))) {
converted = colorNames.black;
}
/* Remove the surrounding 'rgb/rgba()' string then replace commas with spaces and strip
repeated spaces (in case the value included spaces to begin with). */
extracted = (converted || propertyValue).toString().match(CSS.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g, ' ');
}
/* add a fourth (alpha) component if it's missing and default it to 1 (visible). */
if (extracted.split(' ').length === 3) {
extracted += ' 1';
}
return extracted;
case 'inject':
/* add a fourth (alpha) component if it's missing and default it to 1 (visible). */
if (propertyValue.split(' ').length === 3) {
propertyValue += ' 1';
}
/* Re-insert the browser-appropriate wrapper('rgb/rgba()'), insert commas, and strip off decimal units
on all values but the fourth (R, G, and B only accept whole numbers). */
return 'rgba(' + propertyValue.replace(/\s+/g, ',').replace(/\.(\d)+(?=,)/g, '') + ')';
}
};
})();
}
}
},
/************************
CSS Property Names
************************/
Names: {
/* Camelcase a property name into its JavaScript notation (e.g. 'background-color' ==> 'backgroundColor').
Camelcasing is used to normalize property names between and across calls. */
camelCase: function(property) {
return property.replace(/-(\w)/g, function(match, subMatch) {
return subMatch.toUpperCase();
});
},
/* For SVG elements, some properties (namely, dimensional ones) are GET/SET via the element's HTML attributes (instead of via CSS styles). */
SVGAttribute: function(property) {
return new RegExp('^(width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2)$', 'i').test(property);
},
/* Determine whether a property should be set with a vendor prefix. */
/* If a prefixed version of the property exists, return it. Otherwise, return the original property name.
If the property is not at all supported by the browser, return a false flag. */
prefixCheck: function(property) {
/* If this property has already been checked, return the cached value. */
if (CSS.Names.prefixMatches[property]) {
return [ CSS.Names.prefixMatches[property], true ];
} else {
for (var i = 0, vendorsLength = vendorPrefixes.length; i < vendorsLength; i++) {
var propertyPrefixed;
if (i === 0) {
propertyPrefixed = property;
} else {
/* Capitalize the first letter of the property to conform to JavaScript vendor prefix notation (e.g. webkitFilter). */
propertyPrefixed = vendorPrefixes[i] + property.replace(/^\w/, function(match) { return match.toUpperCase(); });
}
/* Check if the browser supports this property as prefixed. */
if (typeof Collide.State.prefixElement.style[propertyPrefixed] === 'string') {
/* Cache the match. */
CSS.Names.prefixMatches[property] = propertyPrefixed;
return [ propertyPrefixed, true ];
}
}
/* If the browser doesn't support this property in any form, include a false flag so that the caller can decide how to proceed. */
return [ property, false ];
}
},
/* cached property name prefixes */
prefixMatches: {}
},
/************************
CSS Property Values
************************/
Values: {
/* Hex to RGB conversion. Copyright Tim Down: http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb */
hexToRgb: function(hex) {
var shortformRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
longformRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,
rgbParts;
hex = hex.replace(shortformRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
rgbParts = longformRegex.exec(hex);
return rgbParts ? [ parseInt(rgbParts[1], 16), parseInt(rgbParts[2], 16), parseInt(rgbParts[3], 16) ] : [ 0, 0, 0 ];
},
isCSSNullValue: function(value) {
/* The browser defaults CSS values that have not been set to either 0 or one of several possible null-value strings.
Thus, we check for both falsiness and these special strings. */
/* Null-value checking is performed to default the special strings to 0 (for the sake of tweening) or their hook
templates as defined as CSS.Hooks (for the sake of hook injection/extraction). */
/* Note: Chrome returns 'rgba(0, 0, 0, 0)' for an undefined color whereas IE returns 'transparent'. */
return (value == 0 || /^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(value));
},
/* Retrieve a property's default unit type. Used for assigning a unit type when one is not supplied by the user. */
getUnitType: function(property) {
if (/^(rotate|skew)/i.test(property)) {
return 'deg';
} else if (/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(property)) {
/* The above properties are unitless. */
return '';
}
/* Default to px for all other properties. */
return 'px';
},
/* HTML elements default to an associated display type when they're not set to display:none. */
/* Note: This function is used for correctly setting the non-'none' display value in certain Collide redirects, such as fadeIn/Out. */
getDisplayType: function(element) {
var tagName = element && element.tagName && element.tagName.toString().toLowerCase();
if (/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/.test(tagName)) {
return 'inline';
} else if (/^(li)$/.test(tagName)) {
return 'list-item';
} else if (/^(tr)$/.test(tagName)) {
return 'table-row';
} else if (/^(table)$/.test(tagName)) {
return 'table';
} else if (/^(tbody)$/.test(tagName)) {
return 'table-row-group';
}
/* Default to 'block' when no match is found. */
return 'block';
}
},
/****************************
CSS Style Getting & Setting
****************************/
/* The singular getPropertyValue, which routes the logic for all normalizations, hooks, and standard CSS properties. */
getPropertyValue: function(element, property, rootPropertyValue, forceStyleLookup) {
/* Get an element's computed property value. */
/* Note: Retrieving the value of a CSS property cannot simply be performed by checking an element's
style attribute (which only reflects user-defined values). Instead, the browser must be queried for a property's
*computed* value. You can read more about getComputedStyle here: https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle */
function computePropertyValue (element, property) {
/* When box-sizing isn't set to border-box, height and width style values are incorrectly computed when an
element's scrollbars are visible (which expands the element's dimensions). Thus, we defer to the more accurate
offsetHeight/Width property, which includes the total dimensions for interior, border, padding, and scrollbar.
We subtract border and padding to get the sum of interior + scrollbar. */
var computedValue = 0;
/* Browsers do not return height and width values for elements that are set to display:'none'. Thus, we temporarily
toggle display to the element type's default value. */
var toggleDisplay = false;
if (/^(width|height)$/.test(property) && CSS.getPropertyValue(element, 'display') === 0) {
toggleDisplay = true;
CSS.setPropertyValue(element, 'display', CSS.Values.getDisplayType(element));
}
function revertDisplay () {
if (toggleDisplay) {
CSS.setPropertyValue(element, 'display', 'none');
}
}
if (!forceStyleLookup) {
if (property === 'height' && CSS.getPropertyValue(element, 'boxSizing').toString().toLowerCase() !== 'border-box') {
var contentBoxHeight = element.offsetHeight - (parseFloat(CSS.getPropertyValue(element, 'borderTopWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'borderBottomWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingTop')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingBottom')) || 0);
revertDisplay();
return contentBoxHeight;
} else if (property === 'width' && CSS.getPropertyValue(element, 'boxSizing').toString().toLowerCase() !== 'border-box') {
var contentBoxWidth = element.offsetWidth - (parseFloat(CSS.getPropertyValue(element, 'borderLeftWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'borderRightWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingLeft')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingRight')) || 0);
revertDisplay();
return contentBoxWidth;
}
}
var computedStyle;
var eleData = data(element);
/* For elements that Collide hasn't been called on directly (e.g. when Collide queries the DOM on behalf
of a parent of an element its animating), perform a direct getComputedStyle lookup since the object isn't cached. */
if (eleData === undefined) {
computedStyle = window.getComputedStyle(element, null); /* GET */
/* If the computedStyle object has yet to be cached, do so now. */
} else if (!eleData.computedStyle) {
computedStyle = eleData.computedStyle = window.getComputedStyle(element, null); /* GET */
/* If computedStyle is cached, use it. */
} else {
computedStyle = eleData.computedStyle;
}
/* IE and Firefox do not return a value for the generic borderColor -- they only return individual values for each border side's color.
Also, in all browsers, when border colors aren't all the same, a compound value is returned that isn't setup to parse.
So, as a polyfill for querying individual border side colors, we just return the top border's color and animate all borders from that value. */
if (property === 'borderColor') {
property = 'borderTopColor';
}
computedValue = computedStyle[property];
/* Fall back to the property's style value (if defined) when computedValue returns nothing,
which can happen when the element hasn't been painted. */
if (computedValue === '' || computedValue === null) {
computedValue = element.style[property];
}
revertDisplay();
/* For top, right, bottom, and left (TRBL) values that are set to 'auto' on elements of 'fixed' or 'absolute' position,
defer to jQuery for converting 'auto' to a numeric value. (For elements with a 'static' or 'relative' position, 'auto' has the same
effect as being set to 0, so no conversion is necessary.) */
/* An example of why numeric conversion is necessary: When an element with 'position:absolute' has an untouched 'left'
property, which reverts to 'auto', left's value is 0 relative to its parent element, but is often non-zero relative
to its *containing* (not parent) element, which is the nearest 'position:relative' ancestor or the viewport (and always the viewport in the case of 'position:fixed'). */
if (computedValue === 'auto' && /^(top|right|bottom|left)$/i.test(property)) {
var position = computePropertyValue(element, 'position'); /* GET */
/* For absolute positioning, CSS.position(element) only returns values for top and left;
right and bottom will have their 'auto' value reverted to 0. */
if (position === 'fixed' || (position === 'absolute' && /top|left/i.test(property))) {
computedValue = CSS.position(element)[property] + 'px'; /* GET */
}
}
return computedValue;
}
var propertyValue;
/* If this is a hooked property (e.g. 'clipLeft' instead of the root property of 'clip'),
extract the hook's value from a normalized rootPropertyValue using CSS.Hooks.extractValue(). */
if (CSS.Hooks.registered[property]) {
var hook = property,
hookRoot = CSS.Hooks.getRoot(hook);
/* If a cached rootPropertyValue wasn't passed in (which Collide always attempts to do in order to avoid requerying the DOM),
query the DOM for the root property's value. */
if (rootPropertyValue === undefined) {
/* Since the browser is now being directly queried, use the official post-prefixing property name for this lookup. */
rootPropertyValue = CSS.getPropertyValue(element, CSS.Names.prefixCheck(hookRoot)[0]); /* GET */
}
/* If this root has a normalization registered, peform the associated normalization extraction. */
if (CSS.Normalizations.registered[hookRoot]) {
rootPropertyValue = CSS.Normalizations.registered[hookRoot]('extract', element, rootPropertyValue);
}
/* Extract the hook's value. */
propertyValue = CSS.Hooks.extractValue(hook, rootPropertyValue);
/* If this is a normalized property (e.g. 'opacity' becomes 'filter' in <=IE8) or 'translateX' becomes 'transform'),
normalize the property's name and value, and handle the special case of transforms. */
/* Note: Normalizing a property is mutually exclusive from hooking a property since hook-extracted values are strictly
numerical and therefore do not require normalization extraction. */
} else if (CSS.Normalizations.registered[property]) {
var normalizedPropertyName,
normalizedPropertyValue;
normalizedPropertyName = CSS.Normalizations.registered[property]('name', element);
/* Transform values are calculated via normalization extraction (see below), which checks against the element's transformCache.
At no point do transform GETs ever actually query the DOM; initial stylesheet values are never processed.
This is because parsing 3D transform matrices is not always accurate and would bloat our codebase;
thus, normalization extraction defaults initial transform values to their zero-values (e.g. 1 for scaleX and 0 for translateX). */
if (normalizedPropertyName !== 'transform') {
normalizedPropertyValue = computePropertyValue(element, CSS.Names.prefixCheck(normalizedPropertyName)[0]); /* GET */
/* If the value is a CSS null-value and this property has a hook template, use that zero-value template so that hooks can be extracted from it. */
if (CSS.Values.isCSSNullValue(normalizedPropertyValue) && CSS.Hooks.templates[property]) {
normalizedPropertyValue = CSS.Hooks.templates[property][1];
}
}
propertyValue = CSS.Normalizations.registered[property]('extract', element, normalizedPropertyValue);
}
/* If a (numeric) value wasn't produced via hook extraction or normalization, query the DOM. */
if (!/^[\d-]/.test(propertyValue)) {
/* For SVG elements, dimensional properties (which SVGAttribute() detects) are tweened via
their HTML attribute values instead of their CSS style values. */
var eleData = data(element);
if (eleData && eleData.isSVG && CSS.Names.SVGAttribute(property)) {
/* Since the height/width attribute values must be set manually, they don't reflect computed values.
Thus, we use use getBBox() to ensure we always get values for elements with undefined height/width attributes. */
if (/^(height|width)$/i.test(property)) {
/* Firefox throws an error if .getBBox() is called on an SVG that isn't attached to the DOM. */
try {
propertyValue = element.getBBox()[property];
} catch (error) {
propertyValue = 0;
}
/* Otherwise, access the attribute value directly. */
} else {
propertyValue = element.getAttribute(property);
}
} else {
propertyValue = computePropertyValue(element, CSS.Names.prefixCheck(property)[0]); /* GET */
}
}
/* Since property lookups are for animation purposes (which entails computing the numeric delta between start and end values),
convert CSS null-values to an integer of value 0. */
if (CSS.Values.isCSSNullValue(propertyValue)) {
propertyValue = 0;
}
if (Collide.debug >= 2) console.log('Get ' + property + ': ' + propertyValue);
return propertyValue;
},
/* The singular setPropertyValue, which routes the logic for all normalizations, hooks, and standard CSS properties. */
setPropertyValue: function(element, property, propertyValue, rootPropertyValue, scrollData) {
var propertyName = property;
/* In order to be subjected to call options and element queueing, scroll animation is routed through Collie as if it were a standard CSS property. */
if (property === 'scroll') {
/* If a container option is present, scroll the container instead of the browser window. */
if (scrollData.container) {
scrollData.container['scroll' + scrollData.direction] = propertyValue;
/* Otherwise, Collide defaults to scrolling the browser window. */
} else {
if (scrollData.direction === 'Left') {
window.scrollTo(propertyValue, scrollData.alternateValue);
} else {
window.scrollTo(scrollData.alternateValue, propertyValue);
}
}
} else {
var eleData = data(element);
/* Transforms (translateX, rotateZ, etc.) are applied to a per-element transformCache object, which is manually flushed via flushTransformCache().
Thus, for now, we merely cache transforms being SET. */
if (CSS.Normalizations.registered[property] && CSS.Normalizations.registered[property]('name', element) === 'transform') {
/* Perform a normalization injection. */
/* Note: The normalization logic handles the transformCache updating. */
CSS.Normalizations.registered[property]('inject', element, propertyValue);
propertyName = 'transform';
propertyValue = eleData.transformCache[property];
} else {
/* Inject hooks. */
if (CSS.Hooks.registered[property]) {
var hookName = property,
hookRoot = CSS.Hooks.getRoot(property);
/* If a cached rootPropertyValue was not provided, query the DOM for the hookRoot's current value. */
rootPropertyValue = rootPropertyValue || CSS.getPropertyValue(element, hookRoot); /* GET */
propertyValue = CSS.Hooks.injectValue(hookName, propertyValue, rootPropertyValue);
property = hookRoot;
}
/* Normalize names and values. */
if (CSS.Normalizations.registered[property]) {
propertyValue = CSS.Normalizations.registered[property]('inject', element, propertyValue);
property = CSS.Normalizations.registered[property]('name', element);
}
/* Assign the appropriate vendor prefix before performing an official style update. */
propertyName = CSS.Names.prefixCheck(property)[0];
/* SVG elements have their dimensional properties (width, height, x, y, cx, etc.) applied directly as attributes instead of as styles. */
if (eleData && eleData.isSVG && CSS.Names.SVGAttribute(property)) {
/* Note: For SVG attributes, vendor-prefixed property names are never used. */
/* Note: Not all CSS properties can be animated via attributes, but the browser won't throw an error for unsupported properties. */
element.setAttribute(property, propertyValue);
} else {
element.style[propertyName] = propertyValue;
}
if (Collide.debug >= 2) console.log('Set ' + property + ' (' + propertyName + '): ' + propertyValue);
}
}
/* Return the normalized property name and value in case the caller wants to know how these values were modified before being applied to the DOM. */
return [ propertyName, propertyValue ];
},
/* To increase performance by batching transform updates into a single SET, transforms are not directly applied to an element until flushTransformCache() is called. */
/* Note: Collide applies transform properties in the same order that they are chronogically introduced to the element's CSS styles. */
flushTransformCache: function(element) {
var transformString = '';
var transformCache = data(element).transformCache;
var transformValue,
perspective;
/* Transform properties are stored as members of the transformCache object. Concatenate all the members into a string. */
for (var transformName in transformCache) {
transformValue = transformCache[transformName];
/* Transform's perspective subproperty must be set first in order to take effect. Store it temporarily. */
if (transformName === 'transformPerspective') {
perspective = transformValue;
return true;
}
transformString += transformName + transformValue + ' ';
}
/* If present, set the perspective subproperty first. */
if (perspective) {
transformString = 'perspective' + perspective + ' ' + transformString;
}
CSS.setPropertyValue(element, 'transform', transformString);
},
offset: function(element) {
var box = element.getBoundingClientRect ? element.getBoundingClientRect() : { top: 0, left: 0 };
return {
top: box.top + (window.pageYOffset || document.scrollTop || 0) - (document.clientTop || 0),
left: box.left + (window.pageXOffset || document.scrollLeft || 0) - (document.clientLeft || 0)
};
},
position: function(element) {
function offsetParent() {
var offsetParent = this.offsetParent || document;
while (offsetParent && (!offsetParent.nodeType.toLowerCase === 'html' && offsetParent.style.position === 'static')) {
offsetParent = offsetParent.offsetParent;
}
return offsetParent || document;
}
var offsetParent = offsetParent.apply(element),
offset = this.offset(element),
parentOffset = /^(?:body|html)$/i.test(offsetParent.nodeName) ? { top: 0, left: 0 } : this.offset(offsetParent)
offset.top -= parseFloat(element.style.marginTop) || 0;
offset.left -= parseFloat(element.style.marginLeft) || 0;
if (offsetParent.style) {
parentOffset.top += parseFloat(offsetParent.style.borderTopWidth) || 0
parentOffset.left += parseFloat(offsetParent.style.borderLeftWidth) || 0
}
return {
top: offset.top - parentOffset.top,
left: offset.left - parentOffset.left
};
}
};
const vendorPrefixes = [ '', 'Webkit', 'ms' ];
/* Register hooks and normalizations. */
CSS.Hooks.register();
CSS.Normalizations.register();

View File

@ -1,316 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
import * as util from 'ionic/util/util'
import {Collide} from './collide'
/**************
Easing
**************/
/* Step easing generator. */
function generateStep (steps) {
return function (p) {
return Math.round(p * steps) * (1 / steps);
};
}
/* Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */
function generateBezier (mX1, mY1, mX2, mY2) {
var NEWTON_ITERATIONS = 4,
NEWTON_MIN_SLOPE = 0.001,
SUBDIVISION_PRECISION = 0.0000001,
SUBDIVISION_MAX_ITERATIONS = 10,
kSplineTableSize = 11,
kSampleStepSize = 1.0 / (kSplineTableSize - 1.0),
float32ArraySupported = 'Float32Array' in window;
/* Must contain four arguments. */
if (arguments.length !== 4) {
return false;
}
/* Arguments must be numbers. */
for (var i = 0; i < 4; ++i) {
if (typeof arguments[i] !== 'number' || isNaN(arguments[i]) || !isFinite(arguments[i])) {
return false;
}
}
/* X values must be in the [0, 1] range. */
mX1 = Math.min(mX1, 1);
mX2 = Math.min(mX2, 1);
mX1 = Math.max(mX1, 0);
mX2 = Math.max(mX2, 0);
var mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
function C (aA1) { return 3.0 * aA1; }
function calcBezier (aT, aA1, aA2) {
return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
}
function getSlope (aT, aA1, aA2) {
return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}
function newtonRaphsonIterate (aX, aGuessT) {
for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
var currentSlope = getSlope(aGuessT, mX1, mX2);
if (currentSlope === 0.0) return aGuessT;
var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
aGuessT -= currentX / currentSlope;
}
return aGuessT;
}
function calcSampleValues () {
for (var i = 0; i < kSplineTableSize; ++i) {
mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
}
}
function binarySubdivide (aX, aA, aB) {
var currentX, currentT, i = 0;
do {
currentT = aA + (aB - aA) / 2.0;
currentX = calcBezier(currentT, mX1, mX2) - aX;
if (currentX > 0.0) {
aB = currentT;
} else {
aA = currentT;
}
} while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
return currentT;
}
function getTForX (aX) {
var intervalStart = 0.0,
currentSample = 1,
lastSample = kSplineTableSize - 1;
for (; currentSample != lastSample && mSampleValues[currentSample] <= aX; ++currentSample) {
intervalStart += kSampleStepSize;
}
--currentSample;
var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample+1] - mSampleValues[currentSample]),
guessForT = intervalStart + dist * kSampleStepSize,
initialSlope = getSlope(guessForT, mX1, mX2);
if (initialSlope >= NEWTON_MIN_SLOPE) {
return newtonRaphsonIterate(aX, guessForT);
} else if (initialSlope == 0.0) {
return guessForT;
} else {
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize);
}
}
var _precomputed = false;
function precompute() {
_precomputed = true;
if (mX1 != mY1 || mX2 != mY2) calcSampleValues();
}
var f = function (aX) {
if (!_precomputed) precompute();
if (mX1 === mY1 && mX2 === mY2) return aX;
if (aX === 0) return 0;
if (aX === 1) return 1;
return calcBezier(getTForX(aX), mY1, mY2);
};
f.getControlPoints = function() { return [{ x: mX1, y: mY1 }, { x: mX2, y: mY2 }]; };
var str = 'generateBezier(' + [mX1, mY1, mX2, mY2] + ')';
f.toString = function () { return str; };
return f;
}
/* Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */
/* Given a tension, friction, and duration, a simulation at 60FPS will first run without a defined duration in order to calculate the full path. A second pass
then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */
var generateSpringRK4 = (function () {
function springAccelerationForState (state) {
return (-state.tension * state.x) - (state.friction * state.v);
}
function springEvaluateStateWithDerivative (initialState, dt, derivative) {
var state = {
x: initialState.x + derivative.dx * dt,
v: initialState.v + derivative.dv * dt,
tension: initialState.tension,
friction: initialState.friction
};
return { dx: state.v, dv: springAccelerationForState(state) };
}
function springIntegrateState (state, dt) {
var a = {
dx: state.v,
dv: springAccelerationForState(state)
},
b = springEvaluateStateWithDerivative(state, dt * 0.5, a),
c = springEvaluateStateWithDerivative(state, dt * 0.5, b),
d = springEvaluateStateWithDerivative(state, dt, c),
dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx),
dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv);
state.x = state.x + dxdt * dt;
state.v = state.v + dvdt * dt;
return state;
}
return function springRK4Factory (tension, friction, duration) {
var initState = {
x: -1,
v: 0,
tension: null,
friction: null
},
path = [0],
time_lapsed = 0,
tolerance = 1 / 10000,
DT = 16 / 1000,
have_duration, dt, last_state;
tension = parseFloat(tension) || 500;
friction = parseFloat(friction) || 20;
duration = duration || null;
initState.tension = tension;
initState.friction = friction;
have_duration = duration !== null;
/* Calculate the actual time it takes for this animation to complete with the provided conditions. */
if (have_duration) {
/* Run the simulation without a duration. */
time_lapsed = springRK4Factory(tension, friction);
/* Compute the adjusted time delta. */
dt = time_lapsed / duration * DT;
} else {
dt = DT;
}
while (true) {
/* Next/step function .*/
last_state = springIntegrateState(last_state || initState, dt);
/* Store the position. */
path.push(1 + last_state.x);
time_lapsed += 16;
/* If the change threshold is reached, break. */
if (!(Math.abs(last_state.x) > tolerance && Math.abs(last_state.v) > tolerance)) {
break;
}
}
/* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the
computed path and returns a snapshot of the position according to a given percentComplete. */
return !have_duration ? time_lapsed : function(percentComplete) { return path[ (percentComplete * (path.length - 1)) | 0 ]; };
};
}());
/* default easings. */
Collide.Easings = {
linear: function(p) { return p; },
swing: function(p) { return 0.5 - Math.cos( p * Math.PI ) / 2 },
spring: function(p) { return 1 - (Math.cos(p * 4.5 * Math.PI) * Math.exp(-p * 6)); }
};
/* CSS3 and Robert Penner easings. */
(function() {
let penner = [
[ 'ease', [ 0.25, 0.1, 0.25, 1 ] ],
[ 'ease-in', [ 0.42, 0.0, 1.00, 1 ] ],
[ 'ease-out', [ 0.00, 0.0, 0.58, 1 ] ],
[ 'ease-in-out', [ 0.42, 0.0, 0.58, 1 ] ],
[ 'easeInSine', [ 0.47, 0, 0.745, 0.715 ] ],
[ 'easeOutSine', [ 0.39, 0.575, 0.565, 1 ] ],
[ 'easeInOutSine', [ 0.445, 0.05, 0.55, 0.95 ] ],
[ 'easeInQuad', [ 0.55, 0.085, 0.68, 0.53 ] ],
[ 'easeOutQuad', [ 0.25, 0.46, 0.45, 0.94 ] ],
[ 'easeInOutQuad', [ 0.455, 0.03, 0.515, 0.955 ] ],
[ 'easeInCubic', [ 0.55, 0.055, 0.675, 0.19 ] ],
[ 'easeOutCubic', [ 0.215, 0.61, 0.355, 1 ] ],
[ 'easeInOutCubic', [ 0.645, 0.045, 0.355, 1 ] ],
[ 'easeInQuart', [ 0.895, 0.03, 0.685, 0.22 ] ],
[ 'easeOutQuart', [ 0.165, 0.84, 0.44, 1 ] ],
[ 'easeInOutQuart', [ 0.77, 0, 0.175, 1 ] ],
[ 'easeInQuint', [ 0.755, 0.05, 0.855, 0.06 ] ],
[ 'easeOutQuint', [ 0.23, 1, 0.32, 1 ] ],
[ 'easeInOutQuint', [ 0.86, 0, 0.07, 1 ] ],
[ 'easeInExpo', [ 0.95, 0.05, 0.795, 0.035 ] ],
[ 'easeOutExpo', [ 0.19, 1, 0.22, 1 ] ],
[ 'easeInOutExpo', [ 1, 0, 0, 1 ] ],
[ 'easeInCirc', [ 0.6, 0.04, 0.98, 0.335 ] ],
[ 'easeOutCirc', [ 0.075, 0.82, 0.165, 1 ] ],
[ 'easeInOutCirc', [ 0.785, 0.135, 0.15, 0.86 ] ]
];
for (var i = 0, l = penner.length; i < l; i++) {
addEasing(penner[i][0], penner[i][1]);
}
})();
export function addEasing(name, values) {
Collide.Easings[name] = generateBezier.apply(null, values);
};
/* Determine the appropriate easing type given an easing input. */
export function getEasing(value, duration) {
let easing = value;
/* The easing option can either be a string that references a pre-registered easing,
or it can be a two-/four-item array of integers to be converted into a bezier/spring function. */
if (util.isString(value)) {
/* Ensure that the easing has been assigned to standard easings object. */
if (!Collide.Easings[value]) {
easing = false;
}
} else if (util.isArray(value) && value.length === 1) {
easing = generateStep.apply(null, value);
} else if (util.isArray(value) && value.length === 2) {
/* springRK4 must be passed the animation's duration. */
/* Note: If the springRK4 array contains non-numbers, generateSpringRK4() returns an easing
function generated with default tension and friction values. */
easing = generateSpringRK4.apply(null, value.concat([ duration ]));
} else if (util.isArray(value) && value.length === 4) {
/* Note: If the bezier array contains non-numbers, generateBezier() returns false. */
easing = generateBezier.apply(null, value);
} else {
easing = false;
}
/* Revert to the fall back to 'swing' */
if (easing === false) {
easing = 'swing';
}
return easing;
};

View File

@ -1,655 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
import * as util from 'ionic/util/util'
import {Collide} from './collide'
import {CSS} from './css'
import {getEasing} from './easing'
import {calculateUnitRatios} from './calculate-unit-ratios'
const data = Collide.data;
/*********************
Element Processing
*********************/
/* Element processing consists of three parts -- data processing that cannot go stale and data processing that *can* go stale (i.e. third-party style modifications):
1) Pre-Queueing: Element-wide variables, including the element's data storage, are instantiated. Call options are prepared. If triggered, the Stop action is executed.
2) Queueing: The logic that runs once this call has reached its point of execution in the element's Collide.queue() stack. Most logic is placed here to avoid risking it becoming stale.
3) Pushing: Consolidation of the tween data followed by its push onto the global in-progress calls container.
*/
export function processElement(action, animation, elementIndex, clearCache) {
var elements = animation._ele;
var elementsLength = elements.length;
var element = elements[elementIndex];
var eleData;
var opts = animation._options;
var propertiesMap = animation._properties;
var callUnitConversionData = animation._unitConversion;
var call = animation._call;
var resolve = animation._resolve;
/*************************
Part I: Pre-Queueing
*************************/
/***************************
Element-Wide Variables
***************************/
/* A container for the processed data associated with each property in the propertyMap.
(Each property in the map produces its own 'tween'.) */
var tweensContainer = {};
var elementUnitConversionData = null;
/******************
Element Init
******************/
if (data(element) === undefined) {
eleData = Collide.initData(element);
} else {
eleData = data(element);
}
if (animation._addStartClasses.length) eleData.startAddCls = animation._addStartClasses.slice();
if (animation._removeStartClasses.length) eleData.startRmvCls = animation._removeStartClasses.slice();
if (animation._addEndClasses.length) eleData.endAddCls = animation._addEndClasses.slice();
if (animation._removeEndClasses.length) eleData.endRmvCls = animation._removeEndClasses.slice();
/******************
Option: Delay
******************/
/* Since queue:false doesn't respect the item's existing queue, we avoid injecting its delay here (it's set later on). */
if (parseFloat(opts.delay) && opts.queue !== false) {
Collide.queue(element, opts.queue, function(next) {
// This is a flag used to indicate to the upcoming completeCall() function that this queue entry was initiated by Collide.
// See completeCall() for further details.
Collide.collideQueueEntryFlag = true;
// The ensuing queue item (which is assigned to the 'next' argument that Collide.queue() automatically passes in) will be triggered after a setTimeout delay.
// The setTimeout is stored so that it can be subjected to clearTimeout() if this animation is prematurely stopped via Collide's 'stop' command.
data(element).delayTimer = {
setTimeout: setTimeout(next, parseFloat(opts.delay)),
next: next
};
});
}
/*********************
Option: Duration
*********************/
opts.duration = parseFloat(opts.duration) || 1;
/*******************
Option: Easing
*******************/
opts.easing = getEasing(opts.easing, opts.duration);
/**********************
Option: Callbacks
**********************/
/* Callbacks must functions. Otherwise, default to null. */
if (opts.begin && typeof opts.begin !== 'function') {
opts.begin = null;
}
if (opts.progress && typeof opts.progress !== 'function') {
opts.progress = null;
}
if (opts.complete && typeof opts.complete !== 'function') {
opts.complete = null;
}
/*********************************
Option: Display & Visibility
*********************************/
/* Refer to Collide's documentation (CollideJS.org/#displayAndVisibility) for a description of the display and visibility options' behavior. */
/* Note: We strictly check for undefined instead of falsiness because display accepts an empty string value. */
if (opts.display !== undefined && opts.display !== null) {
opts.display = opts.display.toString().toLowerCase();
/* Users can pass in a special 'auto' value to instruct Collide to set the element to its default display value. */
if (opts.display === 'auto') {
opts.display = CSS.getDisplayType(element);
}
}
if (opts.visibility !== undefined && opts.visibility !== null) {
opts.visibility = opts.visibility.toString().toLowerCase();
}
/***********************
Part II: Queueing
***********************/
/* When a set of elements is targeted by a Collide call, the set is broken up and each element has the current Collide call individually queued onto it.
In this way, each element's existing queue is respected; some elements may already be animating and accordingly should not have this current Collide call triggered immediately. */
/* In each queue, tween data is processed for each animating property then pushed onto the call-wide calls array. When the last element in the set has had its tweens processed,
the call array is pushed to Collide.State.calls for live processing by the requestAnimationFrame tick. */
function buildQueue(next) {
/*******************
Option: Begin
*******************/
/* The begin callback is fired once per call -- not once per elemenet -- and is passed the full raw DOM element set as both its context and its first argument. */
if (opts.begin && elementIndex === 0) {
/* We throw callbacks in a setTimeout so that thrown errors don't halt the execution of Collide itself. */
try {
opts.begin.call(elements, elements);
} catch (error) {
setTimeout(function() { throw error; });
}
}
/*****************************************
Tween Data Construction (for Scroll)
*****************************************/
/* Note: In order to be subjected to chaining and animation options, scroll's tweening is routed through Collide as if it were a standard CSS property animation. */
if (action === 'scroll') {
/* The scroll action uniquely takes an optional 'offset' option -- specified in pixels -- that offsets the targeted scroll position. */
var scrollDirection = (/^x$/i.test(opts.axis) ? 'Left' : 'Top'),
scrollOffset = parseFloat(opts.offset) || 0,
scrollPositionCurrent,
scrollPositionCurrentAlternate,
scrollPositionEnd;
/* Scroll also uniquely takes an optional 'container' option, which indicates the parent element that should be scrolled --
as opposed to the browser window itself. This is useful for scrolling toward an element that's inside an overflowing parent element. */
if (opts.container) {
/* Ensure that a raw DOM element was passed in. */
if (opts.container.nodeType) {
/* Note: Unlike other properties in Collide, the browser's scroll position is never cached since it so frequently changes
(due to the user's natural interaction with the page). */
scrollPositionCurrent = opts.container['scroll' + scrollDirection]; /* GET */
/* CSS.position(element) values are relative to the container's currently viewable area (without taking into
account the container's true dimensions, for example, if the container was not overflowing). Thus, the scroll end
value is the sum of the child element's position *and* the scroll container's current scroll position. */
scrollPositionEnd = (scrollPositionCurrent + CSS.position(element)[scrollDirection.toLowerCase()]) + scrollOffset; /* GET */
} else {
/* If a value other than a raw DOM element was passed in, default to null so that this option is ignored. */
opts.container = null;
}
} else {
/* If the window itself is being scrolled -- not a containing element -- perform a live scroll position lookup using
the appropriate cached property names (which differ based on browser type). */
scrollPositionCurrent = Collide.State.scrollAnchor[Collide.State['scrollProperty' + scrollDirection]]; /* GET */
/* When scrolling the browser window, cache the alternate axis's current value since window.scrollTo() doesn't let us change only one value at a time. */
scrollPositionCurrentAlternate = Collide.State.scrollAnchor[Collide.State['scrollProperty' + (scrollDirection === 'Left' ? 'Top' : 'Left')]]; /* GET */
/* Unlike CSS.position(element), CSS.offset(element) values are relative to the browser window's true dimensions
-- not merely its currently viewable area -- and therefore end values do not need to be compounded onto current values. */
scrollPositionEnd = CSS.offset(element)[scrollDirection.toLowerCase()] + scrollOffset; /* GET */
}
/* Since there's only one format that scroll's associated tweensContainer can take, we create it manually. */
tweensContainer = {
scroll: {
rootPropertyValue: false,
startValue: scrollPositionCurrent,
currentValue: scrollPositionCurrent,
endValue: scrollPositionEnd,
unitType: '',
easing: opts.easing,
scrollData: {
container: opts.container,
direction: scrollDirection,
alternateValue: scrollPositionCurrentAlternate
}
},
element: element
};
if (Collide.debug) console.log("tweensContainer (scroll): ", tweensContainer.scroll, element);
} else if (action === 'start') {
/****************************
Start: Value Transferring
****************************/
/* If this queue entry follows a previous Collide-initiated queue entry *and* if this entry was created
while the element was in the process of being animated by Collide, then this current call is safe to use
the end values from the prior call as its start values. Collide attempts to perform this value transfer
process whenever possible in order to avoid requerying the DOM. */
/* If values aren't transferred from a prior call and start values were not forcefed by the user (more on this below),
then the DOM is queried for the element's current values as a last resort. */
/* Note: Conversely, animation reversal (and looping) *always* perform inter-call value transfers; they never requery the DOM. */
var lastTweensContainer;
/* The per-element isAnimating flag is used to indicate whether it's safe (i.e. the data isn't stale)
to transfer over end values to use as start values. If it's set to true and there is a previous
Collide call to pull values from, do so. */
eleData = data(element);
if (clearCache) {
eleData.tweensContainer = undefined;
} else if (eleData.tweensContainer && eleData.isAnimating === true) {
lastTweensContainer = eleData.tweensContainer;
}
/********************************
Start: Tween Data Calculation
********************************/
/* This function parses property data and defaults endValue, easing, and startValue as appropriate. */
/* Property map values can either take the form of 1) a single value representing the end value,
or 2) an array in the form of [ endValue, [, easing] [, startValue] ].
The optional third parameter is a forcefed startValue to be used instead of querying the DOM for
the element's current value. */
function parsePropertyValue(valueData, skipResolvingEasing) {
var endValue = valueData[0];
var easing = skipResolvingEasing ? valueData[1] : getEasing(valueData[1] || opts.easing, opts.duration);
var startValue = valueData[2];
/* Default to the call's easing if a per-property easing type was not defined. */
if (!skipResolvingEasing) {
easing = easing || opts.easing;
}
/* If functions were passed in as values, pass the function the current element as its context,
plus the element's index and the element set's size as arguments. Then, assign the returned value. */
if (typeof endValue === 'function') {
endValue = endValue.call(element, elementIndex, elementsLength);
}
if (typeof startValue === 'function') {
startValue = startValue.call(element, elementIndex, elementsLength);
}
/* Allow startValue to be left as undefined to indicate to the ensuing code that its value was not forcefed. */
return [ endValue || 0, easing, startValue ];
} // END: parsePropertyValue()
/* Cycle through each property in the map, looking for shorthand color properties (e.g. 'color' as opposed to 'colorRed'). Inject the corresponding
colorRed, colorGreen, and colorBlue RGB component tweens into the propertiesMap (which Collide understands) and remove the shorthand property. */
for (var property in propertiesMap) {
/* Find shorthand color properties that have been passed a hex string. */
if (RegExp('^' + CSS.Lists.colors.join('$|^') + '$').test(property)) {
/* Parse the value data for each shorthand. */
var valueData = parsePropertyValue(propertiesMap[property], true),
endValue = valueData[0],
easing = valueData[1],
startValue = valueData[2];
if (CSS.RegEx.isHex.test(endValue)) {
/* Convert the hex strings into their RGB component arrays. */
var colorComponents = [ 'Red', 'Green', 'Blue' ],
endValueRGB = CSS.Values.hexToRgb(endValue),
startValueRGB = startValue ? CSS.Values.hexToRgb(startValue) : undefined;
/* Inject the RGB component tweens into propertiesMap. */
for (var i = 0; i < colorComponents.length; i++) {
var dataArray = [ endValueRGB[i] ];
if (easing) {
dataArray.push(easing);
}
if (startValueRGB !== undefined) {
dataArray.push(startValueRGB[i]);
}
propertiesMap[property + colorComponents[i]] = dataArray;
}
/* Remove the intermediary shorthand property entry now that we've processed it. */
delete propertiesMap[property];
}
}
}
/* Create a tween out of each property, and append its associated data to tweensContainer. */
for (var property in propertiesMap) {
/*********************************************
parsePropertyValue(), Start Value Sourcing
*********************************************/
/* Parse out endValue, easing, and startValue from the property's data. */
var valueData = parsePropertyValue(propertiesMap[property]),
endValue = valueData[0],
easing = valueData[1],
startValue = valueData[2];
/* Now that the original property name's format has been used for the parsePropertyValue() lookup above,
we force the property to its camelCase styling to normalize it for manipulation. */
property = CSS.Names.camelCase(property);
/* In case this property is a hook, there are circumstances where we will intend to work on the hook's root property and not the hooked subproperty. */
var rootProperty = CSS.Hooks.getRoot(property),
rootPropertyValue = false;
/* Other than for the dummy tween property, properties that are not supported by the browser (and do not have an associated normalization) will
inherently produce no style changes when set, so they are skipped in order to decrease animation tick overhead.
Property support is determined via prefixCheck(), which returns a false flag when no supported is detected. */
/* Note: Since SVG elements have some of their properties directly applied as HTML attributes,
there is no way to check for their explicit browser support, and so we skip skip this check for them. */
if (!eleData.isSVG && rootProperty !== 'tween' && CSS.Names.prefixCheck(rootProperty)[1] === false && CSS.Normalizations.registered[rootProperty] === undefined) {
if (Collide.debug) console.log('Skipping [' + rootProperty + '] due to a lack of browser support.');
continue;
}
/* If the display option is being set to a non-'none' (e.g. 'block') and opacity (filter on IE<=8) is being
animated to an endValue of non-zero, the user's intention is to fade in from invisible, thus we forcefeed opacity
a startValue of 0 if its startValue hasn't already been sourced by value transferring or prior forcefeeding. */
if (((opts.display !== undefined && opts.display !== null && opts.display !== 'none') || (opts.visibility !== undefined && opts.visibility !== 'hidden')) && /opacity|filter/.test(property) && !startValue && endValue !== 0) {
startValue = 0;
}
/* If values have been transferred from the previous Collide call, extract the endValue and rootPropertyValue
for all of the current call's properties that were *also* animated in the previous call. */
/* Note: Value transferring can optionally be disabled by the user via the _cacheValues option. */
if (opts._cacheValues && lastTweensContainer && lastTweensContainer[property]) {
if (startValue === undefined) {
startValue = lastTweensContainer[property].endValue + lastTweensContainer[property].unitType;
}
/* The previous call's rootPropertyValue is extracted from the element's data cache since that's the
instance of rootPropertyValue that gets freshly updated by the tweening process, whereas the rootPropertyValue
attached to the incoming lastTweensContainer is equal to the root property's value prior to any tweening. */
rootPropertyValue = eleData.rootPropertyValueCache[rootProperty];
/* If values were not transferred from a previous Collide call, query the DOM as needed. */
} else {
/* Handle hooked properties. */
if (CSS.Hooks.registered[property]) {
if (startValue === undefined) {
rootPropertyValue = CSS.getPropertyValue(element, rootProperty); /* GET */
/* Note: The following getPropertyValue() call does not actually trigger a DOM query;
getPropertyValue() will extract the hook from rootPropertyValue. */
startValue = CSS.getPropertyValue(element, property, rootPropertyValue);
/* If startValue is already defined via forcefeeding, do not query the DOM for the root property's value;
just grab rootProperty's zero-value template from CSS.Hooks. This overwrites the element's actual
root property value (if one is set), but this is acceptable since the primary reason users forcefeed is
to avoid DOM queries, and thus we likewise avoid querying the DOM for the root property's value. */
} else {
/* Grab this hook's zero-value template, e.g. '0px 0px 0px black'. */
rootPropertyValue = CSS.Hooks.templates[rootProperty][1];
}
/* Handle non-hooked properties that haven't already been defined via forcefeeding. */
} else if (startValue === undefined) {
startValue = CSS.getPropertyValue(element, property); /* GET */
}
}
/**********************************************
parsePropertyValue(), Value Data Extraction
**********************************************/
var separatedValue,
endValueUnitType,
startValueUnitType,
operator = false;
/* Separates a property value into its numeric value and its unit util. */
function separateValue (property, value) {
var unitType,
numericValue;
numericValue = (value || '0')
.toString()
.toLowerCase()
/* Match the unit type at the end of the value. */
.replace(/[%A-z]+$/, function(match) {
/* Grab the unit util. */
unitType = match;
/* Strip the unit type off of value. */
return '';
});
/* If no unit type was supplied, assign one that is appropriate for this property (e.g. 'deg' for rotateZ or 'px' for width). */
if (!unitType) {
unitType = CSS.Values.getUnitType(property);
}
return [ numericValue, unitType ];
}
/* Separate startValue. */
separatedValue = separateValue(property, startValue);
startValue = separatedValue[0];
startValueUnitType = separatedValue[1];
/* Separate endValue, and extract a value operator (e.g. '+=', '-=') if one exists. */
separatedValue = separateValue(property, endValue);
endValue = separatedValue[0].replace(/^([+-\/*])=/, function(match, subMatch) {
operator = subMatch;
/* Strip the operator off of the value. */
return '';
});
endValueUnitType = separatedValue[1];
/* Parse float values from endValue and startValue. Default to 0 if NaN is returned. */
startValue = parseFloat(startValue) || 0;
endValue = parseFloat(endValue) || 0;
/***************************************
parsePropertyValue, Property-Specific Value Conversion
***************************************/
/* Custom support for properties that don't actually accept the % unit type, but where pollyfilling is trivial and relatively foolproof. */
if (endValueUnitType === '%') {
/* A %-value fontSize/lineHeight is relative to the parent's fontSize (as opposed to the parent's dimensions),
which is identical to the em unit's behavior, so we piggyback off of that. */
if (/^(fontSize|lineHeight)$/.test(property)) {
/* Convert % into an em decimal value. */
endValue = endValue / 100;
endValueUnitType = 'em';
/* For scaleX and scaleY, convert the value into its decimal format and strip off the unit util. */
} else if (/^scale/.test(property)) {
endValue = endValue / 100;
endValueUnitType = '';
/* For RGB components, take the defined percentage of 255 and strip off the unit util. */
} else if (/(Red|Green|Blue)$/i.test(property)) {
endValue = (endValue / 100) * 255;
endValueUnitType = '';
}
}
/****************************************
parsePropertyValue(), Unit Conversion
*****************************************/
/* The * and / operators, which are not passed in with an associated unit, inherently use startValue's unit. Skip value and unit conversion. */
if (/[\/*]/.test(operator)) {
endValueUnitType = startValueUnitType;
/* If startValue and endValue differ in unit type, convert startValue into the same unit type as endValue so that if endValueUnitType
is a relative unit (%, em, rem), the values set during tweening will continue to be accurately relative even if the metrics they depend
on are dynamically changing during the course of the animation. Conversely, if we always normalized into px and used px for setting values, the px ratio
would become stale if the original unit being animated toward was relative and the underlying metrics change during the animation. */
/* Since 0 is 0 in any unit type, no conversion is necessary when startValue is 0 -- we just start at 0 with endValueUnitutil. */
} else if ((startValueUnitType !== endValueUnitType) && startValue !== 0) {
/* Unit conversion is also skipped when endValue is 0, but *startValueUnitType* must be used for tween values to remain accurate. */
/* Note: Skipping unit conversion here means that if endValueUnitType was originally a relative unit, the animation won't relatively
match the underlying metrics if they change, but this is acceptable since we're animating toward invisibility instead of toward visibility,
which remains past the point of the animation's completion. */
if (endValue === 0) {
endValueUnitType = startValueUnitType;
} else {
/* By this point, we cannot avoid unit conversion (it's undesirable since it causes layout thrashing).
If we haven't already, we trigger calculateUnitRatios(), which runs once per element per call. */
elementUnitConversionData = elementUnitConversionData || calculateUnitRatios(element, callUnitConversionData);
/* The following RegEx matches CSS properties that have their % values measured relative to the x-axis. */
/* Note: W3C spec mandates that all of margin and padding's properties (even top and bottom) are %-relative to the *width* of the parent element. */
var axis = (/margin|padding|left|right|width|text|word|letter/i.test(property) || /X$/.test(property) || property === 'x') ? 'x' : 'y';
/* In order to avoid generating n^2 bespoke conversion functions, unit conversion is a two-step process:
1) Convert startValue into pixels. 2) Convert this new pixel value into endValue's unit util. */
switch (startValueUnitType) {
case '%':
/* Note: translateX and translateY are the only properties that are %-relative to an element's own dimensions -- not its parent's dimensions.
Collide does not include a special conversion process to account for this behavior. Therefore, animating translateX/Y from a % value
to a non-% value will produce an incorrect start value. Fortunately, this sort of cross-unit conversion is rarely done by users in practice. */
startValue *= (axis === 'x' ? elementUnitConversionData.percentToPxWidth : elementUnitConversionData.percentToPxHeight);
break;
case 'px':
/* px acts as our midpoint in the unit conversion process; do nothing. */
break;
default:
startValue *= elementUnitConversionData[startValueUnitType + 'ToPx'];
}
/* Invert the px ratios to convert into to the target unit. */
switch (endValueUnitType) {
case '%':
startValue *= 1 / (axis === 'x' ? elementUnitConversionData.percentToPxWidth : elementUnitConversionData.percentToPxHeight);
break;
case 'px':
/* startValue is already in px, do nothing; we're done. */
break;
default:
startValue *= 1 / elementUnitConversionData[endValueUnitType + 'ToPx'];
}
}
}
/****************************************
parsePropertyValue(), Relative Values
****************************************/
/* Operator logic must be performed last since it requires unit-normalized start and end values. */
/* Note: Relative *percent values* do not behave how most people think; while one would expect '+=50%'
to increase the property 1.5x its current value, it in fact increases the percent units in absolute terms:
50 points is added on top of the current % value. */
switch (operator) {
case '+':
endValue = startValue + endValue;
break;
case '-':
endValue = startValue - endValue;
break;
case '*':
endValue = startValue * endValue;
break;
case '/':
endValue = startValue / endValue;
break;
}
var currentValue = startValue;
/*********************************************
parsePropertyValue(), tweensContainer Push
*********************************************/
/* Construct the per-property tween object, and push it to the element's tweensContainer. */
tweensContainer[property] = {
rootPropertyValue: rootPropertyValue,
startValue: startValue,
currentValue: currentValue,
endValue: endValue,
unitType: endValueUnitType,
easing: easing
};
if (Collide.debug) console.log('tweensContainer (' + property + '): ' + JSON.stringify(tweensContainer[property]), element);
}
/* Along with its property data, store a reference to the element itself onto tweensContainer. */
tweensContainer.element = element;
} // END: parsePropertyValue()
/*****************
Call Push
*****************/
/* Note: tweensContainer can be empty if all of the properties in this call's property map were skipped due to not
being supported by the browser. The element property is used for checking that the tweensContainer has been appended to. */
if (tweensContainer.element) {
/* The call array houses the tweensContainers for each element being animated in the current call. */
call.push(tweensContainer);
/* Store the tweensContainer and options if we're working on the default effects queue, so that they can be used by the reverse command. */
if (opts.queue === '') {
eleData.tweensContainer = tweensContainer;
eleData.opts = opts;
}
}
} // END: buildQueue
/* When the queue option is set to false, the call skips the element's queue and fires immediately. */
if (opts.queue === false) {
/* Since this buildQueue call doesn't respect the element's existing queue (which is where a delay option would have been appended),
we manually inject the delay property here with an explicit setTimeout. */
if (opts.delay) {
setTimeout(buildQueue, opts.delay);
} else {
buildQueue();
}
/* Otherwise, the call undergoes element queueing as normal. */
} else {
Collide.queue(element, opts.queue, function(next, clearQueue) {
/* If the clearQueue flag was passed in by the stop command, resolve this call's promise. (Promises can only be resolved once,
so it's fine if this is repeatedly triggered for each element in the associated call.) */
if (clearQueue === true) {
/* Do not continue with animation queueing. */
resolve(elements);
return true;
}
/* This flag indicates to the upcoming completeCall() function that this queue entry was initiated by Collide.
See completeCall() for further details. */
Collide.collideQueueEntryFlag = true;
buildQueue(next);
});
}
}

View File

@ -1,301 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
import {dom} from 'ionic/util'
import {Collide} from './collide'
import {CSS} from './css'
import {completeCall} from './complete-call'
/************
Tick
************/
export function startTick() {
if (!Collide.State.isTicking && Collide.State.calls && Collide.State.calls.length) {
Collide.State.isTicking = true;
tick();
}
}
/* Note: All calls to Collide are pushed to the Collide.State.calls array, which is fully iterated through upon each tick. */
function tick(timestamp) {
/* An empty timestamp argument indicates that this is the first tick occurence since ticking was turned on.
We leverage this metadata to fully ignore the first tick pass since RAF's initial pass is fired whenever
the browser's next tick sync time occurs, which results in the first elements subjected to Collide
calls being animated out of sync with any elements animated immediately thereafter. In short, we ignore
the first RAF tick pass so that elements being immediately consecutively animated -- instead of simultaneously animated
by the same Collide call -- are properly batched into the same initial RAF tick and consequently remain in sync thereafter. */
if (timestamp) {
/* We ignore RAF's high resolution timestamp since it can be significantly offset when the browser is
under high stress; we opt for choppiness over allowing the browser to drop huge chunks of frames. */
var timeCurrent = (new Date).getTime();
/********************
Call Iteration
********************/
var callsLength = Collide.State.calls.length;
/* To speed up iterating over this array, it is compacted (falsey items -- calls that have completed -- are removed)
when its length has ballooned to a point that can impact tick performance. This only becomes necessary when animation
has been continuous with many elements over a long period of time; whenever all active calls are completed, completeCall() clears Collide.State.calls. */
if (callsLength > 10000) {
Collide.State.calls = compactSparseArray(Collide.State.calls);
}
/* Iterate through each active call. */
for (var i = 0; i < callsLength; i++) {
/* When a Collide call is completed, its Collide.State.calls entry is set to false. Continue on to the next call. */
if (!Collide.State.calls[i]) {
continue;
}
/************************
Call-Wide Variables
************************/
var callContainer = Collide.State.calls[i],
call = callContainer[0],
opts = callContainer[2],
timeStart = callContainer[3],
firstTick = !!timeStart,
tweenDummyValue = null;
if (!call) {
continue;
}
/* If timeStart is undefined, then this is the first time that this call has been processed by tick().
We assign timeStart now so that its value is as close to the real animation start time as possible.
(Conversely, had timeStart been defined when this call was added to Collide.State.calls, the delay
between that time and now would cause the first few frames of the tween to be skipped since
percentComplete is calculated relative to timeStart.) */
/* Further, subtract 16ms (the approximate resolution of RAF) from the current time value so that the
first tick iteration isn't wasted by animating at 0% tween completion, which would produce the
same style value as the element's current value. */
if (!timeStart) {
timeStart = Collide.State.calls[i][3] = timeCurrent - 16;
}
var percentComplete;
if (opts.percentComplete !== undefined) {
percentComplete = Math.max(Math.min(parseFloat(opts.percentComplete), 1), 0);
} else {
/* The tween's completion percentage is relative to the tween's start time, not the tween's start value
(which would result in unpredictable tween durations since JavaScript's timers are not particularly accurate).
Accordingly, we ensure that percentComplete does not exceed 1. */
percentComplete = Math.min((timeCurrent - timeStart) / opts.duration, 1);
}
/**********************
Element Iteration
**********************/
/* For every call, iterate through each of the elements in its set. */
for (var j = 0, jj = call.length; j < jj; j++) {
var tweensContainer = call[j];
var element = tweensContainer.element;
var eleData = Collide.data(element);
/* Check to see if this element has been deleted midway through the animation by checking for the
continued existence of its data cache. If it's gone, skip animating this element. */
if (!eleData) {
continue;
}
if (eleData.startAddCls) {
for (var k = 0; k < eleData.startAddCls.length; k++) {
element.classList.add(eleData.startAddCls[k]);
}
eleData.startAddCls = null;
}
if (eleData.startRmvCls) {
for (var k = 0; k < eleData.startRmvCls.length; k++) {
element.classList.remove(eleData.startRmvCls[k]);
}
eleData.startRmvCls = null;
}
var transformPropertyExists = false;
/**********************************
Display & Visibility Toggling
**********************************/
/* If the display option is set to non-'none', set it upfront so that the element can become visible before tweening begins.
(Otherwise, display's 'none' value is set in completeCall() once the animation has completed.) */
if (opts.display !== undefined && opts.display !== null && opts.display !== 'none') {
if (opts.display === 'flex') {
for (var f = 0; f < flexValues.length; f++) {
CSS.setPropertyValue(element, 'display', flexValues[f]);
}
}
CSS.setPropertyValue(element, 'display', opts.display);
}
/* Same goes with the visibility option, but its 'none' equivalent is 'hidden'. */
if (opts.visibility !== undefined && opts.visibility !== 'hidden') {
CSS.setPropertyValue(element, 'visibility', opts.visibility);
}
/************************
Property Iteration
************************/
/* For every element, iterate through each property. */
for (var property in tweensContainer) {
/* Note: In addition to property tween data, tweensContainer contains a reference to its associated element. */
if (property !== 'element') {
var tween = tweensContainer[property];
var currentValue;
/* Easing can either be a pre-genereated function or a string that references a pre-registered easing
on the Collide.Easings object. In either case, return the appropriate easing *function*. */
var easing = (typeof tween.easing === 'string' ? Collide.Easings[tween.easing] : tween.easing);
/******************************
Current Value Calculation
******************************/
/* If this is the last tick pass (if we've reached 100% completion for this tween),
ensure that currentValue is explicitly set to its target endValue so that it's not subjected to any rounding. */
if (percentComplete === 1) {
currentValue = tween.endValue;
/* Otherwise, calculate currentValue based on the current delta from startValue. */
} else {
var tweenDelta = tween.endValue - tween.startValue;
currentValue = tween.startValue + (tweenDelta * easing(percentComplete, opts, tweenDelta));
/* If no value change is occurring, don't proceed with DOM updating. */
if (!firstTick && (currentValue === tween.currentValue)) {
continue;
}
}
tween.currentValue = currentValue;
/* If we're tweening a fake 'tween' property in order to log transition values, update the one-per-call variable so that
it can be passed into the progress callback. */
if (property === 'tween') {
tweenDummyValue = currentValue;
} else {
/******************
Hooks: Part I
******************/
/* For hooked properties, the newly-updated rootPropertyValueCache is cached onto the element so that it can be used
for subsequent hooks in this call that are associated with the same root property. If we didn't cache the updated
rootPropertyValue, each subsequent update to the root property in this tick pass would reset the previous hook's
updates to rootPropertyValue prior to injection. A nice performance byproduct of rootPropertyValue caching is that
subsequently chained animations using the same hookRoot but a different hook can use this cached rootPropertyValue. */
if (CSS.Hooks.registered[property]) {
var hookRoot = CSS.Hooks.getRoot(property),
rootPropertyValueCache = eleData.rootPropertyValueCache[hookRoot];
if (rootPropertyValueCache) {
tween.rootPropertyValue = rootPropertyValueCache;
}
}
/*****************
DOM Update
*****************/
/* setPropertyValue() returns an array of the property name and property value post any normalization that may have been performed. */
/* Note: To solve an IE<=8 positioning bug, the unit type is dropped when setting a property value of 0. */
var adjustedSetData = CSS.setPropertyValue(element, /* SET */
property,
tween.currentValue + (parseFloat(currentValue) === 0 ? '' : tween.unitType),
tween.rootPropertyValue,
tween.scrollData);
/*******************
Hooks: Part II
*******************/
/* Now that we have the hook's updated rootPropertyValue (the post-processed value provided by adjustedSetData), cache it onto the element. */
if (CSS.Hooks.registered[property]) {
/* Since adjustedSetData contains normalized data ready for DOM updating, the rootPropertyValue needs to be re-extracted from its normalized form. ?? */
if (CSS.Normalizations.registered[hookRoot]) {
eleData.rootPropertyValueCache[hookRoot] = CSS.Normalizations.registered[hookRoot]('extract', null, adjustedSetData[1]);
} else {
eleData.rootPropertyValueCache[hookRoot] = adjustedSetData[1];
}
}
/***************
Transforms
***************/
/* Flag whether a transform property is being animated so that flushTransformCache() can be triggered once this tick pass is complete. */
if (adjustedSetData[0] === 'transform') {
transformPropertyExists = true;
}
}
} // END: if (property !== 'element')
} // END: for (var property in tweensContainer)
if (transformPropertyExists) {
CSS.flushTransformCache(element);
}
} // END: for (var j = 0, jj = call.length; j < jj; j++)
/* The non-'none' display value is only applied to an element once -- when its associated call is first ticked through.
Accordingly, it's set to false so that it isn't re-processed by this call in the next tick. */
if (opts.display !== undefined && opts.display !== 'none') {
Collide.State.calls[i][2].display = false;
}
if (opts.visibility !== undefined && opts.visibility !== 'hidden') {
Collide.State.calls[i][2].visibility = false;
}
/* Pass the elements and the timing data (percentComplete, msRemaining, timeStart, tweenDummyValue) into the progress callback. */
if (opts.progress) {
opts.progress.call(callContainer[1],
callContainer[1],
percentComplete,
Math.max(0, (timeStart + opts.duration) - timeCurrent),
timeStart,
tweenDummyValue);
}
/* If this call has finished tweening, pass its index to completeCall() to handle call cleanup. */
if (percentComplete === 1 || opts.percentComplete !== undefined) {
completeCall(i);
}
} // END: for (var i = 0; i < callsLength; i++)
} // END: if (timestamp)
/* Note: completeCall() sets the isTicking flag to false when the last call on Collide.State.calls has completed. */
if (Collide.State.isTicking) {
dom.raf(tick);
}
};
const flexValues = [ '-webkit-box', '-moz-box', '-ms-flexbox', '-webkit-flex' ];

View File

@ -15,132 +15,45 @@ let scale = 0.6;
}) })
class IonicApp { class IonicApp {
fadeOut() { constructor() {
console.debug('fadeOut'); this.animation = new Animation();
var animation = new Animation(); this.animation
.duration(1000)
animation.duration(1000); .easing('ease-in-out');
animation.easing('linear');
var row1 = new Animation();
row1.elements( document.querySelectorAll('.square') )
.to('opacity', opacity)
.to('translateX', translateX)
.to('translateY', translateX)
.to('rotateZ', rotateZ)
.to('scale', scale);
animation.addChild(row1);
var row2 = new Animation(); var row1 = new Animation( document.querySelectorAll('.square') );
row2.elements( document.querySelectorAll('.square2') ); row1
.from('opacity', 1)
.to('opacity', 0)
.beforePlay.addClass('added-before-play')
.afterFinish.addClass('added-after-finish')
row2.to('opacity', opacity); var row2 = new Animation( document.querySelectorAll('.square2') );
row2.to('translateX', '-100px'); row2
row2.to('translateY', '-100px'); .to('transform', 'rotate(90deg) scale(0.5)')
row2.to('rotateZ', '-180deg'); .beforePlay.addClass('added-before-play')
row2.to('scale', 0.4); .afterFinish.addClass('added-after-finish')
animation.addChild(row2); this.animation.children(row1, row2);
let q = animation.start();
q.then(()=> {
console.log('fade out complete')
});
} }
play() {
fadeIn() { console.debug('play');
console.debug('fadeIn'); this.animation.play();;
var animation = new Animation();
animation.duration(1000);
animation.easing('linear');
var row1 = new Animation();
row1.elements( document.querySelectorAll('.square') );
row1.to('opacity', 1);
row1.to('translateX', 0);
row1.to('translateY', 0);
row1.to('rotateZ', 0);
row1.to('scale', 1);
animation.addChild(row1);
var row2 = new Animation();
row2.elements( document.querySelectorAll('.square2') );
row2.to('opacity', 1);
row2.to('translateX', 0);
row2.to('translateY', 0);
row2.to('rotateZ', 0);
row2.to('scale', 1);
animation.addChild(row2);
let q = animation.start();
q.then(()=> {
console.log('fade in complete')
});
} }
stop() { pause() {
this.animation.stop(); console.debug('pause');
this.animation.pause();
} }
percent(ev) { progress(ev) {
let percentComplete = parseFloat(ev.srcElement.value) / 100; let value = ev.srcElement.value;
this.animation.progress(value);
if (!this.percentAnimation) {
this.percentAnimation = new Animation();
// this.percentAnimation.name = 'top';
var row1 = new Animation();
row1.elements( document.querySelectorAll('.square') )
.to('opacity', opacity)
.to('translateX', translateX)
.to('translateY', translateX)
.to('rotateZ', rotateZ)
.to('scale', scale);
this.percentAnimation.addChild(row1);
var row2 = new Animation();
row2.elements( document.querySelectorAll('.square2') );
row2.to('opacity', opacity);
row2.to('translateX', '-100px');
row2.to('translateY', '-100px');
row2.to('rotateZ', '-180deg');
row2.to('scale', 0.4);
this.percentAnimation.addChild(row2);
this.percentAnimation.ready();
}
this.percentAnimation.percent(percentComplete);
}
velocityStart() {
var elements = document.querySelectorAll('.square');
Velocity(elements, {
opacity: 0,
translateX: '100px',
rotateZ: '90deg'
}, 2000);
// setTimeout(function() {
// Velocity(elements, "stop");
// }, 1000);
} }
} }

View File

@ -1,6 +1,7 @@
<html> <html>
<head> <head>
<title>Collide Tests</title> <title>Animation Tests</title>
<script src="./web-animations-js/web-animations-next.dev.js"></script>
</head> </head>
<body> <body>
@ -21,20 +22,13 @@
</div> </div>
<p> <p>
<input type="range" (input)="percent($event)" value="0" min="0" max="100" style="width:200px"> <input type="range" (input)="progress($event)" value="0" min="0" step="0.001" max="1" style="width:200px">
</p> </p>
<p> <p>
<button class="button button-primary" (click)="fadeOut($event)">Out</button> <button class="button button-primary" (click)="play($event)">Play</button>
<button class="button button-primary" (click)="fadeIn($event)">In</button> <button class="button button-primary" (click)="pause($event)">Pause</button>
</p> </p>
<!-- <p>
<button class="button button-primary" (click)="velocityStart($event)">Velocity Start</button>
</p> -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.2/velocity.js"></script>
</body> </body>
</html> </html>

View File

@ -124,7 +124,7 @@ export class NavBase {
leavingItem.state = ACTIVELY_LEAVING_STATE; leavingItem.state = ACTIVELY_LEAVING_STATE;
// start the transition // start the transition
transAnimation.start().then(() => { transAnimation.play().then(() => {
// transition has completed, update each item's state // transition has completed, update each item's state
enteringItem.state = ACTIVE_STATE; enteringItem.state = ACTIVE_STATE;

View File

@ -41,7 +41,7 @@ export * from 'ionic/engine/engine'
export * from 'ionic/engine/cordova/cordova' export * from 'ionic/engine/cordova/cordova'
export * from 'ionic/engine/electron/electron' export * from 'ionic/engine/electron/electron'
export * from 'ionic/collide/animation' export * from 'ionic/animations/animation'
export * from 'ionic/transitions/transition' export * from 'ionic/transitions/transition'
export * from 'ionic/transitions/none-transition' export * from 'ionic/transitions/none-transition'
export * from 'ionic/transitions/ios-transition' export * from 'ionic/transitions/ios-transition'

View File

@ -1,17 +1,21 @@
import {Animation} from '../collide/animation'; import {Animation} from '../animations/animation';
import {addEasing} from '../collide/easing';
import {rafPromise} from '../util/dom' import {rafPromise} from '../util/dom'
import {Transition} from './transition' import {Transition} from './transition'
const EASING_FN = [.36, .66, .04, 1]; const EASING_FN = [.36, .66, .04, 1];
const DURATION = 500; const DURATION = 500;
const OFF_RIGHT = '100%';
const OFF_LEFT = '-33%'; const OPACITY = 'opacity';
const TRANSFORM = 'transform';
const CENTER = 'none';
const OFF_RIGHT = 'translate3d(100%,0px,0px)';
const OFF_LEFT = 'translate3d(-33%,0px,0px)';
const OFF_OPACITY = 0.8; const OFF_OPACITY = 0.8;
const TRANSLATE_X = 'translateX'; const SHOW_TOOLBAR_CSS = 'show-toolbar';
const OPACITY = 'opacity'; const SHOW_NAV_ITEM_CSS = 'show-nav-item';
class IOSTransition extends Animation { class IOSTransition extends Animation {
@ -21,7 +25,6 @@ class IOSTransition extends Animation {
// global duration and easing for all child animations // global duration and easing for all child animations
this.duration(DURATION); this.duration(DURATION);
this.easing('ios');
// get the entering and leaving items // get the entering and leaving items
this.enteringItem = navCtrl.getStagedEnteringItem(); this.enteringItem = navCtrl.getStagedEnteringItem();
@ -50,72 +53,73 @@ class IOSTransition extends Animation {
// entering item moves to center // entering item moves to center
// before starting, set enteringItem to display: block // before starting, set enteringItem to display: block
enteringContent enteringContent
.addStartClass('show-nav-item') .beforePlay.addClass(SHOW_NAV_ITEM_CSS)
.to(TRANSLATE_X, 0) .to(TRANSFORM, CENTER)
.to(OPACITY, 1); .to(OPACITY, 1);
enteringTitle enteringTitle
.to(TRANSLATE_X, 0) .to(TRANSFORM, CENTER)
.to(OPACITY, 1); .to(OPACITY, 1);
enteringToolbars enteringToolbars
.addStartClass('show-toolbar'); .beforePlay.addClass(SHOW_TOOLBAR_CSS);
// leaving view moves off screen // leaving view moves off screen
// when completed, set leavingItem to display: none // when completed, set leavingItem to display: none
leavingContent leavingContent
.removeEndClass('show-nav-item') .afterFinish.removeClass(SHOW_NAV_ITEM_CSS)
.from(TRANSLATE_X, 0) .from(TRANSFORM, CENTER)
.from(OPACITY, 1); .from(OPACITY, 1);
leavingToolbars leavingToolbars
.removeEndClass('show-toolbar'); .afterFinish.removeClass(SHOW_TOOLBAR_CSS);
leavingTitle leavingTitle
.from(TRANSLATE_X, 0) .from(TRANSFORM, CENTER)
.from(OPACITY, 1); .from(OPACITY, 1);
// set properties depending on direction // set properties depending on direction
if (opts.direction === 'back') { if (opts.direction === 'back') {
// back direction // back direction
enteringContent enteringContent
.from(TRANSLATE_X, OFF_LEFT) .from(TRANSFORM, OFF_LEFT)
.from(OPACITY, OFF_OPACITY) .from(OPACITY, OFF_OPACITY)
.to(OPACITY, 1); .to(OPACITY, 1);
enteringTitle enteringTitle
.from(TRANSLATE_X, OFF_LEFT) .from(TRANSFORM, OFF_LEFT)
.from(OPACITY, 0) .from(OPACITY, 0)
.to(OPACITY, 1); .to(OPACITY, 1);
leavingContent leavingContent
.to(TRANSLATE_X, OFF_RIGHT) .to(TRANSFORM, OFF_RIGHT)
.to(OPACITY, 1); .to(OPACITY, 1);
leavingTitle leavingTitle
.to(TRANSLATE_X, OFF_RIGHT) .to(TRANSFORM, OFF_RIGHT)
.to(OPACITY, 1); .to(OPACITY, 1);
} else { } else {
// forward direction // forward direction
enteringContent enteringContent
.from(TRANSLATE_X, OFF_RIGHT) .from(TRANSFORM, OFF_RIGHT)
.from(OPACITY, 1); .from(OPACITY, 1);
enteringTitle enteringTitle
.from(TRANSLATE_X, OFF_RIGHT); .from(TRANSFORM, OFF_RIGHT);
leavingContent leavingContent
.to(TRANSLATE_X, OFF_LEFT) .to(TRANSFORM, OFF_LEFT)
.to(OPACITY, OFF_OPACITY); .to(OPACITY, OFF_OPACITY);
leavingTitle leavingTitle
.to(TRANSLATE_X, OFF_LEFT) .to(TRANSFORM, OFF_LEFT)
.to(OPACITY, 0); .to(OPACITY, 0);
} }
// set child animations // set child animations
this.setChildren([enteringContent, enteringToolbars, enteringTitle, leavingContent, leavingToolbars, leavingTitle]); this.children(enteringContent, enteringToolbars, enteringTitle, leavingContent, leavingToolbars, leavingTitle);
} }
stage() { stage() {
@ -124,6 +128,4 @@ class IOSTransition extends Animation {
} }
addEasing('ios', EASING_FN);
Transition.register('ios', IOSTransition); Transition.register('ios', IOSTransition);

View File

@ -1,6 +1,10 @@
import {Transition} from './transition' import {Transition} from './transition'
const SHOW_TOOLBAR_CSS = 'show-toolbar';
const SHOW_NAV_ITEM_CSS = 'show-nav-item';
class NoneTransition { class NoneTransition {
constructor(navCtrl) { constructor(navCtrl) {
@ -10,26 +14,24 @@ class NoneTransition {
// show entering contet // show entering contet
let enteringContent = enteringItem.getContent(); let enteringContent = enteringItem.getContent();
enteringContent.classList.add('show-nav-item'); enteringContent.classList.add(SHOW_NAV_ITEM_CSS);
enteringContent.style.transform = 'translateX(0%)';
// show entering headers // show entering headers
let enteringToolbars = enteringItem.getToolbars(); let enteringToolbars = enteringItem.getToolbars();
for (let i = 0; i < enteringToolbars.length; i++) { for (let i = 0; i < enteringToolbars.length; i++) {
enteringToolbars[i].classList.add('show-toolbar'); enteringToolbars[i].classList.add(SHOW_TOOLBAR_CSS);
enteringToolbars[i].style.transform = 'translateX(0%)';
} }
// hide the leaving item // hide the leaving item
if (leavingItem) { if (leavingItem) {
let leavingContent = leavingItem.getContent(); let leavingContent = leavingItem.getContent();
if (leavingContent) { if (leavingContent) {
leavingContent.classList.remove('show-nav-item'); leavingContent.classList.remove(SHOW_NAV_ITEM_CSS);
} }
let leavingToolbars = leavingItem.getToolbars(); let leavingToolbars = leavingItem.getToolbars();
for (let i = 0; i < leavingToolbars.length; i++) { for (let i = 0; i < leavingToolbars.length; i++) {
leavingToolbars[i].classList.remove('show-toolbar'); leavingToolbars[i].classList.remove(SHOW_TOOLBAR_CSS);
} }
} }
} }
@ -39,7 +41,7 @@ class NoneTransition {
return Promise.resolve(); return Promise.resolve();
} }
start() { play() {
// immediately resolve // immediately resolve
return Promise.resolve(); return Promise.resolve();
} }

View File

@ -11,7 +11,11 @@
Loading... Loading...
</ion-app> </ion-app>
<!-- scripts auto-added by angular build process -->
$SCRIPTS$ $SCRIPTS$
<!-- web animations polyfill for non-chrome browsers -->
<script src="/polyfills/web-animations.min.js"></script>
</body> </body>
</html> </html>