From d1c78bc5bf4e30b810ebd5ba9ec3ac23b7ffa72d Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Mon, 27 Apr 2015 22:34:28 -0500 Subject: [PATCH] collide updates --- ionic/collide/animation-start.js | 64 ------- ionic/collide/animation.js | 143 ++++++++++++-- ionic/collide/complete-call.js | 4 +- ...nimation-process.js => process-element.js} | 174 ++++-------------- ionic/collide/tick.js | 41 +++-- .../components/app/test/animations/main.html | 2 +- ionic/components/app/test/animations/main.js | 63 +++---- ionic/components/tabs/tab.js | 2 +- ionic/util/util.js | 37 +++- 9 files changed, 248 insertions(+), 282 deletions(-) delete mode 100644 ionic/collide/animation-start.js rename ionic/collide/{animation-process.js => process-element.js} (80%) diff --git a/ionic/collide/animation-start.js b/ionic/collide/animation-start.js deleted file mode 100644 index 341dfecd85..0000000000 --- a/ionic/collide/animation-start.js +++ /dev/null @@ -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); -}; diff --git a/ionic/collide/animation.js b/ionic/collide/animation.js index cc7612b5d1..306c3c0790 100644 --- a/ionic/collide/animation.js +++ b/ionic/collide/animation.js @@ -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 || {}; } diff --git a/ionic/collide/complete-call.js b/ionic/collide/complete-call.js index d4c5f0b732..f46c53827c 100644 --- a/ionic/collide/complete-call.js +++ b/ionic/collide/complete-call.js @@ -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; diff --git a/ionic/collide/animation-process.js b/ionic/collide/process-element.js similarity index 80% rename from ionic/collide/animation-process.js rename to ionic/collide/process-element.js index 0c1a645df1..9507fae692 100644 --- a/ionic/collide/animation-process.js +++ b/ionic/collide/process-element.js @@ -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; } diff --git a/ionic/collide/tick.js b/ionic/collide/tick.js index 7ec82e7c4d..c575fd48af 100644 --- a/ionic/collide/tick.js +++ b/ionic/collide/tick.js @@ -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' ]; diff --git a/ionic/components/app/test/animations/main.html b/ionic/components/app/test/animations/main.html index 7a59168a2c..ba87176913 100644 --- a/ionic/components/app/test/animations/main.html +++ b/ionic/components/app/test/animations/main.html @@ -4,7 +4,7 @@ -
+
diff --git a/ionic/components/app/test/animations/main.js b/ionic/components/app/test/animations/main.js index 344aff9499..120965c405 100644 --- a/ionic/components/app/test/animations/main.js +++ b/ionic/components/app/test/animations/main.js @@ -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() { diff --git a/ionic/components/tabs/tab.js b/ionic/components/tabs/tab.js index ece2dd899b..98859c4043 100644 --- a/ionic/components/tabs/tab.js +++ b/ionic/components/tabs/tab.js @@ -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') diff --git a/ionic/util/util.js b/ionic/util/util.js index 428c6adf85..4e923c89dc 100644 --- a/ionic/util/util.js +++ b/ionic/util/util.js @@ -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 {