mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
upgrade angular
This commit is contained in:
@@ -1,163 +0,0 @@
|
||||
import {dom} from 'ionic2/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,
|
||||
progress: 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: {}
|
||||
};
|
||||
},
|
||||
|
||||
/* 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,172 +0,0 @@
|
||||
/* Ported from Velocity.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
|
||||
|
||||
import {Collide} from 'ionic2/collide/collide'
|
||||
import {CSS} from 'ionic2/collide/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 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 x = 0, transforms3DLength = CSS.Lists.transforms3D.length; i < transforms3DLength; i++) {
|
||||
var transformName = CSS.Lists.transforms3D[x];
|
||||
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; }, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**********************
|
||||
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 jQuery's 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, callsLength = Collide.State.calls.length; j < callsLength; j++) {
|
||||
if (Collide.State.calls[j] !== false) {
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
@@ -1,896 +0,0 @@
|
||||
/* Ported from Velocity.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
|
||||
|
||||
import {Collide} from 'ionic2/collide/collide'
|
||||
|
||||
const data = Collide.data;
|
||||
|
||||
|
||||
/*****************
|
||||
CSS Stack
|
||||
*****************/
|
||||
|
||||
/* The CSS object is a highly condensed and performant CSS stack that fully replaces jQuery's.
|
||||
It handles the validation, getting, and setting of both standard CSS properties and CSS property hooks. */
|
||||
/* Note: A 'CSS' shorthand is aliased so that our code is easier to read. */
|
||||
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;
|
||||
|
||||
/* In IE, color values inside compound-value properties are positioned at the end the value instead of at the beginning.
|
||||
Thus, we re-arrange the templates accordingly. */
|
||||
// if (IE) {
|
||||
// for (rootProperty in CSS.Hooks.templates) {
|
||||
// hookTemplate = CSS.Hooks.templates[rootProperty];
|
||||
// hookNames = hookTemplate[0].split(' ');
|
||||
|
||||
// var defaultValues = hookTemplate[1].match(CSS.RegEx.valueSplit);
|
||||
|
||||
// if (hookNames[0] === 'Color') {
|
||||
// // Reposition both the hook's name and its default value to the end of their respective strings.
|
||||
// hookNames.push(hookNames.shift());
|
||||
// defaultValues.push(defaultValues.shift());
|
||||
|
||||
// // Replace the existing template for the hook's root property.
|
||||
// CSS.Hooks.templates[rootProperty] = [ hookNames.join(' '), defaultValues.join(' ') ];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/* 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, jQuery's $.position() only returns values for top and left;
|
||||
right and bottom will have their 'auto' value reverted to 0. */
|
||||
/* Note: A jQuery object must be created here since jQuery doesn't have a low-level alias for $.position().
|
||||
Not a big deal since we're currently in a GET batch anyway. */
|
||||
if (position === 'fixed' || (position === 'absolute' && /top|left/i.test(property))) {
|
||||
/* Note: jQuery strips the pixel unit from its returned values; we re-add it here to conform with computePropertyValue's behavior. */
|
||||
// TODO!!!!
|
||||
computedValue = $(element).position()[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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const vendorPrefixes = [ '', 'Webkit', 'ms' ];
|
||||
@@ -1,312 +0,0 @@
|
||||
/* Ported from Velocity.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
|
||||
|
||||
import * as util from 'ionic2/util/util'
|
||||
import {Collide} from 'ionic2/collide/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.0 ] ],
|
||||
[ "ease-in", [ 0.42, 0.0, 1.00, 1.0 ] ],
|
||||
[ "ease-out", [ 0.00, 0.0, 0.58, 1.0 ] ],
|
||||
[ "ease-in-out", [ 0.42, 0.0, 0.58, 1.0 ] ],
|
||||
[ "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 (let x = 0; x < penner.length; x++) {
|
||||
Collide.Easings[ penner[x][0] ] = generateBezier.apply(null, penner[x][1]);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
|
||||
/* 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;
|
||||
}
|
||||
@@ -1,945 +0,0 @@
|
||||
/* Ported from Velocity.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
|
||||
|
||||
import * as util from 'ionic2/util/util'
|
||||
import {Collide} from 'ionic2/collide/collide'
|
||||
import {CSS} from 'ionic2/collide/css'
|
||||
import {getEasing} from 'ionic2/collide/easing'
|
||||
import {tick} from 'ionic2/collide/tick'
|
||||
|
||||
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 elementProcess(action, elements, elementsIndex, options, propertiesMap) {
|
||||
var resolve;
|
||||
var promise = new Promise(function(res) {
|
||||
resolve = res;
|
||||
});
|
||||
|
||||
var element = elements[elementsIndex];
|
||||
var elementsLength = elements.length;
|
||||
|
||||
|
||||
/**************************
|
||||
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. */
|
||||
var callUnitConversionData = {
|
||||
lastParent: null,
|
||||
lastPosition: null,
|
||||
lastFontSize: null,
|
||||
lastPercentToPxWidth: null,
|
||||
lastPercentToPxHeight: null,
|
||||
lastEmToPx: null,
|
||||
remToPx: null,
|
||||
vwToPx: null,
|
||||
vhToPx: null
|
||||
};
|
||||
|
||||
/* A container for all the ensuing tween data and metadata associated with this call. This container gets pushed to the page-wide
|
||||
Collide.State.calls array that is processed during animation ticking. */
|
||||
var call = [];
|
||||
|
||||
|
||||
/*************************
|
||||
Part I: Pre-Queueing
|
||||
*************************/
|
||||
|
||||
/***************************
|
||||
Element-Wide Variables
|
||||
***************************/
|
||||
|
||||
/* The runtime opts object is the extension of the current call's options and Collide's page-wide option defaults. */
|
||||
var opts = util.extend({}, Collide.defaults, options);
|
||||
|
||||
/* 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) {
|
||||
Collide.initData(element);
|
||||
}
|
||||
|
||||
|
||||
/******************
|
||||
Option: Delay
|
||||
******************/
|
||||
|
||||
/* Since queue:false doesn't respect the item's existing queue, we avoid injecting its delay here (it's set later on). */
|
||||
/* Note: Collide rolls its own delay function since jQuery doesn't have a utility alias for $.fn.delay()
|
||||
(and thus requires jQuery element creation, which we avoid since its overhead includes DOM querying). */
|
||||
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 && elementsIndex === 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 either a jQuery object or a raw DOM element was passed in. */
|
||||
if (util.isWrapped(opts.container) || util.isNode(opts.container)) {
|
||||
/* Extract the raw DOM element from the jQuery wrapper. */
|
||||
opts.container = opts.container[0] || opts.container;
|
||||
/* 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 */
|
||||
|
||||
/* $.position() values are relative to the container's currently viewable area (without taking into account the container's true dimensions
|
||||
-- say, 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 + $(element).position()[scrollDirection.toLowerCase()]) + scrollOffset; /* GET */
|
||||
|
||||
} else {
|
||||
/* If a value other than a jQuery object or 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 $.position(), $.offset() 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 = $(element).offset()[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
|
||||
};
|
||||
|
||||
|
||||
/******************************************
|
||||
Tween Data Construction (for Reverse)
|
||||
******************************************/
|
||||
|
||||
/* Reverse acts like a 'start' action in that a property map is animated toward. The only difference is
|
||||
that the property map used for reverse is the inverse of the map used in the previous call. Thus, we manipulate
|
||||
the previous call to construct our new map: use the previous map's end values as our new map's start values. Copy over all other data. */
|
||||
/* Note: Reverse can be directly called via the 'reverse' parameter, or it can be indirectly triggered via the loop option. (Loops are composed of multiple reverses.) */
|
||||
/* Note: Reverse calls do not need to be consecutively chained onto a currently-animating element in order to operate on cached values;
|
||||
there is no harm to reverse being called on a potentially stale data cache since reverse's behavior is simply defined
|
||||
as reverting to the element's values as they were prior to the previous *Collide* call. */
|
||||
} else if (action === 'reverse') {
|
||||
/* Abort if there is no prior animation data to reverse to. */
|
||||
if (!data(element).tweensContainer) {
|
||||
/* Dequeue the element so that this queue entry releases itself immediately, allowing subsequent queue entries to run. */
|
||||
Collide.dequeue(element, opts.queue);
|
||||
return;
|
||||
|
||||
} else {
|
||||
/*********************
|
||||
Options Parsing
|
||||
*********************/
|
||||
|
||||
/* If the element was hidden via the display option in the previous call,
|
||||
revert display to 'auto' prior to reversal so that the element is visible again. */
|
||||
if (data(element).opts.display === 'none') {
|
||||
data(element).opts.display = 'auto';
|
||||
}
|
||||
|
||||
if (data(element).opts.visibility === 'hidden') {
|
||||
data(element).opts.visibility = 'visible';
|
||||
}
|
||||
|
||||
/* If the loop option was set in the previous call, disable it so that 'reverse' calls aren't recursively generated.
|
||||
Further, remove the previous call's callback options; typically, users do not want these to be refired. */
|
||||
data(element).opts.loop = false;
|
||||
data(element).opts.begin = null;
|
||||
data(element).opts.complete = null;
|
||||
|
||||
/* Since we're extending an opts object that has already been extended with the defaults options object,
|
||||
we remove non-explicitly-defined properties that are auto-assigned values. */
|
||||
if (!options.easing) {
|
||||
delete opts.easing;
|
||||
}
|
||||
|
||||
if (!options.duration) {
|
||||
delete opts.duration;
|
||||
}
|
||||
|
||||
/* The opts object used for reversal is an extension of the options object optionally passed into this
|
||||
reverse call plus the options used in the previous Collide call. */
|
||||
opts = util.extend({}, data(element).opts, opts);
|
||||
|
||||
/*************************************
|
||||
Tweens Container Reconstruction
|
||||
*************************************/
|
||||
|
||||
/* Create a deepy copy (indicated via the true flag) of the previous call's tweensContainer. */
|
||||
var lastTweensContainer = util.extend(true, {}, data(element).tweensContainer);
|
||||
|
||||
/* Manipulate the previous tweensContainer by replacing its end values and currentValues with its start values. */
|
||||
for (var lastTween in lastTweensContainer) {
|
||||
/* In addition to tween data, tweensContainers contain an element property that we ignore here. */
|
||||
if (lastTween !== 'element') {
|
||||
var lastStartValue = lastTweensContainer[lastTween].startValue;
|
||||
|
||||
lastTweensContainer[lastTween].startValue = lastTweensContainer[lastTween].currentValue = lastTweensContainer[lastTween].endValue;
|
||||
lastTweensContainer[lastTween].endValue = lastStartValue;
|
||||
|
||||
/* Easing is the only option that embeds into the individual tween data (since it can be defined on a per-property basis).
|
||||
Accordingly, every property's easing value must be updated when an options object is passed in with a reverse call.
|
||||
The side effect of this extensibility is that all per-property easing values are forcefully reset to the new value. */
|
||||
if (!util.isEmptyObject(options)) {
|
||||
lastTweensContainer[lastTween].easing = opts.easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tweensContainer = lastTweensContainer;
|
||||
}
|
||||
|
||||
|
||||
/*********************************
|
||||
Start: Tween Data Construction
|
||||
*********************************/
|
||||
|
||||
} 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. */
|
||||
if (data(element).tweensContainer && data(element).isAnimating === true) {
|
||||
lastTweensContainer = data(element).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. Read Velocity's docmentation to learn more about forcefeeding: VelocityJS.org/#forcefeeding */
|
||||
function parsePropertyValue(valueData, skipResolvingEasing) {
|
||||
var endValue = undefined,
|
||||
easing = undefined,
|
||||
startValue = undefined;
|
||||
|
||||
/* Handle the array format, which can be structured as one of three potential overloads:
|
||||
A) [ endValue, easing, startValue ], B) [ endValue, easing ], or C) [ endValue, startValue ] */
|
||||
if (Array.isArray(valueData)) {
|
||||
/* endValue is always the first item in the array. Don't bother validating endValue's value now
|
||||
since the ensuing property cycling logic does that. */
|
||||
endValue = valueData[0];
|
||||
|
||||
/* Two-item array format: If the second item is a number, function, or hex string, treat it as a
|
||||
start value since easings can only be non-hex strings or arrays. */
|
||||
if ((!Array.isArray(valueData[1]) && /^[\d-]/.test(valueData[1])) || typeof valueData[1] === 'function' || CSS.RegEx.isHex.test(valueData[1])) {
|
||||
startValue = valueData[1];
|
||||
|
||||
/* Two or three-item array: If the second item is a non-hex string or an array, treat it as an easing. */
|
||||
} else if ((util.isString(valueData[1]) && !CSS.RegEx.isHex.test(valueData[1])) || Array.isArray(valueData[1])) {
|
||||
easing = skipResolvingEasing ? valueData[1] : getEasing(valueData[1], opts.duration);
|
||||
|
||||
/* Don't bother validating startValue's value now since the ensuing property cycling logic inherently does that. */
|
||||
if (valueData[2] !== undefined) {
|
||||
startValue = valueData[2];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/* Handle the single-value format. */
|
||||
endValue = valueData;
|
||||
}
|
||||
|
||||
/* 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, elementsIndex, elementsLength);
|
||||
}
|
||||
|
||||
if (typeof startValue === 'function') {
|
||||
startValue = startValue.call(element, elementsIndex, 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 (!data(element).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 = data(element).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 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. */
|
||||
function calculateUnitRatios() {
|
||||
|
||||
/**************************************************************
|
||||
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
|
||||
**********************************************************************/
|
||||
|
||||
/* Note: IE8 rounds to the nearest pixel when returning CSS values, thus we perform conversions using a measurement
|
||||
of 100 (instead of 1) to give our ratios a precision of at least 2 decimal values. */
|
||||
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 x = 0; x < overflows.length; x++) {
|
||||
Collide.CSS.setPropertyValue(dummy, cssPropNames[x], '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 x = 0; x < overflows.length; x++) {
|
||||
Collide.CSS.setPropertyValue(dummy, cssPropNames[x], 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. */
|
||||
callUnitConversionData.remToPx = parseFloat(CSS.getPropertyValue(document.body, 'fontSize')) || 16; /* GET */
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
} // END: calculateUnitRatios()
|
||||
|
||||
|
||||
/****************************************
|
||||
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();
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************
|
||||
parsePropertyValue(), tweensContainer Push
|
||||
*********************************************/
|
||||
|
||||
/* Construct the per-property tween object, and push it to the element's tweensContainer. */
|
||||
tweensContainer[property] = {
|
||||
rootPropertyValue: rootPropertyValue,
|
||||
startValue: startValue,
|
||||
currentValue: startValue,
|
||||
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 === '') {
|
||||
data(element).tweensContainer = tweensContainer;
|
||||
data(element).opts = opts;
|
||||
}
|
||||
|
||||
/* Switch on the element's animating flag. */
|
||||
data(element).isAnimating = true;
|
||||
|
||||
/* Once the final element in this call's element set has been processed, push the call array onto
|
||||
Collide.State.calls for the animation tick to immediately begin processing. */
|
||||
if (elementsIndex === elementsLength - 1) {
|
||||
/* 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([ call, elements, opts, null, resolve ]);
|
||||
|
||||
/* If the animation tick isn't running, start it. (Collide shuts it off when there are no active calls to process.) */
|
||||
if (Collide.State.isTicking === false) {
|
||||
Collide.State.isTicking = true;
|
||||
|
||||
/* Start the tick loop. */
|
||||
tick();
|
||||
}
|
||||
|
||||
} else {
|
||||
elementsIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
} // 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*********************
|
||||
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 jQuery prepends to active queue stack arrays.) Regardless, whenever the element's
|
||||
queue is further appended with additional items -- including $.delay()'s or even $.animate() 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. */
|
||||
/* Note: Unfortunately, most people don't fully grasp jQuery's powerful, yet quirky, Collide.queue() function.
|
||||
Lean more here: http://stackoverflow.com/questions/1058158/can-somebody-explain-jquery-queue-to-me */
|
||||
if ((opts.queue === '' || opts.queue === 'fx') && Collide.queue(element)[0] !== 'inprogress') {
|
||||
Collide.dequeue(element);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
/* Ported from Velocity.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
|
||||
|
||||
import {Collide} from 'ionic2/collide/collide'
|
||||
import {CSS} from 'ionic2/collide/css'
|
||||
import {completeCall} from 'ionic2/collide/complete-call'
|
||||
import {dom} from 'ionic2/util'
|
||||
|
||||
|
||||
/************
|
||||
Tick
|
||||
************/
|
||||
|
||||
/* Note: All calls to Collide are pushed to the Collide.State.calls array, which is fully iterated through upon each tick. */
|
||||
export 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 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;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
var 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, callLength = call.length; j < callLength; j++) {
|
||||
var tweensContainer = call[j],
|
||||
element = tweensContainer.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 (!Collide.data(element)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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') {
|
||||
var flexValues = [ '-webkit-box', '-moz-box', '-ms-flexbox', '-webkit-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],
|
||||
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*. */
|
||||
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 = Collide.data(element).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]) {
|
||||
Collide.data(element).rootPropertyValueCache[hookRoot] = CSS.Normalizations.registered[hookRoot]('extract', null, adjustedSetData[1]);
|
||||
} else {
|
||||
Collide.data(element).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, callLength = call.length; j < callLength; 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,155 +0,0 @@
|
||||
/* Ported from Collide.js, MIT License. Julian Shapiro http://twitter.com/shapiro */
|
||||
|
||||
import {Collide} from 'ionic2/collide/collide'
|
||||
import {elementProcess} from 'ionic2/collide/element-process'
|
||||
|
||||
|
||||
export function transitionAction(action, elements, options, propertiesMap) {
|
||||
|
||||
elements = elements && !elements.length ? [elements] : elements
|
||||
|
||||
if (!elements) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
/* The length of the element set (in the form of a nodeList or an array of elements) is defaulted to 1 in case a
|
||||
single raw DOM element is passed in (which doesn't contain a length property). */
|
||||
var elementsLength = elements.length;
|
||||
var elementsIndex = 0;
|
||||
var eleData;
|
||||
|
||||
|
||||
if (action === 'stop') {
|
||||
/******************
|
||||
Action: Stop
|
||||
*******************/
|
||||
|
||||
/* Clear the currently-active delay on each targeted element. */
|
||||
for (var x = 0; x < elements.length; x++) {
|
||||
eleData = Collide.data(elements[x]);
|
||||
|
||||
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 = [];
|
||||
var activeCall;
|
||||
|
||||
/* 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 x = 0, callsLength = Collide.State.calls.length; x < callsLength; x++) {
|
||||
|
||||
/* Inactive calls are set to false by the logic inside completeCall(). Skip them. */
|
||||
activeCall = Collide.State.calls[x];
|
||||
if (activeCall) {
|
||||
|
||||
/* Iterate through the active call's targeted elements. */
|
||||
|
||||
$.each(activeCall[1], function(k, activeElement) {
|
||||
/* 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:
|
||||
- options === true --> stop current default queue calls (and queue:false calls), including remaining queued ones.
|
||||
- options === undefined --> stop current queue:'' call and all queue:false calls.
|
||||
- options === false --> stop only queue:false calls.
|
||||
- options === '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 = (options === undefined) ? '' : options;
|
||||
|
||||
if (queueName !== true && (activeCall[2].queue !== queueName) && !(options === undefined && activeCall[2].queue === false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Iterate through the calls targeted by the stop command. */
|
||||
$.each(elements, function(l, element) {
|
||||
/* Check that this call was applied to the target element. */
|
||||
if (element === activeElement) {
|
||||
/* Optionally clear the remaining queued calls. */
|
||||
if (options === true || Type.isString(options)) {
|
||||
/* Iterate through the items in the element's queue. */
|
||||
$.each($.queue(element, Type.isString(options) ? options : ''), function(_, item) {
|
||||
/* The queue array can contain an 'inprogress' string, which we skip. */
|
||||
if (Type.isFunction(item)) {
|
||||
/* 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.) */
|
||||
item(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
/* Clearing the $.queue() array is achieved by resetting it to []. */
|
||||
$.queue(element, Type.isString(options) ? options : '', []);
|
||||
}
|
||||
|
||||
if (propertiesMap === '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. */
|
||||
if (Data(element) && Data(element).tweensContainer && queueName !== false) {
|
||||
$.each(Data(element).tweensContainer, function(m, activeTween) {
|
||||
activeTween.endValue = activeTween.currentValue;
|
||||
});
|
||||
}
|
||||
|
||||
callsToStop.push(i);
|
||||
} else if (propertiesMap === '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 x = 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 (propertiesMap === 'stop') {
|
||||
$.each(callsToStop, function(i, j) {
|
||||
completeCall(j, true);
|
||||
});
|
||||
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**************************
|
||||
Element Set Iteration
|
||||
**************************/
|
||||
|
||||
var promises = [];
|
||||
|
||||
if (elements && elements.length) {
|
||||
for (var i = 0, l = elements.length; i < l; i++) {
|
||||
if (elements[i] && elements[i].parentElement) {
|
||||
|
||||
promises.push(
|
||||
elementProcess(action, elements, i, options, propertiesMap)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import {transitionAction} from 'ionic2/collide/transition-action'
|
||||
|
||||
|
||||
export class Transition {
|
||||
constructor(ele) {
|
||||
console.log('Transition', ele)
|
||||
|
||||
if (!ele || ele.length === 0) return
|
||||
|
||||
this.elements = !ele.length ? [ele] : ele
|
||||
ele = null
|
||||
|
||||
// Animations that happen to this element
|
||||
this.animations = []
|
||||
|
||||
// Sub transitions that happen to sub elements
|
||||
this.transitions = []
|
||||
|
||||
this.options = {}
|
||||
this.propertiesMap = {}
|
||||
|
||||
this.isRunning = false
|
||||
}
|
||||
|
||||
start() {
|
||||
var p = transitionAction('start', this.elements, this.options, this.propertiesMap)
|
||||
|
||||
p.then(() => {
|
||||
console.log('start success done')
|
||||
}).catch(() => {
|
||||
console.log('start error done')
|
||||
})
|
||||
}
|
||||
|
||||
stop() {
|
||||
transitionAction('stop', this.elements, this.options, this.propertiesMap)
|
||||
}
|
||||
|
||||
properties(val) {
|
||||
this.propertiesMap = val || {}
|
||||
}
|
||||
|
||||
property(key, val) {
|
||||
this.propertiesMap[key] = val
|
||||
}
|
||||
|
||||
removeProperty(key) {
|
||||
delete this.propertiesMap[key]
|
||||
}
|
||||
|
||||
duration(val) {
|
||||
this.options.duration = val
|
||||
}
|
||||
|
||||
easing(val) {
|
||||
this.options.easing = val
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class IOSTransition extends Transition {
|
||||
constructor(ele) {
|
||||
super(ele)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user