collide updates

This commit is contained in:
Adam Bradley
2015-04-27 22:34:28 -05:00
parent 7fb5c90569
commit d1c78bc5bf
9 changed files with 248 additions and 282 deletions

View File

@ -1,64 +0,0 @@
/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */
import {Collide} from './collide'
import {animationProcess} from './animation-process'
export function animationStart(elements, options, propertiesMap) {
if (!elements || !elements.length) {
return Promise.resolve();
}
/* 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;
/**********************************
Animation Call-Wide Variables
**********************************/
/* A container for CSS unit conversion ratios (e.g. %, rem, and em ==> px) that is used to cache ratios across all elements
being animated in a single Collide call. Calculating unit ratios necessitates DOM querying and updating, and is therefore
avoided (via caching) wherever possible. This container is call-wide instead of page-wide to avoid the risk of using stale
conversion metrics across Collide animations that are not immediately consecutively chained. */
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 = [];
/**************************
Element Set Iteration
**************************/
var promises = [];
if (elements && elements.length) {
for (var i = 0, l = elements.length; i < l; i++) {
if (elements[i]) {
promises.push(
animationProcess('start', elements, i, options, propertiesMap, callUnitConversionData, call)
);
}
}
}
return Promise.all(promises);
};

View File

@ -1,8 +1,11 @@
import * as util from 'ionic/util/util'
import {Collide} from './collide'
import {animationStart} from './animation-start'
import {processElement} from './process-element'
import {animationStop} from './animation-stop'
import {startTick} from './tick'
const data = Collide.data;
export class Animation {
constructor() {
@ -19,34 +22,150 @@ export class Animation {
}
}
_setupElements(clearCache) {
/**********************************
Animation Call-Wide Variables
**********************************/
/*************
Actions
*************/
/* A container for CSS unit conversion ratios (e.g. %, rem, and em ==> px) that is used to cache ratios across all elements
being animated in a single Collide call. Calculating unit ratios necessitates DOM querying and updating, and is therefore
avoided (via caching) wherever possible. This container is call-wide instead of page-wide to avoid the risk of using stale
conversion metrics across Collide animations that are not immediately consecutively chained. */
this._unitConversion = {
lastParent: null,
lastPosition: null,
lastFontSize: null,
lastPercentToPxWidth: null,
lastPercentToPxHeight: null,
lastEmToPx: null,
remToPx: null,
vwToPx: null,
vhToPx: null
};
start() {
let promise = animationStart(this._elements, this._options, this._properties);
this._call = [];
this._options = util.extend({}, Collide.defaults, this._options);
for (var i = 0, ii = this._elements.length; i < ii; i++) {
processElement('start', this, i, clearCache);
}
}
_queueAnimation() {
/* Switch on the element's animating flag. */
for (var i = 0, ii = this._elements.length, element; i < ii && (element = this._elements[i]); i++) {
data(element).isAnimating = true;
/*********************
Auto-Dequeuing
*********************/
/* To fire the first non-custom-queue entry on an element, the element
must be dequeued if its queue stack consists *solely* of the current call. (This can be determined by checking
for the 'inprogress' item that is prepended to active queue stack arrays.) Regardless, whenever the element's
queue is further appended with additional items -- including delay()'s calls, the queue's
first entry is automatically fired. This behavior contrasts that of custom queues, which never auto-fire. */
/* Note: When an element set is being subjected to a non-parallel Collide call, the animation will not begin until
each one of the elements in the set has reached the end of its individually pre-existing queue chain. */
if (this._options.queue === '' && Collide.queue(element)[0] !== 'inprogress') {
Collide.dequeue(element);
}
}
/* 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. */
/* Add the current call plus its associated metadata (the element set and the call's options) onto the global call container.
Anything on this call container is subjected to tick() processing. */
Collide.State.calls.push([ this._call,
this._elements,
this._options,
null,
this._resolve ]);
startTick();
}
return promise;
start() {
// get the elements ready
if (this.isAnimating()) {
this.stop();
if (this._resolve) {
this._resolve();
}
}
this._resolve;
this._promise = new Promise(res => {
this._resolve = res;
});
this._setupElements(this._lastType !== 'start');
this._lastType = 'start';
this._queueAnimation();
return this._promise;
}
stop() {
// immediately stop where it's at
animationStop(this._elements, 'stop');
}
percent(ratio) {
this._options.percentComplete = parseFloat(ratio);
animationStart(this._elements, this._options, this._properties);
finish() {
// immediately go to the end of the animation
animationStop(this._elements, 'finish');
}
ready() {
// get the elements ready
if (this.isAnimating()) {
this.stop();
if (this._resolve) {
this._resolve();
}
}
this._resolve;
this._promise = new Promise(res => {
this._resolve = res;
});
this._setupElements(this._lastType !== 'percent');
this._lastType = 'percent';
return this._promise;
}
percent(percentComplete) {
// go to and stop at a specific point in the animation
// must call ready() first
this._options.percentComplete = parseFloat(percentComplete);
this._queueAnimation();
startTick();
}
isAnimating() {
var eleData;
if (this._elements) {
for (var i = 0, ii = this._elements.length; i < ii; i++) {
eleData = data(this._elements[i]);
if (eleData && eleData.isAnimating) {
return true;
}
}
}
return false;
}
/***********************
/************
Options
***********************/
************/
options(val) {
this._options = val || {};
}

View File

@ -61,7 +61,7 @@ export function completeCall(callIndex, isStopped) {
eleData.rootPropertyValueCache = {};
/* If any 3D transform subproperty is at its default value (regardless of unit type), remove it. */
for (var j = 0, transforms3DLength = CSS.Lists.transforms3D.length; j < transforms3DLength; j++) {
for (var j = 0, jj = CSS.Lists.transforms3D.length; j < jj; j++) {
var transformName = CSS.Lists.transforms3D[j];
var defaultValue = /^scale/.test(transformName) ? 1 : 0;
var currentValue = eleData.transformCache[transformName];
@ -154,7 +154,7 @@ export function completeCall(callIndex, isStopped) {
/* 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++) {
for (var j = 0, jj = Collide.State.calls.length; j < jj; j++) {
if (Collide.State.calls[j] !== false) {
remainingCallsExist = true;
break;

View File

@ -19,14 +19,16 @@ const data = Collide.data;
3) Pushing: Consolidation of the tween data followed by its push onto the global in-progress calls container.
*/
export function animationProcess(action, elements, elementsIndex, options, propertiesMap, callUnitConversionData, call) {
var resolve;
var promise = new Promise(function(res) {
resolve = res;
});
var element = elements[elementsIndex];
export function processElement(action, animation, elementIndex, clearCache) {
var elements = animation._elements;
var elementsLength = elements.length;
var element = elements[elementIndex];
var opts = animation._options;
var propertiesMap = animation._properties;
var callUnitConversionData = animation._unitConversion;
var call = animation._call;
var resolve = animation._resolve;
/*************************
@ -37,9 +39,6 @@ export function animationProcess(action, elements, elementsIndex, options, prope
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 = {};
@ -143,7 +142,7 @@ export function animationProcess(action, elements, elementsIndex, options, prope
*******************/
/* 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) {
if (opts.begin && elementIndex === 0) {
/* We throw callbacks in a setTimeout so that thrown errors don't halt the execution of Collide itself. */
try {
opts.begin.call(elements, elements);
@ -219,94 +218,6 @@ export function animationProcess(action, elements, elementsIndex, options, prope
if (Collide.debug) console.log("tweensContainer (scroll): ", tweensContainer.scroll, 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;
}
if (Collide.debug) console.log("reverse tweensContainer (" + lastTween + "): " + JSON.stringify(lastTweensContainer[lastTween]), element);
}
}
tweensContainer = lastTweensContainer;
}
/*********************************
Start: Tween Data Construction
*********************************/
} else if (action === 'start') {
/****************************
@ -325,8 +236,11 @@ export function animationProcess(action, elements, elementsIndex, options, prope
/* 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;
var eleData = data(element);
if (clearCache) {
eleData.tweensContainer = undefined;
} else if (eleData.tweensContainer && eleData.isAnimating === true) {
lastTweensContainer = eleData.tweensContainer;
}
@ -379,11 +293,11 @@ export function animationProcess(action, elements, elementsIndex, options, prope
/* 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);
endValue = endValue.call(element, elementIndex, elementsLength);
}
if (typeof startValue === 'function') {
startValue = startValue.call(element, elementsIndex, elementsLength);
startValue = startValue.call(element, elementIndex, elementsLength);
}
/* Allow startValue to be left as undefined to indicate to the ensuing code that its value was not forcefed. */
@ -412,17 +326,17 @@ export function animationProcess(action, elements, elementsIndex, options, prope
/* Inject the RGB component tweens into propertiesMap. */
for (var i = 0; i < colorComponents.length; i++) {
var dataArray = [ endValueRGB[i] ];
var dataArray = [ endValueRGB[i] ];
if (easing) {
dataArray.push(easing);
}
if (easing) {
dataArray.push(easing);
}
if (startValueRGB !== undefined) {
dataArray.push(startValueRGB[i]);
}
if (startValueRGB !== undefined) {
dataArray.push(startValueRGB[i]);
}
propertiesMap[property + colorComponents[i]] = dataArray;
propertiesMap[property + colorComponents[i]] = dataArray;
}
/* Remove the intermediary shorthand property entry now that we've processed it. */
@ -470,10 +384,12 @@ export function animationProcess(action, elements, elementsIndex, options, prope
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;
}
@ -680,6 +596,7 @@ export function animationProcess(action, elements, elementsIndex, options, prope
break;
}
var currentValue = startValue;
/*********************************************
parsePropertyValue(), tweensContainer Push
@ -689,13 +606,15 @@ export function animationProcess(action, elements, elementsIndex, options, prope
tweensContainer[property] = {
rootPropertyValue: rootPropertyValue,
startValue: startValue,
currentValue: startValue,
currentValue: currentValue,
endValue: endValue,
unitType: endValueUnitType,
easing: easing
};
if (Collide.debug) console.log('tweensContainer (' + property + '): ' + JSON.stringify(tweensContainer[property]), element);
console.log('processElement parsePropertyValue: startValue', startValue, 'currentValue', currentValue, 'endValue', endValue);
}
/* Along with its property data, store a reference to the element itself onto tweensContainer. */
@ -720,20 +639,6 @@ export function animationProcess(action, elements, elementsIndex, options, prope
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 ]);
} else {
elementsIndex++;
}
}
} // END: buildQueue
@ -768,21 +673,4 @@ export function animationProcess(action, elements, elementsIndex, options, prope
});
}
/*********************
Auto-Dequeuing
*********************/
/* To fire the first non-custom-queue entry on an element, the element
must be dequeued if its queue stack consists *solely* of the current call. (This can be determined by checking
for the 'inprogress' item that is prepended to active queue stack arrays.) Regardless, whenever the element's
queue is further appended with additional items -- including delay()'s calls, the queue's
first entry is automatically fired. This behavior contrasts that of custom queues, which never auto-fire. */
/* Note: When an element set is being subjected to a non-parallel Collide call, the animation will not begin until
each one of the elements in the set has reached the end of its individually pre-existing queue chain. */
if ((opts.queue === '' || opts.queue === 'fx') && Collide.queue(element)[0] !== 'inprogress') {
Collide.dequeue(element);
}
return promise;
}

View File

@ -77,13 +77,14 @@ function tick(timestamp) {
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;
if (opts.percentComplete !== undefined) {
percentComplete = opts.percentComplete;
percentComplete = Math.max(Math.min(opts.percentComplete, 1), 0);
} else {
/* The tween's completion percentage is relative to the tween's start time, not the tween's start value
(which would result in unpredictable tween durations since JavaScript's timers are not particularly accurate).
Accordingly, we ensure that percentComplete does not exceed 1. */
percentComplete = Math.min((timeCurrent - timeStart) / opts.duration, 1);
}
@ -93,13 +94,14 @@ function tick(timestamp) {
**********************/
/* 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;
for (var j = 0, jj = call.length; j < jj; j++) {
var tweensContainer = call[j];
var element = tweensContainer.element;
var eleData = Collide.data(element);
/* Check to see if this element has been deleted midway through the animation by checking for the
continued existence of its data cache. If it's gone, skip animating this element. */
if (!Collide.data(element)) {
if (!eleData) {
continue;
}
@ -114,7 +116,6 @@ function tick(timestamp) {
(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]);
@ -139,11 +140,13 @@ function tick(timestamp) {
/* 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;
var tween = tweensContainer[property];
var currentValue;
/* Easing can either be a pre-genereated function or a string that references a pre-registered easing
on the Collide.Easings object. In either case, return the appropriate easing *function*. */
var easing = (typeof tween.easing === 'string' ? Collide.Easings[tween.easing] : tween.easing);
/******************************
Current Value Calculation
@ -185,7 +188,7 @@ function tick(timestamp) {
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];
rootPropertyValueCache = eleData.rootPropertyValueCache[hookRoot];
if (rootPropertyValueCache) {
tween.rootPropertyValue = rootPropertyValueCache;
@ -214,9 +217,9 @@ function tick(timestamp) {
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]);
eleData.rootPropertyValueCache[hookRoot] = CSS.Normalizations.registered[hookRoot]('extract', null, adjustedSetData[1]);
} else {
Collide.data(element).rootPropertyValueCache[hookRoot] = adjustedSetData[1];
eleData.rootPropertyValueCache[hookRoot] = adjustedSetData[1];
}
}
@ -239,7 +242,7 @@ function tick(timestamp) {
CSS.flushTransformCache(element);
}
} // END: for (var j = 0, callLength = call.length; j < callLength; j++)
} // END: for (var j = 0, jj = call.length; j < jj; j++)
/* The non-'none' display value is only applied to an element once -- when its associated call is first ticked through.
@ -276,3 +279,5 @@ function tick(timestamp) {
}
};
const flexValues = [ '-webkit-box', '-moz-box', '-ms-flexbox', '-webkit-flex' ];

View File

@ -4,7 +4,7 @@
</head>
<body>
<div style="position: absolute; top: 20px; left: 300px;">
<div style="position: absolute; top: 300px; left: 0px;">
<div class="red square" style="position:absolute; width:100px; height:100px; background:red; top: 0; left: 0;"></div>
<div class="green square" style="position:absolute; width:100px; height:100px; background:green; top: 0; left: 100px;"></div>

View File

@ -1,7 +1,10 @@
import {Component, Decorator, View as NgView, NgElement, bootstrap} from 'angular2/angular2';
import {Animation} from 'ionic/ionic';
let opacity = 0.2;
let rotateZ = '180deg';
let translateX = '100px';
let scale = 0.6;
@Component({ selector: '[ion-app]' })
@NgView({
@ -10,21 +13,22 @@ import {Animation} from 'ionic/ionic';
class IonicApp {
constructor() {
}
fadeOut() {
console.debug('fadeOut');
this.animation = new Animation();
this.animation.elements( document.querySelectorAll('.square') );
this.animation.duration(2000);
this.animation.easing('swing');
this.animation.duration(1000);
this.animation.easing('linear');
this.animation.property('opacity', opacity);
this.animation.property('translateX', translateX);
this.animation.property('translateY', translateX);
this.animation.property('rotateZ', rotateZ);
this.animation.property('scale', scale);
this.animation.property('opacity', 0.2);
this.animation.property('translateX', '100px');
this.animation.property('translateY', '100px');
this.animation.property('rotateZ', '180deg');
this.animation.property('scale', '0.5');
let q = this.animation.start();
@ -34,17 +38,18 @@ class IonicApp {
}
fadeIn() {
console.debug('fadeIn');
this.animation = new Animation();
this.animation.elements( document.querySelectorAll('.square') );
this.animation.duration(2000);
this.animation.easing('swing');
this.animation.duration(1000);
this.animation.easing('linear');
this.animation.property('opacity', 1);
this.animation.property('translateX', '0px');
this.animation.property('translateY', '0px');
this.animation.property('rotateZ', '0deg');
this.animation.property('scale', '1');
this.animation.property('translateX', 0);
this.animation.property('translateY', 0);
this.animation.property('rotateZ', 0);
this.animation.property('scale', 1);
let q = this.animation.start();
@ -58,26 +63,22 @@ class IonicApp {
}
percent(ev) {
let ratio = parseFloat(ev.srcElement.value) / 100;
console.log('percent ratio', ratio);
let percentComplete = parseFloat(ev.srcElement.value) / 100;
this.animation = new Animation();
this.animation.elements( document.querySelectorAll('.square') );
if (!this.percentAnimation) {
this.percentAnimation = new Animation();
this.percentAnimation.elements( document.querySelectorAll('.square') );
this.animation.duration(2000);
this.animation.easing('swing');
this.percentAnimation.property('opacity', opacity);
this.percentAnimation.property('translateX', translateX);
this.percentAnimation.property('translateY', translateX);
this.percentAnimation.property('rotateZ', rotateZ);
this.percentAnimation.property('scale', scale);
this.animation.property('opacity', 0.2);
this.animation.property('translateX', '100px');
this.animation.property('translateY', '100px');
this.animation.property('rotateZ', '180deg');
this.animation.property('scale', '0.5');
this.percentAnimation.ready();
}
this.animation.percent(ratio);
}
stop() {
this.animation.stop();
this.percentAnimation.percent(percentComplete);
}
velocityStart() {

View File

@ -50,7 +50,7 @@ export class Tab extends NavControllerBase {
this.config = Tab.config.invoke(this);
this.setHidden = setHidden
this.tabId = util.uid()
this.tabId = util.nextUid()
setId('tab-content-' + this.tabId)
setLabelby('tab-item-' + this.tabId)
setRole('tabpanel')

View File

@ -4,16 +4,33 @@ export function clamp(min, n, max) {
return Math.max(min, Math.min(n, max));
}
export function extend(dest) {
for (var i = 1, ii = arguments.length; i < ii; i++) {
var source = arguments[i] || {};
for (var key in source) {
if (source.hasOwnProperty(key)) {
dest[key] = source[key];
export function extend(dst) {
return baseExtend(dst, [].slice.call(arguments, 1), false);
}
export function merge(dst) {
return baseExtend(dst, [].slice.call(arguments, 1), true);
}
function baseExtend(dst, objs, deep) {
for (var i = 0, ii = objs.length; i < ii; ++i) {
var obj = objs[i];
if (!isObject(obj) && !isFunction(obj)) continue;
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
var src = obj[key];
if (deep && isObject(src)) {
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
baseExtend(dst[key], [src], true);
} else {
dst[key] = src;
}
}
}
return dest;
return dst;
}
export function defaults(dest) {
@ -41,9 +58,9 @@ export function pascalCaseToDashCase(str = '') {
})
}
let _uid = 0
export function uid() {
return _uid++
let uid = 0
export function nextUid() {
return ++uid;
}
export class Log {