From b0e48d1709ac1e5aab371893c7a699f76430feb6 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Tue, 19 May 2015 16:24:11 -0500 Subject: [PATCH] let there be web animations --- gulpfile.js | 10 +- ionic/animations/animation.js | 268 +++++ ionic/animations/web-animations.min.js | 17 + ionic/animations/web-animations.min.js.map | 1 + ionic/collide/animation-stop.js | 135 --- ionic/collide/animation.js | 340 ------- ionic/collide/calculate-unit-ratios.js | 127 --- ionic/collide/collide.js | 173 ---- ionic/collide/complete-call.js | 186 ---- ionic/collide/css.js | 915 ------------------ ionic/collide/easing.js | 316 ------ ionic/collide/process-element.js | 655 ------------- ionic/collide/tick.js | 301 ------ ionic/components/app/test/animations/index.js | 141 +-- .../components/app/test/animations/main.html | 16 +- ionic/components/nav/nav-base.js | 2 +- ionic/ionic.js | 2 +- ionic/transitions/ios-transition.js | 54 +- ionic/transitions/none-transition.js | 16 +- scripts/e2e/angular.template.html | 4 + 20 files changed, 370 insertions(+), 3309 deletions(-) create mode 100644 ionic/animations/animation.js create mode 100644 ionic/animations/web-animations.min.js create mode 100644 ionic/animations/web-animations.min.js.map delete mode 100644 ionic/collide/animation-stop.js delete mode 100644 ionic/collide/animation.js delete mode 100644 ionic/collide/calculate-unit-ratios.js delete mode 100644 ionic/collide/collide.js delete mode 100644 ionic/collide/complete-call.js delete mode 100644 ionic/collide/css.js delete mode 100644 ionic/collide/easing.js delete mode 100644 ionic/collide/process-element.js delete mode 100644 ionic/collide/tick.js diff --git a/gulpfile.js b/gulpfile.js index 19bc2daa01..82612d437a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,7 +25,8 @@ gulp.task('build', function() { 'ionic.copy.js', 'ionic.examples', 'sass', - 'fonts'); + 'fonts', + 'polyfills'); }) gulp.task('watch', function() { @@ -36,6 +37,7 @@ gulp.task('watch', function() { 'ionic.examples', 'sass', 'fonts', + 'polyfills', function() { watch('ionic/**/*.js', function() { @@ -111,6 +113,12 @@ gulp.task('fonts', function() { }); +gulp.task('polyfills', function() { + return gulp.src('ionic/animations/web-animations*') + .pipe(gulp.dest('../angular-ionic/dist/js/dev/es5/polyfills')); +}); + + gulp.task('update.angular', function(done) { if (!fs.existsSync('../angular-ionic')) { diff --git a/ionic/animations/animation.js b/ionic/animations/animation.js new file mode 100644 index 0000000000..b6b76dbec3 --- /dev/null +++ b/ionic/animations/animation.js @@ -0,0 +1,268 @@ +import * as util from 'ionic/util/util'; + + +export class Animation { + + constructor(el) { + this._el = []; + this._parent = null; + this._children = []; + this._players = []; + + this._from = null; + this._to = null; + this._duration = null; + this._easing = null; + + this._beforeAddCls = []; + this._beforeRmvCls = []; + this._afterAddCls = []; + this._afterRmvCls = []; + + this.elements(el); + } + + elements(el) { + if (el) { + if (typeof el === 'string') { + el = document.querySelectorAll(ele); + } + + if (el.length) { + for (let i = 0; i < el.length; i++) { + this._el.push(el[i]); + } + + } else if (el.nodeType) { + this._el.push(el); + } + } + return this; + } + + parent(parentAnimation) { + this._parent = parentAnimation; + return this; + } + + addChild(childAnimation) { + if (childAnimation) { + childAnimation.parent(this); + this._children.push(childAnimation); + } + return this; + } + + children(arr) { + arr = Array.isArray(arr) ? arr : arguments; + for (let i = 0; i < arr.length; i++) { + this.addChild(arr[i]); + } + return this; + } + + duration(value) { + if (arguments.length) { + this._duration = value; + return this; + } + return this._duration || (this._parent && this._parent.duration()); + } + + easing(value) { + if (arguments.length) { + this._easing = value; + return this; + } + return this._easing || (this._parent && this._parent.easing()); + } + + from(property, value) { + if (!this._from) { + this._from = {} + } + this._from[property] = value; + return this; + } + + to(property, value) { + if (!this._to) { + this._to = {} + } + this._to[property] = value; + return this; + } + + get beforePlay() { + return { + addClass: (className) => { + this._beforeAddCls.push(className); + return this; + }, + removeClass: (className) => { + this._beforeRmvCls.push(className); + return this; + } + } + } + + get afterFinish() { + return { + addClass: (className) => { + this._afterAddCls.push(className); + return this; + }, + removeClass: (className) => { + this._afterRmvCls.push(className); + return this; + } + } + } + + play() { + let promises = []; + + for (let i = 0; i < this._children.length; i++) { + promises.push( this._children[i].play() ); + } + + if (!this._to) { + // probably just add/removing classes + // create bogus transition + this._from = this._to = {'opacity': 1}; + } + + if (!this._players.length) { + for (let i = 0; i < this._el.length; i++) { + var ele = this._el[i]; + + for (let j = 0; j < this._beforeAddCls.length; j++) { + ele.classList.add(this._beforeAddCls[j]); + } + + for (let j = 0; j < this._beforeRmvCls.length; j++) { + ele.classList.remove(this._beforeRmvCls[j]); + } + + var player = new Animate(ele, this._from, this._to, this.duration(), this.easing()); + this._players.push(player); + + promises.push(player.promise); + } + + } else { + for (let i = 0; i < this._players.length; i++) { + this._players[i].play(); + } + } + + var promise = Promise.all(promises); + + promise.then(() => { + for (let i = 0; i < this._el.length; i++) { + var ele = this._el[i]; + + for (let j = 0; j < this._afterAddCls.length; j++) { + ele.classList.add(this._afterAddCls[j]); + } + + for (let j = 0; j < this._afterRmvCls.length; j++) { + ele.classList.remove(this._afterRmvCls[j]); + } + } + }); + + return promise; + } + + pause() { + for (let i = 0; i < this._children.length; i++) { + this._children[i].pause(); + } + + for (let i = 0; i < this._players.length; i++) { + this._players[i].pause(); + } + } + + progress(value) { + for (let i = 0; i < this._children.length; i++) { + this._children[i].progress(value); + } + + if (!this._players.length) { + this.play(); + this.pause(); + } + + for (let i = 0; i < this._players.length; i++) { + this._players[i].progress(value); + } + } + +} + +class Animate { + + constructor(ele, fromEffect, toEffect, duration, easing) { + // https://w3c.github.io/web-animations/ + // not using the direct API methods because they're still in flux + // however, element.animate() seems locked in and uses the latest + // and correct API methods under the hood, so really doesn't matter + + // fromEffect must be manually computed if it wasn't provided + // https://github.com/web-animations/web-animations-js/issues/14 + fromEffect = fromEffect || {}; + let style = null; + for (let prop in toEffect) { + if (!fromEffect[prop]) { + style = style || window.getComputedStyle(ele); + fromEffect[prop] = style[prop]; + } + } + + this._duration = duration; + this._easing = easing; + + this.player = ele.animate([fromEffect, toEffect], { + duration: duration, + easing: easing, + fill: 'both' + }); + + this.promise = new Promise(resolve => { + this.player.onfinish = () => { + resolve(); + }; + }); + + } + + play() { + this.player.play(); + } + + pause() { + this.player.pause(); + } + + progress(value) { + let player = this.player; + + // passed a number between 0 and 1 + value = Math.max(0, Math.min(1, parseFloat(value))); + + if (value === 1) { + player.currentTime = (this._duration * 0.9999); + player.play(); + return; + } + + if (player.playState !== 'paused') { + player.pause(); + } + + player.currentTime = (this._duration * value); + } + +} diff --git a/ionic/animations/web-animations.min.js b/ionic/animations/web-animations.min.js new file mode 100644 index 0000000000..bf20829071 --- /dev/null +++ b/ionic/animations/web-animations.min.js @@ -0,0 +1,17 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +!function(a,b){b["true"]=a,function(){if(document.documentElement.animate){var a=document.documentElement.animate([],0),b=!0;if(a&&(b=!1,"play|currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split("|").forEach(function(c){void 0===a[c]&&(b=!0)})),!b)return}var c={},d={},e={},f=null;!function(a){function b(b,c){var d={delay:0,endDelay:0,fill:c?"both":"none",iterationStart:0,iterations:1,duration:c?"auto":0,playbackRate:1,direction:"normal",easing:"linear"};return"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNames(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof d[c]||"duration"==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==p.indexOf(b[c]))return;if("direction"==c&&-1==q.indexOf(b[c]))return;if("playbackRate"==c&&1!==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use Animation.playbackRate instead."))return;d[c]=b[c]}}):d.duration=b,d}function c(a,c){var d=b(a,c);return d.easing=f(d.easing),d}function d(a,b,c,d){return 0>a||a>1||0>c||c>1?y:function(e){function f(a,b,c){return 3*a*(1-c)*(1-c)*c+3*b*(1-c)*c*c+c*c*c}for(var g=0,h=1;;){var i=(g+h)/2,j=f(a,c,i);if(Math.abs(e-j)<.001)return f(b,d,i);e>j?g=i:h=i}}}function e(a,b){return function(c){if(c>=1)return 1;var d=1/a;return c+=b*d,c-c%d}}function f(a){var b=w.exec(a);if(b)return d.apply(this,b.slice(1).map(Number));var c=x.exec(a);if(c)return e(Number(c[1]),{start:r,middle:s,end:t}[c[2]]);var f=u[a];return f?f:y}function g(a){return Math.abs(h(a)/a.playbackRate)}function h(a){return a.duration*a.iterations}function i(a,b,c){return null==b?z:b=c.delay+a?B:C}function j(a,b,c,d,e){switch(d){case A:return"backwards"==b||"both"==b?0:null;case C:return c-e;case B:return"forwards"==b||"both"==b?a:null;case z:return null}}function k(a,b,c,d){return(d.playbackRate<0?b-a:b)*d.playbackRate+c}function l(a,b,c,d,e){return 1/0===c||c===-1/0||c-d==b&&e.iterations&&(e.iterations+e.iterationStart)%1==0?a:c%a}function m(a,b,c,d){return 0===c?0:b==a?d.iterationStart+d.iterations-1:Math.floor(c/a)}function n(a,b,c,d){var e=a%2>=1,f="normal"==d.direction||d.direction==(e?"alternate-reverse":"alternate"),g=f?c:b-c,h=g/b;return b*d.easing(h)}function o(a,b,c){var d=i(a,b,c),e=j(a,c.fill,b,d,c.delay);if(null===e)return null;if(0===a)return d===A?0:1;var f=c.iterationStart*c.duration,g=k(a,e,f,c),o=l(c.duration,h(c),g,f,c),p=m(c.duration,o,g,c);return n(p,c.duration,o,c)/c.duration}var p="backwards|forwards|both|none".split("|"),q="reverse|alternate|alternate-reverse".split("|"),r=1,s=.5,t=0,u={ease:d(.25,.1,.25,1),"ease-in":d(.42,0,1,1),"ease-out":d(0,0,.58,1),"ease-in-out":d(.42,0,.58,1),"step-start":e(1,r),"step-middle":e(1,s),"step-end":e(1,t)},v="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",w=new RegExp("cubic-bezier\\("+v+","+v+","+v+","+v+"\\)"),x=/steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/,y=function(a){return a},z=0,A=1,B=2,C=3;a.makeTiming=b,a.normalizeTimingInput=c,a.calculateActiveDuration=g,a.calculateTimeFraction=o,a.calculatePhase=i,a.toTimingFunction=f}(c,f),function(a){function b(a,b){return a in h?h[a][b]||b:b}function c(a,c,d){var g=e[a];if(g){f.style[a]=c;for(var h in g){var i=g[h],j=f.style[i];d[i]=b(i,j)}}else d[a]=b(a,c)}function d(b){function d(){var a=e.length;null==e[a-1].offset&&(e[a-1].offset=1),a>1&&null==e[0].offset&&(e[0].offset=0);for(var b=0,c=e[0].offset,d=1;a>d;d++){var f=e[d].offset;if(null!=f){for(var g=1;d-b>g;g++)e[b+g].offset=c+(f-c)*g/(d-b);b=d,c=f}}}if(!Array.isArray(b)&&null!==b)throw new TypeError("Keyframes must be null or an array of keyframes");if(null==b)return[];for(var e=b.map(function(b){var d={};for(var e in b){var f=b[e];if("offset"==e){if(null!=f&&(f=Number(f),!isFinite(f)))throw new TypeError("keyframe offsets must be numbers.")}else{if("composite"==e)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"add compositing is not supported"};f="easing"==e?a.toTimingFunction(f):""+f}c(e,f,d)}return void 0==d.offset&&(d.offset=null),void 0==d.easing&&(d.easing=a.toTimingFunction("linear")),d}),f=!0,g=-1/0,h=0;hi)throw{code:DOMException.INVALID_MODIFICATION_ERR,name:"InvalidModificationError",message:"Keyframes are not loosely sorted by offset. Sort or specify offsets."};g=i}else f=!1}return e=e.filter(function(a){return a.offset>=0&&a.offset<=1}),f||d(),e}var e={background:["backgroundImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAttachment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderTopColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle","borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth","borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBottomWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor","borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLeftWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius","borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],borderRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["borderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flexShrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fontWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","marginLeft"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingTop","paddingRight","paddingBottom","paddingLeft"]},f=document.createElementNS("http://www.w3.org/1999/xhtml","div"),g={thin:"1px",medium:"3px",thick:"5px"},h={borderBottomWidth:g,borderLeftWidth:g,borderRightWidth:g,borderTopWidth:g,fontSize:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-large":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth:g,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};a.normalizeKeyframes=d}(c,f),function(a){var b={};a.isDeprecated=function(a,c,d,e){var f=e?"are":"is",g=new Date,h=new Date(c);return h.setMonth(h.getMonth()+3),h>g?(a in b||console.warn("Web Animations: "+a+" "+f+" deprecated and will stop working on "+h.toDateString()+". "+d),b[a]=!0,!1):!0},a.deprecated=function(b,c,d,e){var f=e?"are":"is";if(a.isDeprecated(b,c,d,e))throw new Error(b+" "+f+" no longer supported. "+d)}}(c),function(a,b){function c(a){for(var b={},c=0;c=c&&0==a.startTime||c>=1&&1==a.endTime||c>=a.startTime&&c<=a.endTime}).forEach(function(d){var e=c-d.startTime,f=d.endTime-d.startTime,g=0==f?0:d.easing(e/f);b.apply(a,d.property,d.interpolation(g))});else for(var d in g)"offset"!=d&&"easing"!=d&&"composite"!=d&&b.clear(a,d)}}}(c,d,f),function(a){function b(a,b,c){e[c]=e[c]||[],e[c].push([a,b])}function c(a,c,d){for(var e=0;ethis._surrogateStyle.length;)this._length--,Object.defineProperty(this,this._length,{configurable:!0,enumerable:!1,value:void 0})},_set:function(a,b){this._style[a]=b,this._isAnimatedProperty[a]=!0},_clear:function(a){this._style[a]=this._surrogateStyle[a],delete this._isAnimatedProperty[a]}};for(var h in f)c.prototype[h]=function(a,b){return function(){var c=this._surrogateStyle[a].apply(this._surrogateStyle,arguments);return b&&(this._isAnimatedProperty[arguments[0]]||this._style[a].apply(this._style,arguments),this._updateIndices()),c}}(h,h in g);for(var i in document.documentElement.style)i in e||i in f||!function(a){b(c.prototype,a,{get:function(){return this._surrogateStyle[a]},set:function(b){this._surrogateStyle[a]=b,this._updateIndices(),this._isAnimatedProperty[a]||(this._style[a]=b)}})}(i);a.apply=function(b,c,e){d(b),b.style._set(a.propertyName(c),e)},a.clear=function(b,c){b._webAnimationsPatchedStyle&&b.style._clear(a.propertyName(c))}}(d,f),function(a){window.Element.prototype.animate=function(b,c){return a.timeline._play(a.KeyframeEffect(this,b,c))}}(d),function(a){function b(a,c,d){if("number"==typeof a&&"number"==typeof c)return a*(1-d)+c*d;if("boolean"==typeof a&&"boolean"==typeof c)return.5>d?a:c;if(a.length==c.length){for(var e=[],f=0;fj;j++)g.push(c[j]*(Math.cos(e*h)-f*i)+d[j]*i);return g}var d=function(){function a(a,b){for(var c=[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]],d=0;4>d;d++)for(var e=0;4>e;e++)for(var f=0;4>f;f++)c[d][e]+=b[d][f]*a[f][e];return c}function b(a){return 0==a[0][2]&&0==a[0][3]&&0==a[1][2]&&0==a[1][3]&&0==a[2][0]&&0==a[2][1]&&1==a[2][2]&&0==a[2][3]&&0==a[3][2]&&1==a[3][3]}function c(c,d,e,f,g){for(var h=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],i=0;4>i;i++)h[i][3]=g[i];for(var i=0;3>i;i++)for(var j=0;3>j;j++)h[3][i]+=c[j]*h[j][i];var k=f[0],l=f[1],m=f[2],n=f[3],o=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]];o[0][0]=1-2*(l*l+m*m),o[0][1]=2*(k*l-m*n),o[0][2]=2*(k*m+l*n),o[1][0]=2*(k*l+m*n),o[1][1]=1-2*(k*k+m*m),o[1][2]=2*(l*m-k*n),o[2][0]=2*(k*m-l*n),o[2][1]=2*(l*m+k*n),o[2][2]=1-2*(k*k+l*l),h=a(h,o);var p=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]];e[2]&&(p[2][1]=e[2],h=a(h,p)),e[1]&&(p[2][1]=0,p[2][0]=e[0],h=a(h,p)),e[0]&&(p[2][0]=0,p[1][0]=e[0],h=a(h,p));for(var i=0;3>i;i++)for(var j=0;3>j;j++)h[i][j]*=d[i];return b(h)?[h[0][0],h[0][1],h[1][0],h[1][1],h[3][0],h[3][1]]:h[0].concat(h[1],h[2],h[3])}return c}();a.composeMatrix=d,a.quat=c}(d,f),function(a){var b=0,c=function(a,b,c){this.target=a,this.currentTime=b,this.timelineTime=c,this.type="finish",this.bubbles=!1,this.cancelable=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()};a.Animation=function(a){this._sequenceNumber=b++,this._currentTime=0,this._startTime=null,this.paused=!1,this._playbackRate=1,this._inTimeline=!0,this._finishedFlag=!1,this.onfinish=null,this._finishHandlers=[],this._effect=a,this._inEffect=this._effect._update(0),this._idle=!0,this._currentTimePending=!1},a.Animation.prototype={_ensureAlive:function(){this._inEffect=this._effect._update(this.playbackRate<0&&0===this.currentTime?-1:this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(this._inTimeline=!0,a.timeline._animations.push(this))},_tickCurrentTime:function(a,b){a!=this._currentTime&&(this._currentTime=a,this.finished&&!b&&(this._currentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get currentTime(){return this._idle||this._currentTimePending?null:this._currentTime},set currentTime(b){b=+b,isNaN(b)||(a.restart(),this.paused||null==this._startTime||(this._startTime=this._timeline.currentTime-b/this._playbackRate),this._currentTimePending=!1,this._currentTime!=b&&(this._tickCurrentTime(b,!0),a.invalidateEffects()))},get startTime(){return this._startTime},set startTime(b){b=+b,isNaN(b)||this.paused||this._idle||(this._startTime=b,this._tickCurrentTime((this._timeline.currentTime-this._startTime)*this.playbackRate),a.invalidateEffects())},get playbackRate(){return this._playbackRate},set playbackRate(a){if(a!=this._playbackRate){var b=this.currentTime;this._playbackRate=a,this._startTime=null,"paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!=b&&(this.currentTime=b)}},get finished(){return!this._idle&&(this._playbackRate>0&&this._currentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},get _totalDuration(){return this._effect._totalDuration},get playState(){return this._idle?"idle":null==this._startTime&&!this.paused&&0!=this.playbackRate||this._currentTimePending?"pending":this.paused?"paused":this.finished?"finished":"running"},play:function(){this.paused=!1,(this.finished||this._idle)&&(this._currentTime=this._playbackRate>0?0:this._totalDuration,this._startTime=null,a.invalidateEffects()),this._finishedFlag=!1,a.restart(),this._idle=!1,this._ensureAlive()},pause:function(){this.finished||this.paused||this._idle||(this._currentTimePending=!0),this._startTime=null,this.paused=!0},finish:function(){this._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._startTime=this._totalDuration-this.currentTime,this._currentTimePending=!1)},cancel:function(){this._inEffect=!1,this._idle=!0,this.currentTime=0,this._startTime=null},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:function(a,b){"function"==typeof b&&"finish"==a&&this._finishHandlers.push(b)},removeEventListener:function(a,b){if("finish"==a){var c=this._finishHandlers.indexOf(b);c>=0&&this._finishHandlers.splice(c,1)}},_fireEvents:function(a){var b=this.finished;if((b||this._idle)&&!this._finishedFlag){var d=new c(this,this._currentTime,a),e=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTimeout(function(){e.forEach(function(a){a.call(d.target,d)})},0)}this._finishedFlag=b},_tick:function(a){return this._idle||this.paused||(null==this._startTime?this.startTime=a-this._currentTime/this.playbackRate:this.finished||this._tickCurrentTime((a-this._startTime)*this.playbackRate)),this._currentTimePending=!1,this._fireEvents(a),!this._idle&&(this._inEffect||!this._finishedFlag)}}}(d,f),function(a,b){function c(a){var b=i;i=[],g(a),b.forEach(function(b){b[1](a)}),m&&g(a),f()}function d(a,b){return a._sequenceNumber-b._sequenceNumber}function e(){this._animations=[],this.currentTime=window.performance&&performance.now?performance.now():0}function f(){n.forEach(function(a){a()}),n.length=0}function g(a){l=!1;var c=b.timeline;c.currentTime=a,c._animations.sort(d),k=!1;var e=c._animations;c._animations=[];var f=[],g=[];e=e.filter(function(b){return b._inTimeline=b._tick(a),b._inEffect?g.push(b._effect):f.push(b._effect),b.finished||b.paused||b._idle||(k=!0),b._inTimeline}),n.push.apply(n,f),n.push.apply(n,g),c._animations.push.apply(c._animations,e),m=!1,k&&requestAnimationFrame(function(){})}var h=window.requestAnimationFrame,i=[],j=0;window.requestAnimationFrame=function(a){var b=j++;return 0==i.length&&h(c),i.push([b,a]),b},window.cancelAnimationFrame=function(a){i.forEach(function(b){b[0]==a&&(b[1]=function(){})})},e.prototype={_play:function(c){c._timing=a.normalizeTimingInput(c.timing);var d=new b.Animation(c);return d._idle=!1,d._timeline=this,this._animations.push(d),b.restart(),b.invalidateEffects(),d}};var k=!1,l=!1;b.restart=function(){return k||(k=!0,requestAnimationFrame(function(){}),l=!0),l};var m=!1;b.invalidateEffects=function(){m=!0};var n=[],o=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){return m&&g(p.currentTime),f(),o.apply(this,arguments)}});var p=new e;b.timeline=p}(c,d,f),function(a){function b(a,b){for(var c=0,d=0;do;o++){for(var p=0,q=0;3>q;q++)p+=b[3][q]*m[q][o];n.push(p)}return n.push(1),m.push(n),m}function d(a){return[[a[0][0],a[1][0],a[2][0],a[3][0]],[a[0][1],a[1][1],a[2][1],a[3][1]],[a[0][2],a[1][2],a[2][2],a[3][2]],[a[0][3],a[1][3],a[2][3],a[3][3]]]}function e(a,b){for(var c=[],d=0;4>d;d++){for(var e=0,f=0;4>f;f++)e+=a[f]*b[f][d];c.push(e)}return c}function f(a){var b=g(a);return[a[0]/b,a[1]/b,a[2]/b]}function g(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2])}function h(a,b,c,d){return[c*a[0]+d*b[0],c*a[1]+d*b[1],c*a[2]+d*b[2]]}function i(a,b){return[a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]]}function j(j){var k=[j.slice(0,4),j.slice(4,8),j.slice(8,12),j.slice(12,16)];if(1!==k[3][3])return null;for(var l=[],m=0;4>m;m++)l.push(k[m].slice());for(var m=0;3>m;m++)l[m][3]=0;if(0===a(l))return!1;var n,o=[];if(k[0][3]||k[1][3]||k[2][3]){o.push(k[0][3]),o.push(k[1][3]),o.push(k[2][3]),o.push(k[3][3]);var p=c(l),q=d(p);n=e(o,q)}else n=[0,0,0,1];var r=k[3].slice(0,3),s=[];s.push(k[0].slice(0,3));var t=[];t.push(g(s[0])),s[0]=f(s[0]);var u=[];s.push(k[1].slice(0,3)),u.push(b(s[0],s[1])),s[1]=h(s[1],s[0],1,-u[0]),t.push(g(s[1])),s[1]=f(s[1]),u[0]/=t[1],s.push(k[2].slice(0,3)),u.push(b(s[0],s[2])),s[2]=h(s[2],s[0],1,-u[1]),u.push(b(s[1],s[2])),s[2]=h(s[2],s[1],1,-u[2]),t.push(g(s[2])),s[2]=f(s[2]),u[1]/=t[2],u[2]/=t[2];var v=i(s[1],s[2]);if(b(s[0],v)<0)for(var m=0;3>m;m++)t[m]*=-1,s[m][0]*=-1,s[m][1]*=-1,s[m][2]*=-1;var w,x,y=s[0][0]+s[1][1]+s[2][2]+1;return y>1e-4?(w=.5/Math.sqrt(y),x=[(s[2][1]-s[1][2])*w,(s[0][2]-s[2][0])*w,(s[1][0]-s[0][1])*w,.25/w]):s[0][0]>s[1][1]&&s[0][0]>s[2][2]?(w=2*Math.sqrt(1+s[0][0]-s[1][1]-s[2][2]),x=[.25*w,(s[0][1]+s[1][0])/w,(s[0][2]+s[2][0])/w,(s[2][1]-s[1][2])/w]):s[1][1]>s[2][2]?(w=2*Math.sqrt(1+s[1][1]-s[0][0]-s[2][2]),x=[(s[0][1]+s[1][0])/w,.25*w,(s[1][2]+s[2][1])/w,(s[0][2]-s[2][0])/w]):(w=2*Math.sqrt(1+s[2][2]-s[0][0]-s[1][1]),x=[(s[0][2]+s[2][0])/w,(s[1][2]+s[2][1])/w,.25*w,(s[1][0]-s[0][1])/w]),[r,t,u,x,n]}return j}();a.dot=b,a.makeMatrixDecomposition=f}(d,f),function(a){function b(a,b){var c=a.exec(b);return c?(c=a.ignoreCase?c[0].toLowerCase():c[0],[c,b.substr(c.length)]):void 0}function c(a,b){b=b.replace(/^\s*/,"");var c=a(b);return c?[c[0],c[1].replace(/^\s*/,"")]:void 0}function d(a,d,e){a=c.bind(null,a);for(var f=[];;){var g=a(e);if(!g)return[f,e];if(f.push(g[0]),e=g[1],g=b(d,e),!g||""==g[1])return[f,e];e=g[1]}}function e(a,b){for(var c=0,d=0;d=c))break;var e=a(b.substr(0,d));return void 0==e?void 0:[e,b.substr(d)]}function f(a,b){for(var c=a,d=b;c&&d;)c>d?c%=d:d%=c;return c=a*b/(c+d)}function g(a){return function(b){var c=a(b);return c&&(c[0]=void 0),c}}function h(a,b){return function(c){var d=a(c);return d?d:[b,c]}}function i(b,c){for(var d=[],e=0;ek;k++){var l=b(d[k%d.length],e[k%e.length]);if(!l)return;g.push(l[0]),h.push(l[1]),i.push(l[2])}return[g,h,function(b){var d=b.map(function(a,b){return i[b](a)}).join(c);return a?a(d):d}]}function k(a,b,c){for(var d=[],e=[],f=[],g=0,h=0;h=c?a:c>=1?b:"visible"}]:void 0}a.addPropertiesHandler(String,b,["visibility"])}(d),function(a){function b(a){a=a.trim(),e.fillStyle="#000",e.fillStyle=a;var b=e.fillStyle;if(e.fillStyle="#fff",e.fillStyle=a,b==e.fillStyle){e.fillRect(0,0,1,1);var c=e.getImageData(0,0,1,1).data;e.clearRect(0,0,1,1);var d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function c(b,c){return[b,c,function(b){function c(a){return Math.max(0,Math.min(255,a))}if(b[3])for(var d=0;3>d;d++)b[d]=Math.round(c(b[d]/b[3]));return b[3]=a.numberToString(a.clamp(0,1,b[3])),"rgba("+b.join(",")+")"}]}var d=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");d.width=d.height=1;var e=d.getContext("2d");a.addPropertiesHandler(b,c,["background-color","border-bottom-color","border-left-color","border-right-color","border-top-color","color","outline-color","text-decoration-color"]),a.consumeColor=a.consumeParenthesised.bind(null,b),a.mergeColors=c}(d,f),function(a,b){function c(a,b){if(b=b.trim().toLowerCase(),"0"==b&&"px".search(a)>=0)return{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var c={};b=b.replace(a,function(a){return c[a]=null,"U"+a});for(var d="U("+a.source+")",e=b.replace(/[-+]?(\d*\.)?\d+/g,"N").replace(new RegExp("N"+d,"g"),"D").replace(/\s[+-]\s/g,"O").replace(/\s/g,""),f=[/N\*(D)/g,/(N|D)[*/]N/g,/(N|D)O\1/g,/\((N|D)\)/g],g=0;g1?"calc("+c+")":c}]}var f="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",g=c.bind(null,new RegExp(f,"g")),h=c.bind(null,new RegExp(f+"|%","g")),i=c.bind(null,/deg|rad|grad|turn/g);a.parseLength=g,a.parseLengthOrPercent=h,a.consumeLengthOrPercent=a.consumeParenthesised.bind(null,h),a.parseAngle=i,a.mergeDimensions=e;var j=a.consumeParenthesised.bind(null,g),k=a.consumeRepeated.bind(void 0,j,/^/),l=a.consumeRepeated.bind(void 0,k,/^,/);a.consumeSizePairList=l;var m=function(a){var b=l(a);return b&&""==b[1]?b[0]:void 0},n=a.mergeNestedRepeated.bind(void 0,d," "),o=a.mergeNestedRepeated.bind(void 0,n,",");a.mergeNonNegativeSizePair=n,a.addPropertiesHandler(m,o,["background-size"]),a.addPropertiesHandler(h,d,["border-bottom-width","border-image-width","border-left-width","border-right-width","border-top-width","flex-basis","font-size","height","line-height","max-height","max-width","outline-width","width"]),a.addPropertiesHandler(h,e,["border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right","margin-top","min-height","min-width","outline-offset","padding-bottom","padding-left","padding-right","padding-top","perspective","right","shape-margin","text-indent","top","vertical-align","word-spacing"])}(d,f),function(a){function b(b){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function c(c){var d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consumeToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,b,/^,/),a.ignore(a.consumeToken.bind(null,/^\)/))],c);return d&&4==d[0].length?d[0]:void 0}function d(b,c){return"auto"==b||"auto"==c?[!0,!1,function(d){var e=d?b:c;if("auto"==e)return"auto";var f=a.mergeDimensions(e,e);return f[2](f[0])}]:a.mergeDimensions(b,c)}function e(a){return"rect("+a+")"}var f=a.mergeWrappedNestedRepeated.bind(null,e,d,", ");a.parseBox=c,a.mergeBoxes=f,a.addPropertiesHandler(c,f,["clip"]) +}(d,f),function(a){function b(a){return function(b){var c=0;return a.map(function(a){return a===j?b[c++]:a})}}function c(a){return a}function d(b){if(b=b.toLowerCase().trim(),"none"==b)return[];for(var c,d=/\s*(\w+)\(([^)]*)\)/g,e=[],f=0;c=d.exec(b);){if(c.index!=f)return;f=c.index+c[0].length;var g=c[1],h=m[g];if(!h)return;var i=c[2].split(","),j=h[0];if(j.lengthb||b>900||b%100!==0?void 0:b}function c(b){return b=100*Math.round(b/100),b=a.clamp(100,900,b),400===b?"normal":700===b?"bold":String(b)}function d(a,b){return[a,b,c]}a.addPropertiesHandler(b,d,["font-weight"])}(d),function(a){function b(a){var b={};for(var c in a)b[c]=-a[c];return b}function c(b){return a.consumeToken(/^(left|center|right|top|bottom)\b/i,b)||a.consumeLengthOrPercent(b)}function d(b,d){var e=a.consumeRepeated(c,/^/,d);if(e&&""==e[1]){var f=e[0];if(f[0]=f[0]||"center",f[1]=f[1]||"center",3==b&&(f[2]=f[2]||{px:0}),f.length==b){if(/top|bottom/.test(f[0])||/left|right/.test(f[1])){var h=f[0];f[0]=f[1],f[1]=h}if(/left|right|center|Object/.test(f[0])&&/top|bottom|center|Object/.test(f[1]))return f.map(function(a){return"object"==typeof a?a:g[a]})}}}function e(d){var e=a.consumeRepeated(c,/^/,d);if(e){for(var f=e[0],h=[{"%":50},{"%":50}],i=0,j=!1,k=0;k stop current default queue calls (and queue:false calls), including remaining queued ones. - - customQueue === undefined --> stop current queue:'' call and all queue:false calls. - - customQueue === false --> stop only queue:false calls. - - customQueue === 'custom' --> stop current queue:'custom' call, including remaining queued ones (there is no functionality to only clear the currently-running queue:'custom' call). */ - - var queueName = (customQueue === undefined) ? '' : customQueue; - if (queueName !== true && (activeCall[2].queue !== queueName) && !(customQueue === undefined && activeCall[2].queue === false)) { - return true; - } - - var activeElement = activeElements[j]; - - /* Iterate through the calls targeted by the stop command. */ - for (var k = 0; k < elementsLength; k++) { - var element = elements[k]; - - /* Check that this call was applied to the target element. */ - if (element === activeElement) { - - /* Optionally clear the remaining queued calls. */ - if (customQueue === true || typeof customQueue === 'string') { - - /* Iterate through the items in the element's queue. */ - - var eleQueueName = typeof customQueue === 'string' ? customQueue : ''; - var eleQueue = Collide.queue(element, eleQueueName); - for (var l = 0; l < eleQueue.length; l++) { - /* The queue array can contain an 'inprogress' string, which we skip. */ - if (typeof eleQueue[l] === 'function') { - /* Pass the item's callback a flag indicating that we want to abort from the queue call. - (Specifically, the queue will resolve the call's associated promise then abort.) */ - eleQueue[l](null, true); - } - } - - /* Clearing the queue() array is achieved by resetting it to []. */ - Collide.queue(element, eleQueueName, []); - } - - if (action === 'stop') { - /* Since 'reverse' uses cached start values (the previous call's endValues), these values must be - changed to reflect the final value that the elements were actually tweened to. */ - /* Note: If only queue:false animations are currently running on an element, it won't have a tweensContainer - object. Also, queue:false animations can't be reversed. */ - var eleData = Collide.data(element); - if (eleData && eleData.tweensContainer && queueName !== false) { - for (var tweenName in eleData.tweensContainer) { - eleData.tweensContainer[tweenName].endValue = eleData.tweensContainer[tweenName].currentValue; - } - } - - callsToStop.push(i); - - } else if (action === 'finish') { - /* To get active tweens to finish immediately, we forcefully shorten their durations to 1ms so that - they finish upon the next rAf tick then proceed with normal call completion logic. */ - activeCall[2].duration = 1; - } - - } - - } - - } - - } - - } // END: for (var i = 0, l = Collide.State.calls.length; x < l; x++) { - - /* Prematurely call completeCall() on each matched active call. Pass an additional flag for 'stop' to indicate - that the complete callback and display:none setting should be skipped since we're completing prematurely. */ - if (action === 'stop') { - for (var i = 0; i < callsToStop.length; i++) { - completeCall(i, true); - } - } - -}; diff --git a/ionic/collide/animation.js b/ionic/collide/animation.js deleted file mode 100644 index c7043bf0ed..0000000000 --- a/ionic/collide/animation.js +++ /dev/null @@ -1,340 +0,0 @@ -import * as util from 'ionic/util/util' -import {Collide} from './collide' -import {processElement} from './process-element' -import {animationStop} from './animation-stop' -import {startTick} from './tick' -import {dom} from 'ionic/util' - -const data = Collide.data; - - -export class Animation { - constructor(ele) { - this.parent = null; - this._options = {}; - this._properties = {}; - this._resolve = null; - this._call = null; - - this.children = []; - - this._addStartClasses = []; - this._removeStartClasses = []; - - this._addEndClasses = []; - this._removeEndClasses = []; - - this.elements(ele); - } - - addChild(childAnimation) { - childAnimation.parent = this; - this.children.push(childAnimation); - return this; - } - - setChildren(childAnimations) { - for (let i = 0; i < childAnimations.length; i++) { - this.addChild(childAnimations[i]); - } - return this; - } - - elements(ele) { - this._ele = []; - - if (ele) { - if (typeof ele === 'string') { - ele = document.querySelectorAll(ele); - } - - if (ele.length) { - this._ele = ele; - - } else if (ele.nodeType) { - this._ele.push(ele); - } - } - - return this; - } - - _prepare(clearCache, onNextFrame) { - - // ensure another animation wasnt about to start - if (this._nextAF) { - dom.rafCancel(this._nextAF); - } - - if (this.isAnimating()) { - this.stop(); - } - - if (this._ele.length) { - let promise = new Promise(res => { - this._resolve = res; - }); - - this._call = null; - - // in the next frame, do all the DOM GETs to load element info - this._nextAF = dom.raf(() => { - - /********************************** - Animation Call-Wide Variables - **********************************/ - - /* A container for CSS unit conversion ratios (e.g. %, rem, and em ==> px) that is used to cache ratios across all elements - being animated in a single Collide call. Calculating unit ratios necessitates DOM querying and updating, and is therefore - avoided (via caching) wherever possible. This container is call-wide instead of page-wide to avoid the risk of using stale - conversion metrics across Collide animations that are not immediately consecutively chained. */ - this._unitConversion = { - lastParent: null, - lastPosition: null, - lastFontSize: null, - lastPercentToPxWidth: null, - lastPercentToPxHeight: null, - lastEmToPx: null, - remToPx: null, - vwToPx: null, - vhToPx: null - }; - - this._call = []; - - var opts = util.extend({}, Collide.defaults); - - if (this.parent) { - opts = util.extend(opts, this.parent._options); - } - - this._options = util.extend(opts, this._options); - - // get the elements ready - for (let i = 0, ii = this._ele.length; i < ii; i++) { - processElement('start', this, i, clearCache); - } - - onNextFrame(); - }); - - return promise; - } - - return Promise.resolve(); - } - - _queue() { - if (this._ele.length) { - - if (this._call === null) { - return; - } - - var eleData; - for (var i = 0, ii = this._ele.length, element; i < ii && (element = this._ele[i]); i++) { - if (element) { - eleData = data(element); - if (eleData) { - eleData.isAnimating = true; - } - - /********************* - Auto-Dequeuing - *********************/ - - /* To fire the first non-custom-queue entry on an element, the element - must be dequeued if its queue stack consists *solely* of the current call. (This can be determined by checking - for the 'inprogress' item that is prepended to active queue stack arrays.) Regardless, whenever the element's - queue is further appended with additional items -- including delay()'s calls, the queue's - first entry is automatically fired. This behavior contrasts that of custom queues, which never auto-fire. */ - /* Note: When an element set is being subjected to a non-parallel Collide call, the animation will not begin until - each one of the elements in the set has reached the end of its individually pre-existing queue chain. */ - if (this._options.queue === '' && Collide.queue(element)[0] !== 'inprogress') { - Collide.dequeue(element); - } - } - } - - /* Push the call array onto Collide.State.calls for the animation tick to immediately begin processing. */ - /* Add the current call plus its associated metadata (the element set and the call's options) onto the global call container. - Anything on this call container is subjected to tick() processing. */ - Collide.State.calls.push([ this._call, - this._ele, - this._options, - null, - this._resolve ]); - } - - startTick(); - } - - start() { - var promises = []; - for (var i = 0; i < this.children.length; i++) { - promises.push( - this.children[i].start() - ); - } - - var clearCache = (this._aniType !== 'start'); - - var promise = this._prepare(clearCache, () => { - this._aniType = 'start'; - this._queue(); - }); - - promises.push(promise); - - return Promise.all(promises); - } - - ready() { - var promises = []; - for (var i = 0; i < this.children.length; i++) { - promises.push( - this.children[i].ready() - ); - } - - var clearCache = (this._aniType !== 'progress'); - - var promise = this._prepare(clearCache, () => { - this._aniType = 'progress'; - }); - - promises.push(promise); - - return Promise.all(promises); - } - - progress(value) { - // go to and stop at a specific point in the animation - - if (this._aniType = 'progress') { - this._options.percentComplete = value; - - for (var i = 0; i < this.children.length; i++) { - this.children[i].progress(value); - } - - this._queue(); - } - } - - stop() { - // immediately stop where it's at - animationStop(this._ele, 'stop'); - return this; - } - - finish() { - // immediately go to the end of the animation - animationStop(this._ele, 'finish'); - return this; - } - - addStartClass(className) { - this._addStartClasses.push(className); - return this; - } - - removeStartClass(className) { - this._removeStartClasses.push(className); - return this; - } - - addEndClass(className) { - this._addEndClasses.push(className); - return this; - } - - removeEndClass(className) { - this._removeEndClasses.push(className); - return this; - } - - isAnimating() { - var eleData; - if (this._ele) { - for (var i = 0, ii = this._ele.length; i < ii; i++) { - eleData = data(this._ele[i]); - if (eleData && eleData.isAnimating) { - return true; - } - } - } - return false; - } - - - /************ - Options - ************/ - options(val) { - this._options = val || {}; - return this; - } - - option(key, val) { - this._options[key] = val; - return this; - } - - duration(val) { - this._options.duration = val; - return this; - } - - easing(val) { - this._options.easing = val; - return this; - } - - display(val) { - this._options.display = val; - return this; - } - - - /*************** - Properties - ***************/ - - from(propertyName, value) { - // [endValue, easing, startValue] - let prop = this.getProperty(propertyName); - prop[2] = value; - return this; - } - - to() { - // [endValue, easing, startValue] - if (arguments.length > 1) { - let prop = this.getProperty(arguments[0]); - prop[0] = arguments[1]; - } else { - this._properties = arguments[0] || {}; - } - return this; - } - - getProperty(propertyName) { - // [endValue, easing, startValue] - if (!this._properties[propertyName]) { - this._properties[propertyName] = [null, null, null]; - } - return this._properties[propertyName]; - } - - - /********* - Misc - *********/ - - debug(val) { - Collide.debug = !!val; - return this; - } - -} diff --git a/ionic/collide/calculate-unit-ratios.js b/ionic/collide/calculate-unit-ratios.js deleted file mode 100644 index a76d017508..0000000000 --- a/ionic/collide/calculate-unit-ratios.js +++ /dev/null @@ -1,127 +0,0 @@ -/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */ - -/*************************** - Unit Ratio Calculation -***************************/ - -/* When queried, the browser returns (most) CSS property values in pixels. Therefore, if an endValue with a unit type of - %, em, or rem is animated toward, startValue must be converted from pixels into the same unit type as endValue in order - for value manipulation logic (increment/decrement) to proceed. Further, if the startValue was forcefed or transferred - from a previous call, startValue may also not be in pixels. Unit conversion logic therefore consists of two steps: - 1) Calculating the ratio of %/em/rem/vh/vw relative to pixels - 2) Converting startValue into the same unit of measurement as endValue based on these ratios. */ -/* Unit conversion ratios are calculated by inserting a sibling node next to the target node, copying over its position property, - setting values with the target unit type then comparing the returned pixel value. */ -/* Note: Even if only one of these unit types is being animated, all unit ratios are calculated at once since the overhead - of batching the SETs and GETs together upfront outweights the potential overhead - of layout thrashing caused by re-querying for uncalculated ratios for subsequently-processed properties. */ -/* Todo: Shift this logic into the calls' first tick instance so that it's synced with RAF. */ -export function calculateUnitRatios(element, callUnitConversionData) { - - /************************************************************** - parsePropertyValue(), calculateUnitRatios(), Same Ratio Checks - **************************************************************/ - - /* The properties below are used to determine whether the element differs sufficiently from this call's - previously iterated element to also differ in its unit conversion ratios. If the properties match up with those - of the prior element, the prior element's conversion ratios are used. Like most optimizations in Collide, - this is done to minimize DOM querying. */ - var sameRatioIndicators = { - myParent: element.parentNode || document.body, /* GET */ - position: CSS.getPropertyValue(element, 'position'), /* GET */ - fontSize: CSS.getPropertyValue(element, 'fontSize') /* GET */ - }; - - /* Determine if the same % ratio can be used. % is based on the element's position value and its parent's width and height dimensions. */ - var samePercentRatio = ((sameRatioIndicators.position === callUnitConversionData.lastPosition) && (sameRatioIndicators.myParent === callUnitConversionData.lastParent)); - - /* Determine if the same em ratio can be used. em is relative to the element's fontSize. */ - var sameEmRatio = (sameRatioIndicators.fontSize === callUnitConversionData.lastFontSize); - - /* Store these ratio indicators call-wide for the next element to compare against. */ - callUnitConversionData.lastParent = sameRatioIndicators.myParent; - callUnitConversionData.lastPosition = sameRatioIndicators.position; - callUnitConversionData.lastFontSize = sameRatioIndicators.fontSize; - - - /********************************************************************** - parsePropertyValue(), calculateUnitRatios(), Element-Specific Units - **********************************************************************/ - - var measurement = 100, - unitRatios = {}; - - if (!sameEmRatio || !samePercentRatio) { - var dummy = data(element).isSVG ? document.createElementNS('http://www.w3.org/2000/svg', 'rect') : document.createElement('div'); - - Collide.init(dummy); - sameRatioIndicators.myParent.appendChild(dummy); - - /* To accurately and consistently calculate conversion ratios, the element's cascaded overflow and box-sizing are stripped. - Similarly, since width/height can be artificially constrained by their min-/max- equivalents, these are controlled for as well. */ - /* Note: Overflow must be also be controlled for per-axis since the overflow property overwrites its per-axis values. */ - var cssPropNames = [ 'overflow', 'overflowX', 'overflowY' ]; - for (var i = 0; i < overflows.length; i++) { - Collide.CSS.setPropertyValue(dummy, cssPropNames[i], 'hidden'); - } - - Collide.CSS.setPropertyValue(dummy, 'position', sameRatioIndicators.position); - Collide.CSS.setPropertyValue(dummy, 'fontSize', sameRatioIndicators.fontSize); - Collide.CSS.setPropertyValue(dummy, 'boxSizing', 'content-box'); - - /* width and height act as our proxy properties for measuring the horizontal and vertical % ratios. */ - cssPropNames = [ 'minWidth', 'maxWidth', 'width', 'minHeight', 'maxHeight', 'height' ]; - for (var i = 0; i < overflows.length; i++) { - Collide.CSS.setPropertyValue(dummy, cssPropNames[i], measurement + '%'); - } - - /* paddingLeft arbitrarily acts as our proxy property for the em ratio. */ - Collide.CSS.setPropertyValue(dummy, 'paddingLeft', measurement + 'em'); - - /* Divide the returned value by the measurement to get the ratio between 1% and 1px. Default to 1 since working with 0 can produce Infinite. */ - unitRatios.percentToPxWidth = callUnitConversionData.lastPercentToPxWidth = (parseFloat(CSS.getPropertyValue(dummy, 'width', null, true)) || 1) / measurement; /* GET */ - unitRatios.percentToPxHeight = callUnitConversionData.lastPercentToPxHeight = (parseFloat(CSS.getPropertyValue(dummy, 'height', null, true)) || 1) / measurement; /* GET */ - unitRatios.emToPx = callUnitConversionData.lastEmToPx = (parseFloat(CSS.getPropertyValue(dummy, 'paddingLeft')) || 1) / measurement; /* GET */ - - sameRatioIndicators.myParent.removeChild(dummy); - - } else { - unitRatios.emToPx = callUnitConversionData.lastEmToPx; - unitRatios.percentToPxWidth = callUnitConversionData.lastPercentToPxWidth; - unitRatios.percentToPxHeight = callUnitConversionData.lastPercentToPxHeight; - } - - - /********************************************************************** - parsePropertyValue(), calculateUnitRatios(), Element-Agnostic Units - ***********************************************************************/ - - /* Whereas % and em ratios are determined on a per-element basis, the rem unit only needs to be checked - once per call since it's exclusively dependant upon document.body's fontSize. If this is the first time - that calculateUnitRatios() is being run during this call, remToPx will still be set to its default value of null, - so we calculate it now. */ - if (callUnitConversionData.remToPx === null) { - /* Default to browsers' default fontSize of 16px in the case of 0. */ - if (!remToPx) { - remToPx = parseFloat(CSS.getPropertyValue(document.body, 'fontSize')) || 16; /* GET */ - } - callUnitConversionData.remToPx = remToPx - } - - /* Similarly, viewport units are %-relative to the window's inner dimensions. */ - if (callUnitConversionData.vwToPx === null) { - callUnitConversionData.vwToPx = parseFloat(window.innerWidth) / 100; /* GET */ - callUnitConversionData.vhToPx = parseFloat(window.innerHeight) / 100; /* GET */ - } - - unitRatios.remToPx = callUnitConversionData.remToPx; - unitRatios.vwToPx = callUnitConversionData.vwToPx; - unitRatios.vhToPx = callUnitConversionData.vhToPx; - - if (Collide.debug >= 1) console.log('Unit ratios: ' + JSON.stringify(unitRatios), element); - - return unitRatios; - -}; - -let remToPx = null; diff --git a/ionic/collide/collide.js b/ionic/collide/collide.js deleted file mode 100644 index 023c5d6c95..0000000000 --- a/ionic/collide/collide.js +++ /dev/null @@ -1,173 +0,0 @@ -/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */ - -import {dom} from 'ionic/util' - - -export let Collide = { - - /* Container for page-wide Collide state data. */ - State: { - /* Create a cached element for re-use when checking for CSS property prefixes. */ - prefixElement: document.createElement('div'), - - /* Cache every prefix match to avoid repeating lookups. */ - prefixMatches: {}, - - /* Cache the anchor used for animating window scrolling. */ - scrollAnchor: null, - - /* Cache the browser-specific property names associated with the scroll anchor. */ - scrollPropertyLeft: null, - scrollPropertyTop: null, - - /* Keep track of whether our RAF tick is running. */ - isTicking: false, - - /* Container for every in-progress call to Collide. */ - calls: [] - }, - - CSS: {}, - Easings: {}, - - /* Collide option defaults, which can be overriden by the user. */ - defaults: { - queue: '', - duration: 400, - easing: 'swing', - begin: undefined, - complete: undefined, - percentComplete: undefined, - percent: undefined, - display: undefined, - visibility: undefined, - loop: false, - delay: false, - /* Advanced: Set to false to prevent property values from being cached between consecutive Collide-initiated chain calls. */ - _cacheValues: true - }, - - /* Used for getting/setting Collide's hooked CSS properties. */ - hook: null, /* Defined below. */ - - /* Collide-wide animation time remapping for testing purposes. */ - mock: false, - - /* Set to 1 or 2 (most verbose) to output debug info to console. */ - debug: false, - - /* initialize element data */ - initData: function(element) { - element.$collide = { - /* Store whether this is an SVG element, since its properties are retrieved and updated differently than standard HTML elements. */ - isSVG: dom.isSVG(element), - - /* Keep track of whether the element is currently being animated by Collide. - This is used to ensure that property values are not transferred between non-consecutive (stale) calls. */ - isAnimating: false, - - /* A reference to the element's live computedStyle object. Learn more here: https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle */ - computedStyle: null, - - /* Tween data is cached for each animation on the element so that data can be passed across calls -- - in particular, end values are used as subsequent start values in consecutive Collide calls. */ - tweensContainer: null, - - /* The full root property values of each CSS hook being animated on this element are cached so that: - 1) Concurrently-animating hooks sharing the same root can have their root values' merged into one while tweening. - 2) Post-hook-injection root values can be transferred over to consecutively chained Collide calls as starting root values. */ - rootPropertyValueCache: {}, - - /* A cache for transform updates, which must be manually flushed via CSS.flushTransformCache(). */ - transformCache: {}, - - startAddCls: null, - startRmvCls: null, - endAddCls: null, - endRmvCls: null - }; - - return element.$collide; - }, - - /* get/set element data */ - data: function(element, key, value) { - if (value === undefined) { - - if (key === undefined) { - // get data object: Data(element) - return element.$collide; - } - - if (element.$collide) { - // get data by key: Data(element, key) - return element.$collide[key]; - } - - } else if (key !== undefined) { - // set data: Data(element, key, value) - if (!element.$collide) { - element.$collide = {}; - } - element.$collide[key] = value; - } - - }, - - /* get/set element queue data */ - queue: function (element, type, data) { - function $makeArray (arr, results) { - let ret = results || []; - - if (arr != null) { - [].push.call(ret, arr); - } - - return ret; - } - - if (!element) { - return; - } - - type = (type || 'collide') + 'queue'; - - var q = this.data(element, type); - - if (!data) { - return q || []; - } - - if (!q || Array.isArray(data)) { - q = this.data(element, type, $makeArray(data)); - - } else { - q.push(data); - } - - return q; - }, - - /* dequeue element */ - dequeue: function (element, type) { - type = type || 'collide'; - - let queue = this.queue(element, type); - let fn = queue.shift(); - - if (fn === 'inprogress') { - fn = queue.shift(); - } - - if (fn) { - if (type === 'collide') { - queue.unshift('inprogress'); - } - - fn.call(element, () => { - this.dequeue(element, type); - }); - } - } - -}; diff --git a/ionic/collide/complete-call.js b/ionic/collide/complete-call.js deleted file mode 100644 index c46b296ac5..0000000000 --- a/ionic/collide/complete-call.js +++ /dev/null @@ -1,186 +0,0 @@ -/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */ - -import {Collide} from './collide' -import {CSS} from './css' - - -/*************************** - Call Completion -***************************/ - -/* Note: Unlike tick(), which processes all active calls at once, call completion is handled on a per-call basis. */ -export function completeCall(callIndex, isStopped) { - - /* Ensure the call exists. */ - if (!Collide.State.calls[callIndex]) { - return false; - } - - /* Pull the metadata from the call. */ - var call = Collide.State.calls[callIndex][0], - elements = Collide.State.calls[callIndex][1], - opts = Collide.State.calls[callIndex][2], - resolve = Collide.State.calls[callIndex][4]; - - var remainingCallsExist = false; - - - /************************* - Element Finalization - *************************/ - - for (var i = 0, callLength = call.length; i < callLength; i++) { - var element = call[i].element; - var eleData = Collide.data(element); - - /* If the user set display to 'none' (intending to hide the element), set it now that the animation has completed. */ - /* Note: display:none isn't set when calls are manually stopped (via Collide('stop'). */ - /* Note: Display gets ignored with 'reverse' calls and infinite loops, since this behavior would be undesirable. */ - if (!isStopped && !opts.loop) { - if (opts.display === 'none') { - CSS.setPropertyValue(element, 'display', opts.display); - } - - if (opts.visibility === 'hidden') { - CSS.setPropertyValue(element, 'visibility', opts.visibility); - } - } - - if (eleData.endAddCls) { - for (var k = 0; k < eleData.endAddCls.length; k++) { - element.classList.add(eleData.endAddCls[k]); - } - eleData.endAddCls = null; - } - - if (eleData.endRmvCls) { - for (var k = 0; k < eleData.endRmvCls.length; k++) { - element.classList.remove(eleData.endRmvCls[k]); - } - eleData.endRmvCls = null; - } - - /* If the element's queue is empty (if only the 'inprogress' item is left at position 0) or if its queue is about to run - a non-Collide-initiated entry, turn off the isAnimating flag. A non-Collide-initiatied queue entry's logic might alter - an element's CSS values and thereby cause Collide's cached value data to go stale. To detect if a queue entry was initiated by Collide, - we check for the existence of our special Collide.queueEntryFlag declaration, which minifiers won't rename since the flag - is assigned to's global object and thus exists out of Collide's own scope. */ - if (opts.loop !== true && (Collide.queue(element)[1] === undefined || !/\.collideQueueEntryFlag/i.test(Collide.queue(element)[1]))) { - /* The element may have been deleted. Ensure that its data cache still exists before acting on it. */ - - if (eleData) { - eleData.isAnimating = false; - - /* Clear the element's rootPropertyValueCache, which will become stale. */ - eleData.rootPropertyValueCache = {}; - - /* If any 3D transform subproperty is at its default value (regardless of unit type), remove it. */ - for (var j = 0, jj = CSS.Lists.transforms3D.length; j < jj; j++) { - var transformName = CSS.Lists.transforms3D[j]; - var defaultValue = /^scale/.test(transformName) ? 1 : 0; - var currentValue = eleData.transformCache[transformName]; - - if (currentValue !== undefined && new RegExp('^\\(' + defaultValue + '[^.]').test(currentValue)) { - delete eleData.transformCache[transformName]; - } - } - - /* Flush the subproperty removals to the DOM. */ - CSS.flushTransformCache(element); - } - } - - - /********************* - Option: Complete - *********************/ - - /* Complete is fired once per call (not once per element) and is passed the full raw DOM element set as both its context and its first argument. */ - /* Note: Callbacks aren't fired when calls are manually stopped (via Collide('stop'). */ - if (!isStopped && opts.complete && !opts.loop && (i === callLength - 1)) { - /* We throw callbacks in a setTimeout so that thrown errors don't halt the execution of Collide itself. */ - try { - opts.complete.call(elements, elements); - } catch (error) { - setTimeout(function() { throw error; }); - } - } - - - /********************** - Promise Resolving - **********************/ - - /* Note: Infinite loops don't return promises. */ - if (resolve && opts.loop !== true) { - resolve(elements); - } - - - /**************************** - Option: Loop (Infinite) - ****************************/ - - if (eleData && opts.loop === true && !isStopped) { - /* If a rotateX/Y/Z property is being animated to 360 deg with loop:true, swap tween start/end values to enable - continuous iterative rotation looping. (Otherise, the element would just rotate back and forth.) */ - - for (var propertyName in eleData.tweensContainer) { - var tweenContainer = eleData.tweensContainer[propertyName] - - if (/^rotate/.test(propertyName) && parseFloat(tweenContainer.endValue) === 360) { - tweenContainer.endValue = 0; - tweenContainer.startValue = 360; - } - - if (/^backgroundPosition/.test(propertyName) && parseFloat(tweenContainer.endValue) === 100 && tweenContainer.unitType === '%') { - tweenContainer.endValue = 0; - tweenContainer.startValue = 100; - } - } - - //TODO!!! FIXME!!! - //Collide(element, 'reverse', { loop: true, delay: opts.delay }); - } - - - /*************** - Dequeueing - ***************/ - - /* Fire the next call in the queue so long as this call's queue wasn't set to false (to trigger a parallel animation), - which would have already caused the next call to fire. Note: Even if the end of the animation queue has been reached, - Collide.dequeue() must still be called in order to completely clear animation queue. */ - if (opts.queue !== false) { - Collide.dequeue(element, opts.queue); - } - - } // END: for (var i = 0, callLength = call.length; i < callLength; i++) - - - /*********************** - Calls Array Cleanup - ************************/ - - /* Since this call is complete, set it to false so that the rAF tick skips it. This array is later compacted via compactSparseArray(). - (For performance reasons, the call is set to false instead of being deleted from the array: http://www.html5rocks.com/en/tutorials/speed/v8/) */ - Collide.State.calls[callIndex] = false; - - /* Iterate through the calls array to determine if this was the final in-progress animation. - If so, set a flag to end ticking and clear the calls array. */ - for (var j = 0, jj = Collide.State.calls.length; j < jj; j++) { - if (Collide.State.calls[j] !== false && Collide.State.calls[j][0]) { - remainingCallsExist = true; - break; - } - } - - if (remainingCallsExist === false) { - /* tick() will detect this flag upon its next iteration and subsequently turn itself off. */ - Collide.State.isTicking = false; - - /* Clear the calls array so that its length is reset. */ - delete Collide.State.calls; - Collide.State.calls = []; - } -} diff --git a/ionic/collide/css.js b/ionic/collide/css.js deleted file mode 100644 index e8779a9945..0000000000 --- a/ionic/collide/css.js +++ /dev/null @@ -1,915 +0,0 @@ -/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */ - -import {Collide} from './collide' - -const data = Collide.data; - - -/***************** - CSS Stack -*****************/ - -/* The CSS object handles the validation, getting, and setting of both standard - CSS properties and CSS property hooks. */ -export var CSS = { - - - /************* - CSS RegEx - *************/ - - RegEx: { - isHex: /^#([A-f\d]{3}){1,2}$/i, - - /* Unwrap a property value's surrounding text, e.g. 'rgba(4, 3, 2, 1)' ==> '4, 3, 2, 1' and 'rect(4px 3px 2px 1px)' ==> '4px 3px 2px 1px'. */ - valueUnwrap: /^[A-z]+\((.*)\)$/i, - - wrappedValueAlreadyExtracted: /[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/, - - /* Split a multi-value property into an array of subvalues, e.g. 'rgba(4, 3, 2, 1) 4px 3px 2px 1px' ==> [ 'rgba(4, 3, 2, 1)', '4px', '3px', '2px', '1px' ]. */ - valueSplit: /([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/ig - }, - - - /************ - CSS Lists - ************/ - - Lists: { - colors: [ 'fill', 'stroke', 'stopColor', 'color', 'backgroundColor', 'borderColor', 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor', 'outlineColor' ], - transformsBase: [ 'translateX', 'translateY', 'scale', 'scaleX', 'scaleY', 'skewX', 'skewY', 'rotateZ' ], - transforms3D: [ 'transformPerspective', 'translateZ', 'scaleZ', 'rotateX', 'rotateY' ] - }, - - - /************ - CSS Hooks - ************/ - - /* Hooks allow a subproperty (e.g. 'boxShadowBlur') of a compound-value CSS property - (e.g. 'boxShadow: X Y Blur Spread Color') to be animated as if it were a discrete property. */ - /* Note: Beyond enabling fine-grained property animation, hooking is necessary since Collide only - tweens properties with single numeric values; unlike CSS transitions, Collide does not interpolate compound-values. */ - Hooks: { - - /******************** - CSS Hook Registration - ********************/ - - /* Templates are a concise way of indicating which subproperties must be individually registered for each compound-value CSS property. */ - /* Each template consists of the compound-value's base name, its constituent subproperty names, and those subproperties' default values. */ - templates: { - textShadow: [ 'Color X Y Blur', 'black 0px 0px 0px' ], - boxShadow: [ 'Color X Y Blur Spread', 'black 0px 0px 0px 0px' ], - clip: [ 'Top Right Bottom Left', '0px 0px 0px 0px' ], - backgroundPosition: [ 'X Y', '0% 0%' ], - transformOrigin: [ 'X Y Z', '50% 50% 0px' ], - perspectiveOrigin: [ 'X Y', '50% 50%' ] - }, - - /* A 'registered' hook is one that has been converted from its template form into a live, - tweenable property. It contains data to associate it with its root property. */ - registered: { - /* Note: A registered hook looks like this ==> textShadowBlur: [ 'textShadow', 3 ], - which consists of the subproperty's name, the associated root property's name, - and the subproperty's position in the root's value. */ - }, - - /* Convert the templates into individual hooks then append them to the registered object above. */ - register: function() { - /* Color hooks registration: Colors are defaulted to white -- as opposed to black -- since colors that are - currently set to 'transparent' default to their respective template below when color-animated, - and white is typically a closer match to transparent than black is. An exception is made for text ('color'), - which is almost always set closer to black than white. */ - for (var i = 0; i < CSS.Lists.colors.length; i++) { - var rgbComponents = (CSS.Lists.colors[i] === 'color') ? '0 0 0 1' : '255 255 255 1'; - CSS.Hooks.templates[CSS.Lists.colors[i]] = [ 'Red Green Blue Alpha', rgbComponents ]; - } - - var rootProperty, - hookTemplate, - hookNames; - - /* Hook registration. */ - for (rootProperty in CSS.Hooks.templates) { - hookTemplate = CSS.Hooks.templates[rootProperty]; - hookNames = hookTemplate[0].split(' '); - - for (var i in hookNames) { - var fullHookName = rootProperty + hookNames[i], - hookPosition = i; - - /* For each hook, register its full name (e.g. textShadowBlur) with its root property (e.g. textShadow) - and the hook's position in its template's default value string. */ - CSS.Hooks.registered[fullHookName] = [ rootProperty, hookPosition ]; - } - } - }, - - - /***************************** - CSS Hook Injection and Extraction - *****************************/ - - /* Look up the root property associated with the hook (e.g. return 'textShadow' for 'textShadowBlur'). */ - /* Since a hook cannot be set directly (the browser won't recognize it), style updating for hooks is routed through the hook's root property. */ - getRoot: function(property) { - var hookData = CSS.Hooks.registered[property]; - - if (hookData) { - return hookData[0]; - } - - /* If there was no hook match, return the property name untouched. */ - return property; - }, - - /* Convert any rootPropertyValue, null or otherwise, into a space-delimited list of hook values so that - the targeted hook can be injected or extracted at its standard position. */ - cleanRootPropertyValue: function(rootProperty, rootPropertyValue) { - /* If the rootPropertyValue is wrapped with 'rgb()', 'clip()', etc., remove the wrapping to normalize the value before manipulation. */ - if (CSS.RegEx.valueUnwrap.test(rootPropertyValue)) { - rootPropertyValue = rootPropertyValue.match(CSS.RegEx.valueUnwrap)[1]; - } - - /* If rootPropertyValue is a CSS null-value (from which there's inherently no hook value to extract), - default to the root's default value as defined in CSS.Hooks.templates. */ - /* Note: CSS null-values include 'none', 'auto', and 'transparent'. They must be converted into their - zero-values (e.g. textShadow: 'none' ==> textShadow: '0px 0px 0px black') for hook manipulation to proceed. */ - if (CSS.Values.isCSSNullValue(rootPropertyValue)) { - rootPropertyValue = CSS.Hooks.templates[rootProperty][1]; - } - - return rootPropertyValue; - }, - - /* Extracted the hook's value from its root property's value. This is used to get the starting value of an animating hook. */ - extractValue: function(fullHookName, rootPropertyValue) { - var hookData = CSS.Hooks.registered[fullHookName]; - - if (hookData) { - var hookRoot = hookData[0], - hookPosition = hookData[1]; - - rootPropertyValue = CSS.Hooks.cleanRootPropertyValue(hookRoot, rootPropertyValue); - - /* Split rootPropertyValue into its constituent hook values then grab the desired hook at its standard position. */ - return rootPropertyValue.toString().match(CSS.RegEx.valueSplit)[hookPosition]; - } - - /* If the provided fullHookName isn't a registered hook, return the rootPropertyValue that was passed in. */ - return rootPropertyValue; - }, - - /* Inject the hook's value into its root property's value. This is used to piece back together the root property - once Collide has updated one of its individually hooked values through tweening. */ - injectValue: function(fullHookName, hookValue, rootPropertyValue) { - var hookData = CSS.Hooks.registered[fullHookName]; - - if (hookData) { - var hookRoot = hookData[0], - hookPosition = hookData[1], - rootPropertyValueParts, - rootPropertyValueUpdated; - - rootPropertyValue = CSS.Hooks.cleanRootPropertyValue(hookRoot, rootPropertyValue); - - /* Split rootPropertyValue into its individual hook values, replace the targeted value with hookValue, - then reconstruct the rootPropertyValue string. */ - rootPropertyValueParts = rootPropertyValue.toString().match(CSS.RegEx.valueSplit); - rootPropertyValueParts[hookPosition] = hookValue; - rootPropertyValueUpdated = rootPropertyValueParts.join(' '); - - return rootPropertyValueUpdated; - } - - /* If the provided fullHookName isn't a registered hook, return the rootPropertyValue that was passed in. */ - return rootPropertyValue; - } - }, - - - /******************* - CSS Normalizations - *******************/ - - /* Normalizations standardize CSS property manipulation by pollyfilling browser-specific implementations (e.g. opacity) - and reformatting special properties (e.g. clip, rgba) to look like standard ones. */ - Normalizations: { - - /* Normalizations are passed a normalization target (either the property's name, its extracted value, or its injected value), - the targeted element (which may need to be queried), and the targeted property value. */ - registered: { - clip: function(type, element, propertyValue) { - switch (type) { - - case 'name': - return 'clip'; - - /* Clip needs to be unwrapped and stripped of its commas during extraction. */ - case 'extract': - var extracted; - - /* If Collide also extracted this value, skip extraction. */ - if (CSS.RegEx.wrappedValueAlreadyExtracted.test(propertyValue)) { - extracted = propertyValue; - - } else { - /* Remove the 'rect()' wrapper. */ - extracted = propertyValue.toString().match(CSS.RegEx.valueUnwrap); - - /* Strip off commas. */ - extracted = extracted ? extracted[1].replace(/,(\s+)?/g, ' ') : propertyValue; - } - - return extracted; - - /* Clip needs to be re-wrapped during injection. */ - case 'inject': - return 'rect(' + propertyValue + ')'; - } - }, - - blur: function(type, element, propertyValue) { - switch (type) { - - case 'name': - return '-webkit-filter'; - - case 'extract': - var extracted = parseFloat(propertyValue); - - /* If extracted is NaN, meaning the value isn't already extracted. */ - if (!(extracted || extracted === 0)) { - var blurComponent = propertyValue.toString().match(/blur\(([0-9]+[A-z]+)\)/i); - - /* If the filter string had a blur component, return just the blur value and unit type. */ - if (blurComponent) { - extracted = blurComponent[1]; - - /* If the component doesn't exist, default blur to 0. */ - } else { - extracted = 0; - } - } - - return extracted; - - /* Blur needs to be re-wrapped during injection. */ - case 'inject': - /* For the blur effect to be fully de-applied, it needs to be set to 'none' instead of 0. */ - if (!parseFloat(propertyValue)) { - return 'none'; - } - - return 'blur(' + propertyValue + ')'; - } - }, - - opacity: function(type, element, propertyValue) { - switch (type) { - case 'name': - return 'opacity'; - - case 'extract': - return propertyValue; - - case 'inject': - return propertyValue; - } - } - }, - - - /***************************** - CSS Batched Registrations - *****************************/ - - /* Note: Batched normalizations extend the CSS.Normalizations.registered object. */ - register: function() { - - /**************************************** - CSS Batched Registration Transforms - ****************************************/ - - /* Transforms are the subproperties contained by the CSS 'transform' property. Transforms must undergo normalization - so that they can be referenced in a properties map by their individual names. */ - /* Note: When transforms are 'set', they are actually assigned to a per-element transformCache. When all transform - setting is complete complete, CSS.flushTransformCache() must be manually called to flush the values to the DOM. - Transform setting is batched in this way to improve performance: the transform style only needs to be updated - once when multiple transform subproperties are being animated simultaneously. */ - - for (var i = 0; i < CSS.Lists.transformsBase.length; i++) { - /* Wrap the dynamically generated normalization function in a new scope so that transformName's value is - paired with its respective function. (Otherwise, all functions would take the final for loop's transformName.) */ - (function() { - var transformName = CSS.Lists.transformsBase[i]; - - CSS.Normalizations.registered[transformName] = function(type, element, propertyValue) { - var eleData = data(element); - - switch (type) { - - /* The normalized property name is the parent 'transform' property -- the property that is actually set in CSS. */ - case 'name': - return 'transform'; - - /* Transform values are cached onto a per-element transformCache object. */ - case 'extract': - /* If this transform has yet to be assigned a value, return its null value. */ - if (eleData === undefined || eleData.transformCache[transformName] === undefined) { - /* Scale CSS.Lists.transformsBase default to 1 whereas all other transform properties default to 0. */ - return /^scale/i.test(transformName) ? 1 : 0; - } - /* When transform values are set, they are wrapped in parentheses as per the CSS spec. - Thus, when extracting their values (for tween calculations), we strip off the parentheses. */ - return eleData.transformCache[transformName].replace(/[()]/g, ''); - - case 'inject': - var invalid = false; - - /* If an individual transform property contains an unsupported unit type, the browser ignores the *entire* transform property. - Thus, protect users from themselves by skipping setting for transform values supplied with invalid unit types. */ - /* Switch on the base transform type; ignore the axis by removing the last letter from the transform's name. */ - switch (transformName.substr(0, transformName.length - 1)) { - /* Whitelist unit types for each transform. */ - case 'translate': - invalid = !/(%|px|em|rem|vw|vh|\d)$/i.test(propertyValue); - break; - - /* Since an axis-free 'scale' property is supported as well, a little hack is used here to detect it by chopping off its last letter. */ - case 'scal': - case 'scale': - invalid = !/(\d)$/i.test(propertyValue); - break; - - case 'skew': - invalid = !/(deg|\d)$/i.test(propertyValue); - break; - - case 'rotate': - invalid = !/(deg|\d)$/i.test(propertyValue); - break; - } - - if (!invalid) { - /* As per the CSS spec, wrap the value in parentheses. */ - eleData.transformCache[transformName] = '(' + propertyValue + ')'; - } - - /* Although the value is set on the transformCache object, return the newly-updated value for the calling code to process as normal. */ - return eleData.transformCache[transformName]; - } - }; - })(); - } - - /************************************ - CSS Batched Registration Colors - ************************************/ - - /* Since Collide only animates a single numeric value per property, color animation is achieved by hooking the individual RGBA components of CSS color properties. - Accordingly, color values must be normalized (e.g. '#ff0000', 'red', and 'rgb(255, 0, 0)' ==> '255 0 0 1') so that their components can be injected/extracted by CSS.Hooks logic. */ - for (var i = 0; i < CSS.Lists.colors.length; i++) { - /* Wrap the dynamically generated normalization function in a new scope so that colorName's value is paired with its respective function. - (Otherwise, all functions would take the final for loop's colorName.) */ - (function() { - var colorName = CSS.Lists.colors[i]; - - /* Note: In IE<=8, which support rgb but not rgba, color properties are reverted to rgb by stripping off the alpha component. */ - CSS.Normalizations.registered[colorName] = function(type, element, propertyValue) { - switch (type) { - - case 'name': - return colorName; - - /* Convert all color values into the rgb format. (Old IE can return hex values and color names instead of rgb/rgba.) */ - case 'extract': - var extracted; - - /* If the color is already in its hookable form (e.g. '255 255 255 1') due to having been previously extracted, skip extraction. */ - if (CSS.RegEx.wrappedValueAlreadyExtracted.test(propertyValue)) { - extracted = propertyValue; - - } else { - var converted, - colorNames = { - black: 'rgb(0, 0, 0)', - blue: 'rgb(0, 0, 255)', - gray: 'rgb(128, 128, 128)', - green: 'rgb(0, 128, 0)', - red: 'rgb(255, 0, 0)', - white: 'rgb(255, 255, 255)' - }; - - /* Convert color names to rgb. */ - if (/^[A-z]+$/i.test(propertyValue)) { - if (colorNames[propertyValue] !== undefined) { - converted = colorNames[propertyValue] - - } else { - /* If an unmatched color name is provided, default to black. */ - converted = colorNames.black; - } - - /* Convert hex values to rgb. */ - } else if (CSS.RegEx.isHex.test(propertyValue)) { - converted = 'rgb(' + CSS.Values.hexToRgb(propertyValue).join(' ') + ')'; - - /* If the provided color doesn't match any of the accepted color formats, default to black. */ - } else if (!(/^rgba?\(/i.test(propertyValue))) { - converted = colorNames.black; - } - - /* Remove the surrounding 'rgb/rgba()' string then replace commas with spaces and strip - repeated spaces (in case the value included spaces to begin with). */ - extracted = (converted || propertyValue).toString().match(CSS.RegEx.valueUnwrap)[1].replace(/,(\s+)?/g, ' '); - } - - /* add a fourth (alpha) component if it's missing and default it to 1 (visible). */ - if (extracted.split(' ').length === 3) { - extracted += ' 1'; - } - - return extracted; - - case 'inject': - /* add a fourth (alpha) component if it's missing and default it to 1 (visible). */ - if (propertyValue.split(' ').length === 3) { - propertyValue += ' 1'; - } - - /* Re-insert the browser-appropriate wrapper('rgb/rgba()'), insert commas, and strip off decimal units - on all values but the fourth (R, G, and B only accept whole numbers). */ - return 'rgba(' + propertyValue.replace(/\s+/g, ',').replace(/\.(\d)+(?=,)/g, '') + ')'; - } - }; - })(); - } - } - }, - - - /************************ - CSS Property Names - ************************/ - - Names: { - /* Camelcase a property name into its JavaScript notation (e.g. 'background-color' ==> 'backgroundColor'). - Camelcasing is used to normalize property names between and across calls. */ - camelCase: function(property) { - return property.replace(/-(\w)/g, function(match, subMatch) { - return subMatch.toUpperCase(); - }); - }, - - /* For SVG elements, some properties (namely, dimensional ones) are GET/SET via the element's HTML attributes (instead of via CSS styles). */ - SVGAttribute: function(property) { - return new RegExp('^(width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2)$', 'i').test(property); - }, - - /* Determine whether a property should be set with a vendor prefix. */ - /* If a prefixed version of the property exists, return it. Otherwise, return the original property name. - If the property is not at all supported by the browser, return a false flag. */ - prefixCheck: function(property) { - /* If this property has already been checked, return the cached value. */ - if (CSS.Names.prefixMatches[property]) { - return [ CSS.Names.prefixMatches[property], true ]; - - } else { - for (var i = 0, vendorsLength = vendorPrefixes.length; i < vendorsLength; i++) { - var propertyPrefixed; - - if (i === 0) { - propertyPrefixed = property; - - } else { - /* Capitalize the first letter of the property to conform to JavaScript vendor prefix notation (e.g. webkitFilter). */ - propertyPrefixed = vendorPrefixes[i] + property.replace(/^\w/, function(match) { return match.toUpperCase(); }); - } - - /* Check if the browser supports this property as prefixed. */ - if (typeof Collide.State.prefixElement.style[propertyPrefixed] === 'string') { - /* Cache the match. */ - CSS.Names.prefixMatches[property] = propertyPrefixed; - - return [ propertyPrefixed, true ]; - } - } - - /* If the browser doesn't support this property in any form, include a false flag so that the caller can decide how to proceed. */ - return [ property, false ]; - } - }, - - /* cached property name prefixes */ - prefixMatches: {} - }, - - - /************************ - CSS Property Values - ************************/ - - Values: { - /* Hex to RGB conversion. Copyright Tim Down: http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb */ - hexToRgb: function(hex) { - var shortformRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i, - longformRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i, - rgbParts; - - hex = hex.replace(shortformRegex, function(m, r, g, b) { - return r + r + g + g + b + b; - }); - - rgbParts = longformRegex.exec(hex); - - return rgbParts ? [ parseInt(rgbParts[1], 16), parseInt(rgbParts[2], 16), parseInt(rgbParts[3], 16) ] : [ 0, 0, 0 ]; - }, - - isCSSNullValue: function(value) { - /* The browser defaults CSS values that have not been set to either 0 or one of several possible null-value strings. - Thus, we check for both falsiness and these special strings. */ - /* Null-value checking is performed to default the special strings to 0 (for the sake of tweening) or their hook - templates as defined as CSS.Hooks (for the sake of hook injection/extraction). */ - /* Note: Chrome returns 'rgba(0, 0, 0, 0)' for an undefined color whereas IE returns 'transparent'. */ - return (value == 0 || /^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(value)); - }, - - /* Retrieve a property's default unit type. Used for assigning a unit type when one is not supplied by the user. */ - getUnitType: function(property) { - if (/^(rotate|skew)/i.test(property)) { - return 'deg'; - - } else if (/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(property)) { - /* The above properties are unitless. */ - return ''; - } - - /* Default to px for all other properties. */ - return 'px'; - }, - - /* HTML elements default to an associated display type when they're not set to display:none. */ - /* Note: This function is used for correctly setting the non-'none' display value in certain Collide redirects, such as fadeIn/Out. */ - getDisplayType: function(element) { - var tagName = element && element.tagName && element.tagName.toString().toLowerCase(); - - if (/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/.test(tagName)) { - return 'inline'; - - } else if (/^(li)$/.test(tagName)) { - return 'list-item'; - - } else if (/^(tr)$/.test(tagName)) { - return 'table-row'; - - } else if (/^(table)$/.test(tagName)) { - return 'table'; - - } else if (/^(tbody)$/.test(tagName)) { - return 'table-row-group'; - } - - /* Default to 'block' when no match is found. */ - return 'block'; - } - - }, - - - /**************************** - CSS Style Getting & Setting - ****************************/ - - /* The singular getPropertyValue, which routes the logic for all normalizations, hooks, and standard CSS properties. */ - getPropertyValue: function(element, property, rootPropertyValue, forceStyleLookup) { - - /* Get an element's computed property value. */ - /* Note: Retrieving the value of a CSS property cannot simply be performed by checking an element's - style attribute (which only reflects user-defined values). Instead, the browser must be queried for a property's - *computed* value. You can read more about getComputedStyle here: https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle */ - function computePropertyValue (element, property) { - /* When box-sizing isn't set to border-box, height and width style values are incorrectly computed when an - element's scrollbars are visible (which expands the element's dimensions). Thus, we defer to the more accurate - offsetHeight/Width property, which includes the total dimensions for interior, border, padding, and scrollbar. - We subtract border and padding to get the sum of interior + scrollbar. */ - var computedValue = 0; - - /* Browsers do not return height and width values for elements that are set to display:'none'. Thus, we temporarily - toggle display to the element type's default value. */ - var toggleDisplay = false; - - if (/^(width|height)$/.test(property) && CSS.getPropertyValue(element, 'display') === 0) { - toggleDisplay = true; - CSS.setPropertyValue(element, 'display', CSS.Values.getDisplayType(element)); - } - - function revertDisplay () { - if (toggleDisplay) { - CSS.setPropertyValue(element, 'display', 'none'); - } - } - - if (!forceStyleLookup) { - if (property === 'height' && CSS.getPropertyValue(element, 'boxSizing').toString().toLowerCase() !== 'border-box') { - var contentBoxHeight = element.offsetHeight - (parseFloat(CSS.getPropertyValue(element, 'borderTopWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'borderBottomWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingTop')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingBottom')) || 0); - revertDisplay(); - - return contentBoxHeight; - - } else if (property === 'width' && CSS.getPropertyValue(element, 'boxSizing').toString().toLowerCase() !== 'border-box') { - var contentBoxWidth = element.offsetWidth - (parseFloat(CSS.getPropertyValue(element, 'borderLeftWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'borderRightWidth')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingLeft')) || 0) - (parseFloat(CSS.getPropertyValue(element, 'paddingRight')) || 0); - revertDisplay(); - - return contentBoxWidth; - } - } - - var computedStyle; - var eleData = data(element); - - /* For elements that Collide hasn't been called on directly (e.g. when Collide queries the DOM on behalf - of a parent of an element its animating), perform a direct getComputedStyle lookup since the object isn't cached. */ - if (eleData === undefined) { - computedStyle = window.getComputedStyle(element, null); /* GET */ - - /* If the computedStyle object has yet to be cached, do so now. */ - } else if (!eleData.computedStyle) { - computedStyle = eleData.computedStyle = window.getComputedStyle(element, null); /* GET */ - - /* If computedStyle is cached, use it. */ - } else { - computedStyle = eleData.computedStyle; - } - - /* IE and Firefox do not return a value for the generic borderColor -- they only return individual values for each border side's color. - Also, in all browsers, when border colors aren't all the same, a compound value is returned that isn't setup to parse. - So, as a polyfill for querying individual border side colors, we just return the top border's color and animate all borders from that value. */ - if (property === 'borderColor') { - property = 'borderTopColor'; - } - - computedValue = computedStyle[property]; - - /* Fall back to the property's style value (if defined) when computedValue returns nothing, - which can happen when the element hasn't been painted. */ - if (computedValue === '' || computedValue === null) { - computedValue = element.style[property]; - } - - revertDisplay(); - - /* For top, right, bottom, and left (TRBL) values that are set to 'auto' on elements of 'fixed' or 'absolute' position, - defer to jQuery for converting 'auto' to a numeric value. (For elements with a 'static' or 'relative' position, 'auto' has the same - effect as being set to 0, so no conversion is necessary.) */ - /* An example of why numeric conversion is necessary: When an element with 'position:absolute' has an untouched 'left' - property, which reverts to 'auto', left's value is 0 relative to its parent element, but is often non-zero relative - to its *containing* (not parent) element, which is the nearest 'position:relative' ancestor or the viewport (and always the viewport in the case of 'position:fixed'). */ - if (computedValue === 'auto' && /^(top|right|bottom|left)$/i.test(property)) { - var position = computePropertyValue(element, 'position'); /* GET */ - - /* For absolute positioning, CSS.position(element) only returns values for top and left; - right and bottom will have their 'auto' value reverted to 0. */ - if (position === 'fixed' || (position === 'absolute' && /top|left/i.test(property))) { - computedValue = CSS.position(element)[property] + 'px'; /* GET */ - } - } - - return computedValue; - } - - var propertyValue; - - /* If this is a hooked property (e.g. 'clipLeft' instead of the root property of 'clip'), - extract the hook's value from a normalized rootPropertyValue using CSS.Hooks.extractValue(). */ - if (CSS.Hooks.registered[property]) { - var hook = property, - hookRoot = CSS.Hooks.getRoot(hook); - - /* If a cached rootPropertyValue wasn't passed in (which Collide always attempts to do in order to avoid requerying the DOM), - query the DOM for the root property's value. */ - if (rootPropertyValue === undefined) { - /* Since the browser is now being directly queried, use the official post-prefixing property name for this lookup. */ - rootPropertyValue = CSS.getPropertyValue(element, CSS.Names.prefixCheck(hookRoot)[0]); /* GET */ - } - - /* If this root has a normalization registered, peform the associated normalization extraction. */ - if (CSS.Normalizations.registered[hookRoot]) { - rootPropertyValue = CSS.Normalizations.registered[hookRoot]('extract', element, rootPropertyValue); - } - - /* Extract the hook's value. */ - propertyValue = CSS.Hooks.extractValue(hook, rootPropertyValue); - - /* If this is a normalized property (e.g. 'opacity' becomes 'filter' in <=IE8) or 'translateX' becomes 'transform'), - normalize the property's name and value, and handle the special case of transforms. */ - /* Note: Normalizing a property is mutually exclusive from hooking a property since hook-extracted values are strictly - numerical and therefore do not require normalization extraction. */ - } else if (CSS.Normalizations.registered[property]) { - var normalizedPropertyName, - normalizedPropertyValue; - - normalizedPropertyName = CSS.Normalizations.registered[property]('name', element); - - /* Transform values are calculated via normalization extraction (see below), which checks against the element's transformCache. - At no point do transform GETs ever actually query the DOM; initial stylesheet values are never processed. - This is because parsing 3D transform matrices is not always accurate and would bloat our codebase; - thus, normalization extraction defaults initial transform values to their zero-values (e.g. 1 for scaleX and 0 for translateX). */ - if (normalizedPropertyName !== 'transform') { - normalizedPropertyValue = computePropertyValue(element, CSS.Names.prefixCheck(normalizedPropertyName)[0]); /* GET */ - - /* If the value is a CSS null-value and this property has a hook template, use that zero-value template so that hooks can be extracted from it. */ - if (CSS.Values.isCSSNullValue(normalizedPropertyValue) && CSS.Hooks.templates[property]) { - normalizedPropertyValue = CSS.Hooks.templates[property][1]; - } - } - - propertyValue = CSS.Normalizations.registered[property]('extract', element, normalizedPropertyValue); - } - - /* If a (numeric) value wasn't produced via hook extraction or normalization, query the DOM. */ - if (!/^[\d-]/.test(propertyValue)) { - /* For SVG elements, dimensional properties (which SVGAttribute() detects) are tweened via - their HTML attribute values instead of their CSS style values. */ - var eleData = data(element); - if (eleData && eleData.isSVG && CSS.Names.SVGAttribute(property)) { - /* Since the height/width attribute values must be set manually, they don't reflect computed values. - Thus, we use use getBBox() to ensure we always get values for elements with undefined height/width attributes. */ - if (/^(height|width)$/i.test(property)) { - /* Firefox throws an error if .getBBox() is called on an SVG that isn't attached to the DOM. */ - try { - propertyValue = element.getBBox()[property]; - } catch (error) { - propertyValue = 0; - } - - /* Otherwise, access the attribute value directly. */ - } else { - propertyValue = element.getAttribute(property); - } - - } else { - propertyValue = computePropertyValue(element, CSS.Names.prefixCheck(property)[0]); /* GET */ - } - } - - /* Since property lookups are for animation purposes (which entails computing the numeric delta between start and end values), - convert CSS null-values to an integer of value 0. */ - if (CSS.Values.isCSSNullValue(propertyValue)) { - propertyValue = 0; - } - - if (Collide.debug >= 2) console.log('Get ' + property + ': ' + propertyValue); - - return propertyValue; - }, - - /* The singular setPropertyValue, which routes the logic for all normalizations, hooks, and standard CSS properties. */ - setPropertyValue: function(element, property, propertyValue, rootPropertyValue, scrollData) { - var propertyName = property; - - /* In order to be subjected to call options and element queueing, scroll animation is routed through Collie as if it were a standard CSS property. */ - if (property === 'scroll') { - /* If a container option is present, scroll the container instead of the browser window. */ - if (scrollData.container) { - scrollData.container['scroll' + scrollData.direction] = propertyValue; - - /* Otherwise, Collide defaults to scrolling the browser window. */ - } else { - if (scrollData.direction === 'Left') { - window.scrollTo(propertyValue, scrollData.alternateValue); - } else { - window.scrollTo(scrollData.alternateValue, propertyValue); - } - } - - } else { - var eleData = data(element); - - /* Transforms (translateX, rotateZ, etc.) are applied to a per-element transformCache object, which is manually flushed via flushTransformCache(). - Thus, for now, we merely cache transforms being SET. */ - if (CSS.Normalizations.registered[property] && CSS.Normalizations.registered[property]('name', element) === 'transform') { - /* Perform a normalization injection. */ - /* Note: The normalization logic handles the transformCache updating. */ - CSS.Normalizations.registered[property]('inject', element, propertyValue); - - propertyName = 'transform'; - propertyValue = eleData.transformCache[property]; - - } else { - /* Inject hooks. */ - if (CSS.Hooks.registered[property]) { - var hookName = property, - hookRoot = CSS.Hooks.getRoot(property); - - /* If a cached rootPropertyValue was not provided, query the DOM for the hookRoot's current value. */ - rootPropertyValue = rootPropertyValue || CSS.getPropertyValue(element, hookRoot); /* GET */ - - propertyValue = CSS.Hooks.injectValue(hookName, propertyValue, rootPropertyValue); - property = hookRoot; - } - - /* Normalize names and values. */ - if (CSS.Normalizations.registered[property]) { - propertyValue = CSS.Normalizations.registered[property]('inject', element, propertyValue); - property = CSS.Normalizations.registered[property]('name', element); - } - - /* Assign the appropriate vendor prefix before performing an official style update. */ - propertyName = CSS.Names.prefixCheck(property)[0]; - - /* SVG elements have their dimensional properties (width, height, x, y, cx, etc.) applied directly as attributes instead of as styles. */ - - if (eleData && eleData.isSVG && CSS.Names.SVGAttribute(property)) { - /* Note: For SVG attributes, vendor-prefixed property names are never used. */ - /* Note: Not all CSS properties can be animated via attributes, but the browser won't throw an error for unsupported properties. */ - element.setAttribute(property, propertyValue); - - } else { - element.style[propertyName] = propertyValue; - } - - if (Collide.debug >= 2) console.log('Set ' + property + ' (' + propertyName + '): ' + propertyValue); - } - } - - /* Return the normalized property name and value in case the caller wants to know how these values were modified before being applied to the DOM. */ - return [ propertyName, propertyValue ]; - }, - - /* To increase performance by batching transform updates into a single SET, transforms are not directly applied to an element until flushTransformCache() is called. */ - /* Note: Collide applies transform properties in the same order that they are chronogically introduced to the element's CSS styles. */ - flushTransformCache: function(element) { - var transformString = ''; - var transformCache = data(element).transformCache; - - var transformValue, - perspective; - - /* Transform properties are stored as members of the transformCache object. Concatenate all the members into a string. */ - for (var transformName in transformCache) { - transformValue = transformCache[transformName]; - - /* Transform's perspective subproperty must be set first in order to take effect. Store it temporarily. */ - if (transformName === 'transformPerspective') { - perspective = transformValue; - return true; - } - - transformString += transformName + transformValue + ' '; - } - - /* If present, set the perspective subproperty first. */ - if (perspective) { - transformString = 'perspective' + perspective + ' ' + transformString; - } - - CSS.setPropertyValue(element, 'transform', transformString); - }, - - offset: function(element) { - var box = element.getBoundingClientRect ? element.getBoundingClientRect() : { top: 0, left: 0 }; - - return { - top: box.top + (window.pageYOffset || document.scrollTop || 0) - (document.clientTop || 0), - left: box.left + (window.pageXOffset || document.scrollLeft || 0) - (document.clientLeft || 0) - }; - }, - - position: function(element) { - function offsetParent() { - var offsetParent = this.offsetParent || document; - - while (offsetParent && (!offsetParent.nodeType.toLowerCase === 'html' && offsetParent.style.position === 'static')) { - offsetParent = offsetParent.offsetParent; - } - - return offsetParent || document; - } - - var offsetParent = offsetParent.apply(element), - offset = this.offset(element), - parentOffset = /^(?:body|html)$/i.test(offsetParent.nodeName) ? { top: 0, left: 0 } : this.offset(offsetParent) - - offset.top -= parseFloat(element.style.marginTop) || 0; - offset.left -= parseFloat(element.style.marginLeft) || 0; - - if (offsetParent.style) { - parentOffset.top += parseFloat(offsetParent.style.borderTopWidth) || 0 - parentOffset.left += parseFloat(offsetParent.style.borderLeftWidth) || 0 - } - - return { - top: offset.top - parentOffset.top, - left: offset.left - parentOffset.left - }; - } - -}; - -const vendorPrefixes = [ '', 'Webkit', 'ms' ]; - -/* Register hooks and normalizations. */ -CSS.Hooks.register(); -CSS.Normalizations.register(); diff --git a/ionic/collide/easing.js b/ionic/collide/easing.js deleted file mode 100644 index e4ecb6a2d5..0000000000 --- a/ionic/collide/easing.js +++ /dev/null @@ -1,316 +0,0 @@ -/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */ - -import * as util from 'ionic/util/util' -import {Collide} from './collide' - - -/************** - Easing -**************/ - -/* Step easing generator. */ -function generateStep (steps) { - return function (p) { - return Math.round(p * steps) * (1 / steps); - }; -} - -/* Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */ -function generateBezier (mX1, mY1, mX2, mY2) { - var NEWTON_ITERATIONS = 4, - NEWTON_MIN_SLOPE = 0.001, - SUBDIVISION_PRECISION = 0.0000001, - SUBDIVISION_MAX_ITERATIONS = 10, - kSplineTableSize = 11, - kSampleStepSize = 1.0 / (kSplineTableSize - 1.0), - float32ArraySupported = 'Float32Array' in window; - - /* Must contain four arguments. */ - if (arguments.length !== 4) { - return false; - } - - /* Arguments must be numbers. */ - for (var i = 0; i < 4; ++i) { - if (typeof arguments[i] !== 'number' || isNaN(arguments[i]) || !isFinite(arguments[i])) { - return false; - } - } - - /* X values must be in the [0, 1] range. */ - mX1 = Math.min(mX1, 1); - mX2 = Math.min(mX2, 1); - mX1 = Math.max(mX1, 0); - mX2 = Math.max(mX2, 0); - - var mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); - - function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } - function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } - function C (aA1) { return 3.0 * aA1; } - - function calcBezier (aT, aA1, aA2) { - return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT; - } - - function getSlope (aT, aA1, aA2) { - return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1); - } - - function newtonRaphsonIterate (aX, aGuessT) { - for (var i = 0; i < NEWTON_ITERATIONS; ++i) { - var currentSlope = getSlope(aGuessT, mX1, mX2); - - if (currentSlope === 0.0) return aGuessT; - - var currentX = calcBezier(aGuessT, mX1, mX2) - aX; - aGuessT -= currentX / currentSlope; - } - - return aGuessT; - } - - function calcSampleValues () { - for (var i = 0; i < kSplineTableSize; ++i) { - mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); - } - } - - function binarySubdivide (aX, aA, aB) { - var currentX, currentT, i = 0; - - do { - currentT = aA + (aB - aA) / 2.0; - currentX = calcBezier(currentT, mX1, mX2) - aX; - if (currentX > 0.0) { - aB = currentT; - } else { - aA = currentT; - } - } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); - - return currentT; - } - - function getTForX (aX) { - var intervalStart = 0.0, - currentSample = 1, - lastSample = kSplineTableSize - 1; - - for (; currentSample != lastSample && mSampleValues[currentSample] <= aX; ++currentSample) { - intervalStart += kSampleStepSize; - } - - --currentSample; - - var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample+1] - mSampleValues[currentSample]), - guessForT = intervalStart + dist * kSampleStepSize, - initialSlope = getSlope(guessForT, mX1, mX2); - - if (initialSlope >= NEWTON_MIN_SLOPE) { - return newtonRaphsonIterate(aX, guessForT); - } else if (initialSlope == 0.0) { - return guessForT; - } else { - return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize); - } - } - - var _precomputed = false; - - function precompute() { - _precomputed = true; - if (mX1 != mY1 || mX2 != mY2) calcSampleValues(); - } - - var f = function (aX) { - if (!_precomputed) precompute(); - if (mX1 === mY1 && mX2 === mY2) return aX; - if (aX === 0) return 0; - if (aX === 1) return 1; - - return calcBezier(getTForX(aX), mY1, mY2); - }; - - f.getControlPoints = function() { return [{ x: mX1, y: mY1 }, { x: mX2, y: mY2 }]; }; - - var str = 'generateBezier(' + [mX1, mY1, mX2, mY2] + ')'; - f.toString = function () { return str; }; - - return f; -} - -/* Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */ -/* Given a tension, friction, and duration, a simulation at 60FPS will first run without a defined duration in order to calculate the full path. A second pass - then adjusts the time delta -- using the relation between actual time and duration -- to calculate the path for the duration-constrained animation. */ -var generateSpringRK4 = (function () { - function springAccelerationForState (state) { - return (-state.tension * state.x) - (state.friction * state.v); - } - - function springEvaluateStateWithDerivative (initialState, dt, derivative) { - var state = { - x: initialState.x + derivative.dx * dt, - v: initialState.v + derivative.dv * dt, - tension: initialState.tension, - friction: initialState.friction - }; - - return { dx: state.v, dv: springAccelerationForState(state) }; - } - - function springIntegrateState (state, dt) { - var a = { - dx: state.v, - dv: springAccelerationForState(state) - }, - b = springEvaluateStateWithDerivative(state, dt * 0.5, a), - c = springEvaluateStateWithDerivative(state, dt * 0.5, b), - d = springEvaluateStateWithDerivative(state, dt, c), - dxdt = 1.0 / 6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx), - dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv); - - state.x = state.x + dxdt * dt; - state.v = state.v + dvdt * dt; - - return state; - } - - return function springRK4Factory (tension, friction, duration) { - - var initState = { - x: -1, - v: 0, - tension: null, - friction: null - }, - path = [0], - time_lapsed = 0, - tolerance = 1 / 10000, - DT = 16 / 1000, - have_duration, dt, last_state; - - tension = parseFloat(tension) || 500; - friction = parseFloat(friction) || 20; - duration = duration || null; - - initState.tension = tension; - initState.friction = friction; - - have_duration = duration !== null; - - /* Calculate the actual time it takes for this animation to complete with the provided conditions. */ - if (have_duration) { - /* Run the simulation without a duration. */ - time_lapsed = springRK4Factory(tension, friction); - /* Compute the adjusted time delta. */ - dt = time_lapsed / duration * DT; - } else { - dt = DT; - } - - while (true) { - /* Next/step function .*/ - last_state = springIntegrateState(last_state || initState, dt); - /* Store the position. */ - path.push(1 + last_state.x); - time_lapsed += 16; - /* If the change threshold is reached, break. */ - if (!(Math.abs(last_state.x) > tolerance && Math.abs(last_state.v) > tolerance)) { - break; - } - } - - /* If duration is not defined, return the actual time required for completing this animation. Otherwise, return a closure that holds the - computed path and returns a snapshot of the position according to a given percentComplete. */ - return !have_duration ? time_lapsed : function(percentComplete) { return path[ (percentComplete * (path.length - 1)) | 0 ]; }; - }; -}()); - - -/* default easings. */ -Collide.Easings = { - linear: function(p) { return p; }, - swing: function(p) { return 0.5 - Math.cos( p * Math.PI ) / 2 }, - spring: function(p) { return 1 - (Math.cos(p * 4.5 * Math.PI) * Math.exp(-p * 6)); } -}; - - -/* CSS3 and Robert Penner easings. */ -(function() { - - let penner = [ - [ 'ease', [ 0.25, 0.1, 0.25, 1 ] ], - [ 'ease-in', [ 0.42, 0.0, 1.00, 1 ] ], - [ 'ease-out', [ 0.00, 0.0, 0.58, 1 ] ], - [ 'ease-in-out', [ 0.42, 0.0, 0.58, 1 ] ], - [ 'easeInSine', [ 0.47, 0, 0.745, 0.715 ] ], - [ 'easeOutSine', [ 0.39, 0.575, 0.565, 1 ] ], - [ 'easeInOutSine', [ 0.445, 0.05, 0.55, 0.95 ] ], - [ 'easeInQuad', [ 0.55, 0.085, 0.68, 0.53 ] ], - [ 'easeOutQuad', [ 0.25, 0.46, 0.45, 0.94 ] ], - [ 'easeInOutQuad', [ 0.455, 0.03, 0.515, 0.955 ] ], - [ 'easeInCubic', [ 0.55, 0.055, 0.675, 0.19 ] ], - [ 'easeOutCubic', [ 0.215, 0.61, 0.355, 1 ] ], - [ 'easeInOutCubic', [ 0.645, 0.045, 0.355, 1 ] ], - [ 'easeInQuart', [ 0.895, 0.03, 0.685, 0.22 ] ], - [ 'easeOutQuart', [ 0.165, 0.84, 0.44, 1 ] ], - [ 'easeInOutQuart', [ 0.77, 0, 0.175, 1 ] ], - [ 'easeInQuint', [ 0.755, 0.05, 0.855, 0.06 ] ], - [ 'easeOutQuint', [ 0.23, 1, 0.32, 1 ] ], - [ 'easeInOutQuint', [ 0.86, 0, 0.07, 1 ] ], - [ 'easeInExpo', [ 0.95, 0.05, 0.795, 0.035 ] ], - [ 'easeOutExpo', [ 0.19, 1, 0.22, 1 ] ], - [ 'easeInOutExpo', [ 1, 0, 0, 1 ] ], - [ 'easeInCirc', [ 0.6, 0.04, 0.98, 0.335 ] ], - [ 'easeOutCirc', [ 0.075, 0.82, 0.165, 1 ] ], - [ 'easeInOutCirc', [ 0.785, 0.135, 0.15, 0.86 ] ] - ]; - - for (var i = 0, l = penner.length; i < l; i++) { - addEasing(penner[i][0], penner[i][1]); - } - -})(); - -export function addEasing(name, values) { - Collide.Easings[name] = generateBezier.apply(null, values); -}; - - -/* Determine the appropriate easing type given an easing input. */ -export function getEasing(value, duration) { - let easing = value; - - /* The easing option can either be a string that references a pre-registered easing, - or it can be a two-/four-item array of integers to be converted into a bezier/spring function. */ - if (util.isString(value)) { - /* Ensure that the easing has been assigned to standard easings object. */ - if (!Collide.Easings[value]) { - easing = false; - } - - } else if (util.isArray(value) && value.length === 1) { - easing = generateStep.apply(null, value); - - } else if (util.isArray(value) && value.length === 2) { - /* springRK4 must be passed the animation's duration. */ - /* Note: If the springRK4 array contains non-numbers, generateSpringRK4() returns an easing - function generated with default tension and friction values. */ - easing = generateSpringRK4.apply(null, value.concat([ duration ])); - - } else if (util.isArray(value) && value.length === 4) { - /* Note: If the bezier array contains non-numbers, generateBezier() returns false. */ - easing = generateBezier.apply(null, value); - - } else { - easing = false; - } - - /* Revert to the fall back to 'swing' */ - if (easing === false) { - easing = 'swing'; - } - - return easing; -}; diff --git a/ionic/collide/process-element.js b/ionic/collide/process-element.js deleted file mode 100644 index 15eb33a650..0000000000 --- a/ionic/collide/process-element.js +++ /dev/null @@ -1,655 +0,0 @@ -/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */ - -import * as util from 'ionic/util/util' -import {Collide} from './collide' -import {CSS} from './css' -import {getEasing} from './easing' -import {calculateUnitRatios} from './calculate-unit-ratios' - - -const data = Collide.data; - -/********************* - Element Processing -*********************/ - -/* Element processing consists of three parts -- data processing that cannot go stale and data processing that *can* go stale (i.e. third-party style modifications): - 1) Pre-Queueing: Element-wide variables, including the element's data storage, are instantiated. Call options are prepared. If triggered, the Stop action is executed. - 2) Queueing: The logic that runs once this call has reached its point of execution in the element's Collide.queue() stack. Most logic is placed here to avoid risking it becoming stale. - 3) Pushing: Consolidation of the tween data followed by its push onto the global in-progress calls container. -*/ - -export function processElement(action, animation, elementIndex, clearCache) { - var elements = animation._ele; - var elementsLength = elements.length; - var element = elements[elementIndex]; - var eleData; - - var opts = animation._options; - var propertiesMap = animation._properties; - var callUnitConversionData = animation._unitConversion; - var call = animation._call; - var resolve = animation._resolve; - - - /************************* - Part I: Pre-Queueing - *************************/ - - /*************************** - Element-Wide Variables - ***************************/ - - /* A container for the processed data associated with each property in the propertyMap. - (Each property in the map produces its own 'tween'.) */ - var tweensContainer = {}; - var elementUnitConversionData = null; - - - /****************** - Element Init - ******************/ - - if (data(element) === undefined) { - eleData = Collide.initData(element); - } else { - eleData = data(element); - } - - if (animation._addStartClasses.length) eleData.startAddCls = animation._addStartClasses.slice(); - if (animation._removeStartClasses.length) eleData.startRmvCls = animation._removeStartClasses.slice(); - if (animation._addEndClasses.length) eleData.endAddCls = animation._addEndClasses.slice(); - if (animation._removeEndClasses.length) eleData.endRmvCls = animation._removeEndClasses.slice(); - - - /****************** - Option: Delay - ******************/ - - /* Since queue:false doesn't respect the item's existing queue, we avoid injecting its delay here (it's set later on). */ - if (parseFloat(opts.delay) && opts.queue !== false) { - Collide.queue(element, opts.queue, function(next) { - // This is a flag used to indicate to the upcoming completeCall() function that this queue entry was initiated by Collide. - // See completeCall() for further details. - Collide.collideQueueEntryFlag = true; - - // The ensuing queue item (which is assigned to the 'next' argument that Collide.queue() automatically passes in) will be triggered after a setTimeout delay. - // The setTimeout is stored so that it can be subjected to clearTimeout() if this animation is prematurely stopped via Collide's 'stop' command. - data(element).delayTimer = { - setTimeout: setTimeout(next, parseFloat(opts.delay)), - next: next - }; - }); - } - - - /********************* - Option: Duration - *********************/ - - opts.duration = parseFloat(opts.duration) || 1; - - - /******************* - Option: Easing - *******************/ - - opts.easing = getEasing(opts.easing, opts.duration); - - - /********************** - Option: Callbacks - **********************/ - - /* Callbacks must functions. Otherwise, default to null. */ - if (opts.begin && typeof opts.begin !== 'function') { - opts.begin = null; - } - - if (opts.progress && typeof opts.progress !== 'function') { - opts.progress = null; - } - - if (opts.complete && typeof opts.complete !== 'function') { - opts.complete = null; - } - - - /********************************* - Option: Display & Visibility - *********************************/ - - /* Refer to Collide's documentation (CollideJS.org/#displayAndVisibility) for a description of the display and visibility options' behavior. */ - /* Note: We strictly check for undefined instead of falsiness because display accepts an empty string value. */ - if (opts.display !== undefined && opts.display !== null) { - opts.display = opts.display.toString().toLowerCase(); - - /* Users can pass in a special 'auto' value to instruct Collide to set the element to its default display value. */ - if (opts.display === 'auto') { - opts.display = CSS.getDisplayType(element); - } - } - - if (opts.visibility !== undefined && opts.visibility !== null) { - opts.visibility = opts.visibility.toString().toLowerCase(); - } - - - /*********************** - Part II: Queueing - ***********************/ - - /* When a set of elements is targeted by a Collide call, the set is broken up and each element has the current Collide call individually queued onto it. - In this way, each element's existing queue is respected; some elements may already be animating and accordingly should not have this current Collide call triggered immediately. */ - /* In each queue, tween data is processed for each animating property then pushed onto the call-wide calls array. When the last element in the set has had its tweens processed, - the call array is pushed to Collide.State.calls for live processing by the requestAnimationFrame tick. */ - function buildQueue(next) { - - /******************* - Option: Begin - *******************/ - - /* The begin callback is fired once per call -- not once per elemenet -- and is passed the full raw DOM element set as both its context and its first argument. */ - if (opts.begin && elementIndex === 0) { - /* We throw callbacks in a setTimeout so that thrown errors don't halt the execution of Collide itself. */ - try { - opts.begin.call(elements, elements); - } catch (error) { - setTimeout(function() { throw error; }); - } - } - - - /***************************************** - Tween Data Construction (for Scroll) - *****************************************/ - - /* Note: In order to be subjected to chaining and animation options, scroll's tweening is routed through Collide as if it were a standard CSS property animation. */ - if (action === 'scroll') { - /* The scroll action uniquely takes an optional 'offset' option -- specified in pixels -- that offsets the targeted scroll position. */ - var scrollDirection = (/^x$/i.test(opts.axis) ? 'Left' : 'Top'), - scrollOffset = parseFloat(opts.offset) || 0, - scrollPositionCurrent, - scrollPositionCurrentAlternate, - scrollPositionEnd; - - /* Scroll also uniquely takes an optional 'container' option, which indicates the parent element that should be scrolled -- - as opposed to the browser window itself. This is useful for scrolling toward an element that's inside an overflowing parent element. */ - if (opts.container) { - /* Ensure that a raw DOM element was passed in. */ - if (opts.container.nodeType) { - /* Note: Unlike other properties in Collide, the browser's scroll position is never cached since it so frequently changes - (due to the user's natural interaction with the page). */ - scrollPositionCurrent = opts.container['scroll' + scrollDirection]; /* GET */ - - /* CSS.position(element) values are relative to the container's currently viewable area (without taking into - account the container's true dimensions, for example, if the container was not overflowing). Thus, the scroll end - value is the sum of the child element's position *and* the scroll container's current scroll position. */ - scrollPositionEnd = (scrollPositionCurrent + CSS.position(element)[scrollDirection.toLowerCase()]) + scrollOffset; /* GET */ - - } else { - /* If a value other than a raw DOM element was passed in, default to null so that this option is ignored. */ - opts.container = null; - } - - } else { - /* If the window itself is being scrolled -- not a containing element -- perform a live scroll position lookup using - the appropriate cached property names (which differ based on browser type). */ - scrollPositionCurrent = Collide.State.scrollAnchor[Collide.State['scrollProperty' + scrollDirection]]; /* GET */ - - /* When scrolling the browser window, cache the alternate axis's current value since window.scrollTo() doesn't let us change only one value at a time. */ - scrollPositionCurrentAlternate = Collide.State.scrollAnchor[Collide.State['scrollProperty' + (scrollDirection === 'Left' ? 'Top' : 'Left')]]; /* GET */ - - /* Unlike CSS.position(element), CSS.offset(element) values are relative to the browser window's true dimensions - -- not merely its currently viewable area -- and therefore end values do not need to be compounded onto current values. */ - scrollPositionEnd = CSS.offset(element)[scrollDirection.toLowerCase()] + scrollOffset; /* GET */ - } - - /* Since there's only one format that scroll's associated tweensContainer can take, we create it manually. */ - tweensContainer = { - scroll: { - rootPropertyValue: false, - startValue: scrollPositionCurrent, - currentValue: scrollPositionCurrent, - endValue: scrollPositionEnd, - unitType: '', - easing: opts.easing, - scrollData: { - container: opts.container, - direction: scrollDirection, - alternateValue: scrollPositionCurrentAlternate - } - }, - element: element - }; - - if (Collide.debug) console.log("tweensContainer (scroll): ", tweensContainer.scroll, element); - - - } else if (action === 'start') { - - /**************************** - Start: Value Transferring - ****************************/ - - /* If this queue entry follows a previous Collide-initiated queue entry *and* if this entry was created - while the element was in the process of being animated by Collide, then this current call is safe to use - the end values from the prior call as its start values. Collide attempts to perform this value transfer - process whenever possible in order to avoid requerying the DOM. */ - /* If values aren't transferred from a prior call and start values were not forcefed by the user (more on this below), - then the DOM is queried for the element's current values as a last resort. */ - /* Note: Conversely, animation reversal (and looping) *always* perform inter-call value transfers; they never requery the DOM. */ - var lastTweensContainer; - - /* The per-element isAnimating flag is used to indicate whether it's safe (i.e. the data isn't stale) - to transfer over end values to use as start values. If it's set to true and there is a previous - Collide call to pull values from, do so. */ - eleData = data(element); - if (clearCache) { - eleData.tweensContainer = undefined; - } else if (eleData.tweensContainer && eleData.isAnimating === true) { - lastTweensContainer = eleData.tweensContainer; - } - - - /******************************** - Start: Tween Data Calculation - ********************************/ - - /* This function parses property data and defaults endValue, easing, and startValue as appropriate. */ - /* Property map values can either take the form of 1) a single value representing the end value, - or 2) an array in the form of [ endValue, [, easing] [, startValue] ]. - The optional third parameter is a forcefed startValue to be used instead of querying the DOM for - the element's current value. */ - function parsePropertyValue(valueData, skipResolvingEasing) { - var endValue = valueData[0]; - var easing = skipResolvingEasing ? valueData[1] : getEasing(valueData[1] || opts.easing, opts.duration); - var startValue = valueData[2]; - - /* Default to the call's easing if a per-property easing type was not defined. */ - if (!skipResolvingEasing) { - easing = easing || opts.easing; - } - - /* If functions were passed in as values, pass the function the current element as its context, - plus the element's index and the element set's size as arguments. Then, assign the returned value. */ - if (typeof endValue === 'function') { - endValue = endValue.call(element, elementIndex, elementsLength); - } - - if (typeof startValue === 'function') { - startValue = startValue.call(element, elementIndex, elementsLength); - } - - /* Allow startValue to be left as undefined to indicate to the ensuing code that its value was not forcefed. */ - return [ endValue || 0, easing, startValue ]; - - } // END: parsePropertyValue() - - - /* Cycle through each property in the map, looking for shorthand color properties (e.g. 'color' as opposed to 'colorRed'). Inject the corresponding - colorRed, colorGreen, and colorBlue RGB component tweens into the propertiesMap (which Collide understands) and remove the shorthand property. */ - for (var property in propertiesMap) { - - /* Find shorthand color properties that have been passed a hex string. */ - if (RegExp('^' + CSS.Lists.colors.join('$|^') + '$').test(property)) { - /* Parse the value data for each shorthand. */ - var valueData = parsePropertyValue(propertiesMap[property], true), - endValue = valueData[0], - easing = valueData[1], - startValue = valueData[2]; - - if (CSS.RegEx.isHex.test(endValue)) { - /* Convert the hex strings into their RGB component arrays. */ - var colorComponents = [ 'Red', 'Green', 'Blue' ], - endValueRGB = CSS.Values.hexToRgb(endValue), - startValueRGB = startValue ? CSS.Values.hexToRgb(startValue) : undefined; - - /* Inject the RGB component tweens into propertiesMap. */ - for (var i = 0; i < colorComponents.length; i++) { - var dataArray = [ endValueRGB[i] ]; - - if (easing) { - dataArray.push(easing); - } - - if (startValueRGB !== undefined) { - dataArray.push(startValueRGB[i]); - } - - propertiesMap[property + colorComponents[i]] = dataArray; - } - - /* Remove the intermediary shorthand property entry now that we've processed it. */ - delete propertiesMap[property]; - } - } - } - - - /* Create a tween out of each property, and append its associated data to tweensContainer. */ - for (var property in propertiesMap) { - - /********************************************* - parsePropertyValue(), Start Value Sourcing - *********************************************/ - - /* Parse out endValue, easing, and startValue from the property's data. */ - var valueData = parsePropertyValue(propertiesMap[property]), - endValue = valueData[0], - easing = valueData[1], - startValue = valueData[2]; - - /* Now that the original property name's format has been used for the parsePropertyValue() lookup above, - we force the property to its camelCase styling to normalize it for manipulation. */ - property = CSS.Names.camelCase(property); - - /* In case this property is a hook, there are circumstances where we will intend to work on the hook's root property and not the hooked subproperty. */ - var rootProperty = CSS.Hooks.getRoot(property), - rootPropertyValue = false; - - /* Other than for the dummy tween property, properties that are not supported by the browser (and do not have an associated normalization) will - inherently produce no style changes when set, so they are skipped in order to decrease animation tick overhead. - Property support is determined via prefixCheck(), which returns a false flag when no supported is detected. */ - /* Note: Since SVG elements have some of their properties directly applied as HTML attributes, - there is no way to check for their explicit browser support, and so we skip skip this check for them. */ - if (!eleData.isSVG && rootProperty !== 'tween' && CSS.Names.prefixCheck(rootProperty)[1] === false && CSS.Normalizations.registered[rootProperty] === undefined) { - if (Collide.debug) console.log('Skipping [' + rootProperty + '] due to a lack of browser support.'); - continue; - } - - /* If the display option is being set to a non-'none' (e.g. 'block') and opacity (filter on IE<=8) is being - animated to an endValue of non-zero, the user's intention is to fade in from invisible, thus we forcefeed opacity - a startValue of 0 if its startValue hasn't already been sourced by value transferring or prior forcefeeding. */ - if (((opts.display !== undefined && opts.display !== null && opts.display !== 'none') || (opts.visibility !== undefined && opts.visibility !== 'hidden')) && /opacity|filter/.test(property) && !startValue && endValue !== 0) { - startValue = 0; - } - - - /* If values have been transferred from the previous Collide call, extract the endValue and rootPropertyValue - for all of the current call's properties that were *also* animated in the previous call. */ - /* Note: Value transferring can optionally be disabled by the user via the _cacheValues option. */ - if (opts._cacheValues && lastTweensContainer && lastTweensContainer[property]) { - - if (startValue === undefined) { - startValue = lastTweensContainer[property].endValue + lastTweensContainer[property].unitType; - } - - /* The previous call's rootPropertyValue is extracted from the element's data cache since that's the - instance of rootPropertyValue that gets freshly updated by the tweening process, whereas the rootPropertyValue - attached to the incoming lastTweensContainer is equal to the root property's value prior to any tweening. */ - rootPropertyValue = eleData.rootPropertyValueCache[rootProperty]; - - /* If values were not transferred from a previous Collide call, query the DOM as needed. */ - } else { - /* Handle hooked properties. */ - if (CSS.Hooks.registered[property]) { - if (startValue === undefined) { - rootPropertyValue = CSS.getPropertyValue(element, rootProperty); /* GET */ - /* Note: The following getPropertyValue() call does not actually trigger a DOM query; - getPropertyValue() will extract the hook from rootPropertyValue. */ - startValue = CSS.getPropertyValue(element, property, rootPropertyValue); - - /* If startValue is already defined via forcefeeding, do not query the DOM for the root property's value; - just grab rootProperty's zero-value template from CSS.Hooks. This overwrites the element's actual - root property value (if one is set), but this is acceptable since the primary reason users forcefeed is - to avoid DOM queries, and thus we likewise avoid querying the DOM for the root property's value. */ - } else { - /* Grab this hook's zero-value template, e.g. '0px 0px 0px black'. */ - rootPropertyValue = CSS.Hooks.templates[rootProperty][1]; - } - - /* Handle non-hooked properties that haven't already been defined via forcefeeding. */ - } else if (startValue === undefined) { - startValue = CSS.getPropertyValue(element, property); /* GET */ - } - } - - - /********************************************** - parsePropertyValue(), Value Data Extraction - **********************************************/ - - var separatedValue, - endValueUnitType, - startValueUnitType, - operator = false; - - /* Separates a property value into its numeric value and its unit util. */ - function separateValue (property, value) { - var unitType, - numericValue; - - numericValue = (value || '0') - .toString() - .toLowerCase() - /* Match the unit type at the end of the value. */ - .replace(/[%A-z]+$/, function(match) { - /* Grab the unit util. */ - unitType = match; - - /* Strip the unit type off of value. */ - return ''; - }); - - /* If no unit type was supplied, assign one that is appropriate for this property (e.g. 'deg' for rotateZ or 'px' for width). */ - if (!unitType) { - unitType = CSS.Values.getUnitType(property); - } - - return [ numericValue, unitType ]; - } - - /* Separate startValue. */ - separatedValue = separateValue(property, startValue); - startValue = separatedValue[0]; - startValueUnitType = separatedValue[1]; - - /* Separate endValue, and extract a value operator (e.g. '+=', '-=') if one exists. */ - separatedValue = separateValue(property, endValue); - endValue = separatedValue[0].replace(/^([+-\/*])=/, function(match, subMatch) { - operator = subMatch; - - /* Strip the operator off of the value. */ - return ''; - }); - endValueUnitType = separatedValue[1]; - - /* Parse float values from endValue and startValue. Default to 0 if NaN is returned. */ - startValue = parseFloat(startValue) || 0; - endValue = parseFloat(endValue) || 0; - - - /*************************************** - parsePropertyValue, Property-Specific Value Conversion - ***************************************/ - - /* Custom support for properties that don't actually accept the % unit type, but where pollyfilling is trivial and relatively foolproof. */ - if (endValueUnitType === '%') { - /* A %-value fontSize/lineHeight is relative to the parent's fontSize (as opposed to the parent's dimensions), - which is identical to the em unit's behavior, so we piggyback off of that. */ - if (/^(fontSize|lineHeight)$/.test(property)) { - /* Convert % into an em decimal value. */ - endValue = endValue / 100; - endValueUnitType = 'em'; - - /* For scaleX and scaleY, convert the value into its decimal format and strip off the unit util. */ - } else if (/^scale/.test(property)) { - endValue = endValue / 100; - endValueUnitType = ''; - - /* For RGB components, take the defined percentage of 255 and strip off the unit util. */ - } else if (/(Red|Green|Blue)$/i.test(property)) { - endValue = (endValue / 100) * 255; - endValueUnitType = ''; - } - } - - - /**************************************** - parsePropertyValue(), Unit Conversion - *****************************************/ - - /* The * and / operators, which are not passed in with an associated unit, inherently use startValue's unit. Skip value and unit conversion. */ - if (/[\/*]/.test(operator)) { - endValueUnitType = startValueUnitType; - - /* If startValue and endValue differ in unit type, convert startValue into the same unit type as endValue so that if endValueUnitType - is a relative unit (%, em, rem), the values set during tweening will continue to be accurately relative even if the metrics they depend - on are dynamically changing during the course of the animation. Conversely, if we always normalized into px and used px for setting values, the px ratio - would become stale if the original unit being animated toward was relative and the underlying metrics change during the animation. */ - /* Since 0 is 0 in any unit type, no conversion is necessary when startValue is 0 -- we just start at 0 with endValueUnitutil. */ - } else if ((startValueUnitType !== endValueUnitType) && startValue !== 0) { - /* Unit conversion is also skipped when endValue is 0, but *startValueUnitType* must be used for tween values to remain accurate. */ - /* Note: Skipping unit conversion here means that if endValueUnitType was originally a relative unit, the animation won't relatively - match the underlying metrics if they change, but this is acceptable since we're animating toward invisibility instead of toward visibility, - which remains past the point of the animation's completion. */ - if (endValue === 0) { - endValueUnitType = startValueUnitType; - - } else { - /* By this point, we cannot avoid unit conversion (it's undesirable since it causes layout thrashing). - If we haven't already, we trigger calculateUnitRatios(), which runs once per element per call. */ - elementUnitConversionData = elementUnitConversionData || calculateUnitRatios(element, callUnitConversionData); - - /* The following RegEx matches CSS properties that have their % values measured relative to the x-axis. */ - /* Note: W3C spec mandates that all of margin and padding's properties (even top and bottom) are %-relative to the *width* of the parent element. */ - var axis = (/margin|padding|left|right|width|text|word|letter/i.test(property) || /X$/.test(property) || property === 'x') ? 'x' : 'y'; - - /* In order to avoid generating n^2 bespoke conversion functions, unit conversion is a two-step process: - 1) Convert startValue into pixels. 2) Convert this new pixel value into endValue's unit util. */ - switch (startValueUnitType) { - case '%': - /* Note: translateX and translateY are the only properties that are %-relative to an element's own dimensions -- not its parent's dimensions. - Collide does not include a special conversion process to account for this behavior. Therefore, animating translateX/Y from a % value - to a non-% value will produce an incorrect start value. Fortunately, this sort of cross-unit conversion is rarely done by users in practice. */ - startValue *= (axis === 'x' ? elementUnitConversionData.percentToPxWidth : elementUnitConversionData.percentToPxHeight); - break; - - case 'px': - /* px acts as our midpoint in the unit conversion process; do nothing. */ - break; - - default: - startValue *= elementUnitConversionData[startValueUnitType + 'ToPx']; - } - - /* Invert the px ratios to convert into to the target unit. */ - switch (endValueUnitType) { - case '%': - startValue *= 1 / (axis === 'x' ? elementUnitConversionData.percentToPxWidth : elementUnitConversionData.percentToPxHeight); - break; - - case 'px': - /* startValue is already in px, do nothing; we're done. */ - break; - - default: - startValue *= 1 / elementUnitConversionData[endValueUnitType + 'ToPx']; - } - } - } - - - /**************************************** - parsePropertyValue(), Relative Values - ****************************************/ - - /* Operator logic must be performed last since it requires unit-normalized start and end values. */ - /* Note: Relative *percent values* do not behave how most people think; while one would expect '+=50%' - to increase the property 1.5x its current value, it in fact increases the percent units in absolute terms: - 50 points is added on top of the current % value. */ - switch (operator) { - case '+': - endValue = startValue + endValue; - break; - - case '-': - endValue = startValue - endValue; - break; - - case '*': - endValue = startValue * endValue; - break; - - case '/': - endValue = startValue / endValue; - break; - } - - var currentValue = startValue; - - /********************************************* - parsePropertyValue(), tweensContainer Push - *********************************************/ - - /* Construct the per-property tween object, and push it to the element's tweensContainer. */ - tweensContainer[property] = { - rootPropertyValue: rootPropertyValue, - startValue: startValue, - currentValue: currentValue, - endValue: endValue, - unitType: endValueUnitType, - easing: easing - }; - - if (Collide.debug) console.log('tweensContainer (' + property + '): ' + JSON.stringify(tweensContainer[property]), element); - } - - /* Along with its property data, store a reference to the element itself onto tweensContainer. */ - tweensContainer.element = element; - - } // END: parsePropertyValue() - - - /***************** - Call Push - *****************/ - - /* Note: tweensContainer can be empty if all of the properties in this call's property map were skipped due to not - being supported by the browser. The element property is used for checking that the tweensContainer has been appended to. */ - if (tweensContainer.element) { - - /* The call array houses the tweensContainers for each element being animated in the current call. */ - call.push(tweensContainer); - - /* Store the tweensContainer and options if we're working on the default effects queue, so that they can be used by the reverse command. */ - if (opts.queue === '') { - eleData.tweensContainer = tweensContainer; - eleData.opts = opts; - } - } - - } // END: buildQueue - - - /* When the queue option is set to false, the call skips the element's queue and fires immediately. */ - if (opts.queue === false) { - /* Since this buildQueue call doesn't respect the element's existing queue (which is where a delay option would have been appended), - we manually inject the delay property here with an explicit setTimeout. */ - if (opts.delay) { - setTimeout(buildQueue, opts.delay); - } else { - buildQueue(); - } - - /* Otherwise, the call undergoes element queueing as normal. */ - } else { - Collide.queue(element, opts.queue, function(next, clearQueue) { - /* If the clearQueue flag was passed in by the stop command, resolve this call's promise. (Promises can only be resolved once, - so it's fine if this is repeatedly triggered for each element in the associated call.) */ - if (clearQueue === true) { - /* Do not continue with animation queueing. */ - resolve(elements); - return true; - } - - /* This flag indicates to the upcoming completeCall() function that this queue entry was initiated by Collide. - See completeCall() for further details. */ - Collide.collideQueueEntryFlag = true; - - buildQueue(next); - }); - } - -} diff --git a/ionic/collide/tick.js b/ionic/collide/tick.js deleted file mode 100644 index e89a1e95c9..0000000000 --- a/ionic/collide/tick.js +++ /dev/null @@ -1,301 +0,0 @@ -/* Forked from VelocityJS, MIT License: https://github.com/julianshapiro/velocity | Julian Shapiro http://twitter.com/shapiro */ - -import {dom} from 'ionic/util' -import {Collide} from './collide' -import {CSS} from './css' -import {completeCall} from './complete-call' - - -/************ - Tick -************/ - -export function startTick() { - if (!Collide.State.isTicking && Collide.State.calls && Collide.State.calls.length) { - Collide.State.isTicking = true; - tick(); - } -} - -/* Note: All calls to Collide are pushed to the Collide.State.calls array, which is fully iterated through upon each tick. */ -function tick(timestamp) { - /* An empty timestamp argument indicates that this is the first tick occurence since ticking was turned on. - We leverage this metadata to fully ignore the first tick pass since RAF's initial pass is fired whenever - the browser's next tick sync time occurs, which results in the first elements subjected to Collide - calls being animated out of sync with any elements animated immediately thereafter. In short, we ignore - the first RAF tick pass so that elements being immediately consecutively animated -- instead of simultaneously animated - by the same Collide call -- are properly batched into the same initial RAF tick and consequently remain in sync thereafter. */ - - if (timestamp) { - /* We ignore RAF's high resolution timestamp since it can be significantly offset when the browser is - under high stress; we opt for choppiness over allowing the browser to drop huge chunks of frames. */ - var timeCurrent = (new Date).getTime(); - - - /******************** - Call Iteration - ********************/ - - var callsLength = Collide.State.calls.length; - - /* To speed up iterating over this array, it is compacted (falsey items -- calls that have completed -- are removed) - when its length has ballooned to a point that can impact tick performance. This only becomes necessary when animation - has been continuous with many elements over a long period of time; whenever all active calls are completed, completeCall() clears Collide.State.calls. */ - if (callsLength > 10000) { - Collide.State.calls = compactSparseArray(Collide.State.calls); - } - - /* Iterate through each active call. */ - for (var i = 0; i < callsLength; i++) { - - /* When a Collide call is completed, its Collide.State.calls entry is set to false. Continue on to the next call. */ - if (!Collide.State.calls[i]) { - continue; - } - - - /************************ - Call-Wide Variables - ************************/ - - var callContainer = Collide.State.calls[i], - call = callContainer[0], - opts = callContainer[2], - timeStart = callContainer[3], - firstTick = !!timeStart, - tweenDummyValue = null; - - if (!call) { - continue; - } - - /* If timeStart is undefined, then this is the first time that this call has been processed by tick(). - We assign timeStart now so that its value is as close to the real animation start time as possible. - (Conversely, had timeStart been defined when this call was added to Collide.State.calls, the delay - between that time and now would cause the first few frames of the tween to be skipped since - percentComplete is calculated relative to timeStart.) */ - /* Further, subtract 16ms (the approximate resolution of RAF) from the current time value so that the - first tick iteration isn't wasted by animating at 0% tween completion, which would produce the - same style value as the element's current value. */ - if (!timeStart) { - timeStart = Collide.State.calls[i][3] = timeCurrent - 16; - } - - var percentComplete; - if (opts.percentComplete !== undefined) { - percentComplete = Math.max(Math.min(parseFloat(opts.percentComplete), 1), 0); - - } else { - /* The tween's completion percentage is relative to the tween's start time, not the tween's start value - (which would result in unpredictable tween durations since JavaScript's timers are not particularly accurate). - Accordingly, we ensure that percentComplete does not exceed 1. */ - percentComplete = Math.min((timeCurrent - timeStart) / opts.duration, 1); - } - - - /********************** - Element Iteration - **********************/ - - /* For every call, iterate through each of the elements in its set. */ - for (var j = 0, jj = call.length; j < jj; j++) { - var tweensContainer = call[j]; - var element = tweensContainer.element; - var eleData = Collide.data(element); - - /* Check to see if this element has been deleted midway through the animation by checking for the - continued existence of its data cache. If it's gone, skip animating this element. */ - if (!eleData) { - continue; - } - - if (eleData.startAddCls) { - for (var k = 0; k < eleData.startAddCls.length; k++) { - element.classList.add(eleData.startAddCls[k]); - } - eleData.startAddCls = null; - } - - if (eleData.startRmvCls) { - for (var k = 0; k < eleData.startRmvCls.length; k++) { - element.classList.remove(eleData.startRmvCls[k]); - } - eleData.startRmvCls = null; - } - - var transformPropertyExists = false; - - - /********************************** - Display & Visibility Toggling - **********************************/ - - /* If the display option is set to non-'none', set it upfront so that the element can become visible before tweening begins. - (Otherwise, display's 'none' value is set in completeCall() once the animation has completed.) */ - if (opts.display !== undefined && opts.display !== null && opts.display !== 'none') { - if (opts.display === 'flex') { - - for (var f = 0; f < flexValues.length; f++) { - CSS.setPropertyValue(element, 'display', flexValues[f]); - } - } - - CSS.setPropertyValue(element, 'display', opts.display); - } - - /* Same goes with the visibility option, but its 'none' equivalent is 'hidden'. */ - if (opts.visibility !== undefined && opts.visibility !== 'hidden') { - CSS.setPropertyValue(element, 'visibility', opts.visibility); - } - - - /************************ - Property Iteration - ************************/ - - /* For every element, iterate through each property. */ - for (var property in tweensContainer) { - - /* Note: In addition to property tween data, tweensContainer contains a reference to its associated element. */ - if (property !== 'element') { - var tween = tweensContainer[property]; - - var currentValue; - - /* Easing can either be a pre-genereated function or a string that references a pre-registered easing - on the Collide.Easings object. In either case, return the appropriate easing *function*. */ - var easing = (typeof tween.easing === 'string' ? Collide.Easings[tween.easing] : tween.easing); - - /****************************** - Current Value Calculation - ******************************/ - - /* If this is the last tick pass (if we've reached 100% completion for this tween), - ensure that currentValue is explicitly set to its target endValue so that it's not subjected to any rounding. */ - if (percentComplete === 1) { - currentValue = tween.endValue; - - /* Otherwise, calculate currentValue based on the current delta from startValue. */ - } else { - var tweenDelta = tween.endValue - tween.startValue; - currentValue = tween.startValue + (tweenDelta * easing(percentComplete, opts, tweenDelta)); - - /* If no value change is occurring, don't proceed with DOM updating. */ - if (!firstTick && (currentValue === tween.currentValue)) { - continue; - } - } - - tween.currentValue = currentValue; - - /* If we're tweening a fake 'tween' property in order to log transition values, update the one-per-call variable so that - it can be passed into the progress callback. */ - if (property === 'tween') { - tweenDummyValue = currentValue; - - } else { - - /****************** - Hooks: Part I - ******************/ - - /* For hooked properties, the newly-updated rootPropertyValueCache is cached onto the element so that it can be used - for subsequent hooks in this call that are associated with the same root property. If we didn't cache the updated - rootPropertyValue, each subsequent update to the root property in this tick pass would reset the previous hook's - updates to rootPropertyValue prior to injection. A nice performance byproduct of rootPropertyValue caching is that - subsequently chained animations using the same hookRoot but a different hook can use this cached rootPropertyValue. */ - if (CSS.Hooks.registered[property]) { - var hookRoot = CSS.Hooks.getRoot(property), - rootPropertyValueCache = eleData.rootPropertyValueCache[hookRoot]; - - if (rootPropertyValueCache) { - tween.rootPropertyValue = rootPropertyValueCache; - } - } - - - /***************** - DOM Update - *****************/ - - /* setPropertyValue() returns an array of the property name and property value post any normalization that may have been performed. */ - /* Note: To solve an IE<=8 positioning bug, the unit type is dropped when setting a property value of 0. */ - var adjustedSetData = CSS.setPropertyValue(element, /* SET */ - property, - tween.currentValue + (parseFloat(currentValue) === 0 ? '' : tween.unitType), - tween.rootPropertyValue, - tween.scrollData); - - - /******************* - Hooks: Part II - *******************/ - - /* Now that we have the hook's updated rootPropertyValue (the post-processed value provided by adjustedSetData), cache it onto the element. */ - if (CSS.Hooks.registered[property]) { - /* Since adjustedSetData contains normalized data ready for DOM updating, the rootPropertyValue needs to be re-extracted from its normalized form. ?? */ - if (CSS.Normalizations.registered[hookRoot]) { - eleData.rootPropertyValueCache[hookRoot] = CSS.Normalizations.registered[hookRoot]('extract', null, adjustedSetData[1]); - } else { - eleData.rootPropertyValueCache[hookRoot] = adjustedSetData[1]; - } - } - - /*************** - Transforms - ***************/ - - /* Flag whether a transform property is being animated so that flushTransformCache() can be triggered once this tick pass is complete. */ - if (adjustedSetData[0] === 'transform') { - transformPropertyExists = true; - } - - } - - } // END: if (property !== 'element') - - } // END: for (var property in tweensContainer) - - if (transformPropertyExists) { - CSS.flushTransformCache(element); - } - - } // END: for (var j = 0, jj = call.length; j < jj; j++) - - - /* The non-'none' display value is only applied to an element once -- when its associated call is first ticked through. - Accordingly, it's set to false so that it isn't re-processed by this call in the next tick. */ - if (opts.display !== undefined && opts.display !== 'none') { - Collide.State.calls[i][2].display = false; - } - if (opts.visibility !== undefined && opts.visibility !== 'hidden') { - Collide.State.calls[i][2].visibility = false; - } - - /* Pass the elements and the timing data (percentComplete, msRemaining, timeStart, tweenDummyValue) into the progress callback. */ - if (opts.progress) { - opts.progress.call(callContainer[1], - callContainer[1], - percentComplete, - Math.max(0, (timeStart + opts.duration) - timeCurrent), - timeStart, - tweenDummyValue); - } - - /* If this call has finished tweening, pass its index to completeCall() to handle call cleanup. */ - if (percentComplete === 1 || opts.percentComplete !== undefined) { - completeCall(i); - } - - } // END: for (var i = 0; i < callsLength; i++) - - } // END: if (timestamp) - - /* Note: completeCall() sets the isTicking flag to false when the last call on Collide.State.calls has completed. */ - if (Collide.State.isTicking) { - dom.raf(tick); - } - -}; - -const flexValues = [ '-webkit-box', '-moz-box', '-ms-flexbox', '-webkit-flex' ]; diff --git a/ionic/components/app/test/animations/index.js b/ionic/components/app/test/animations/index.js index 86cf0244fc..a9b43fa2e2 100644 --- a/ionic/components/app/test/animations/index.js +++ b/ionic/components/app/test/animations/index.js @@ -15,132 +15,45 @@ let scale = 0.6; }) class IonicApp { - fadeOut() { - console.debug('fadeOut'); + constructor() { + this.animation = new Animation(); - var animation = new Animation(); - - animation.duration(1000); - animation.easing('linear'); - - var row1 = new Animation(); - row1.elements( document.querySelectorAll('.square') ) - .to('opacity', opacity) - .to('translateX', translateX) - .to('translateY', translateX) - .to('rotateZ', rotateZ) - .to('scale', scale); - - animation.addChild(row1); + this.animation + .duration(1000) + .easing('ease-in-out'); - var row2 = new Animation(); - row2.elements( document.querySelectorAll('.square2') ); + var row1 = new Animation( document.querySelectorAll('.square') ); + row1 + .from('opacity', 1) + .to('opacity', 0) + .beforePlay.addClass('added-before-play') + .afterFinish.addClass('added-after-finish') - row2.to('opacity', opacity); - row2.to('translateX', '-100px'); - row2.to('translateY', '-100px'); - row2.to('rotateZ', '-180deg'); - row2.to('scale', 0.4); + var row2 = new Animation( document.querySelectorAll('.square2') ); + row2 + .to('transform', 'rotate(90deg) scale(0.5)') + .beforePlay.addClass('added-before-play') + .afterFinish.addClass('added-after-finish') - animation.addChild(row2); + this.animation.children(row1, row2); - let q = animation.start(); - - q.then(()=> { - console.log('fade out complete') - }); } - - fadeIn() { - console.debug('fadeIn'); - - var animation = new Animation(); - - animation.duration(1000); - animation.easing('linear'); - - - var row1 = new Animation(); - row1.elements( document.querySelectorAll('.square') ); - - row1.to('opacity', 1); - row1.to('translateX', 0); - row1.to('translateY', 0); - row1.to('rotateZ', 0); - row1.to('scale', 1); - - animation.addChild(row1); - - - var row2 = new Animation(); - row2.elements( document.querySelectorAll('.square2') ); - - row2.to('opacity', 1); - row2.to('translateX', 0); - row2.to('translateY', 0); - row2.to('rotateZ', 0); - row2.to('scale', 1); - - animation.addChild(row2); - - let q = animation.start(); - - q.then(()=> { - console.log('fade in complete') - }); + play() { + console.debug('play'); + this.animation.play();; } - stop() { - this.animation.stop(); + pause() { + console.debug('pause'); + + this.animation.pause(); } - percent(ev) { - let percentComplete = parseFloat(ev.srcElement.value) / 100; - - if (!this.percentAnimation) { - this.percentAnimation = new Animation(); - // this.percentAnimation.name = 'top'; - - var row1 = new Animation(); - row1.elements( document.querySelectorAll('.square') ) - .to('opacity', opacity) - .to('translateX', translateX) - .to('translateY', translateX) - .to('rotateZ', rotateZ) - .to('scale', scale); - - this.percentAnimation.addChild(row1); - - var row2 = new Animation(); - row2.elements( document.querySelectorAll('.square2') ); - - row2.to('opacity', opacity); - row2.to('translateX', '-100px'); - row2.to('translateY', '-100px'); - row2.to('rotateZ', '-180deg'); - row2.to('scale', 0.4); - - this.percentAnimation.addChild(row2); - - this.percentAnimation.ready(); - } - - this.percentAnimation.percent(percentComplete); - } - - velocityStart() { - var elements = document.querySelectorAll('.square'); - Velocity(elements, { - opacity: 0, - translateX: '100px', - rotateZ: '90deg' - }, 2000); - - // setTimeout(function() { - // Velocity(elements, "stop"); - // }, 1000); + progress(ev) { + let value = ev.srcElement.value; + this.animation.progress(value); } } diff --git a/ionic/components/app/test/animations/main.html b/ionic/components/app/test/animations/main.html index 626efee77d..10ddcbdf49 100644 --- a/ionic/components/app/test/animations/main.html +++ b/ionic/components/app/test/animations/main.html @@ -1,6 +1,7 @@ - Collide Tests + Animation Tests + @@ -21,20 +22,13 @@

- +

- - + +

- - - - - diff --git a/ionic/components/nav/nav-base.js b/ionic/components/nav/nav-base.js index 41672dc6a3..b24ed63c9d 100644 --- a/ionic/components/nav/nav-base.js +++ b/ionic/components/nav/nav-base.js @@ -124,7 +124,7 @@ export class NavBase { leavingItem.state = ACTIVELY_LEAVING_STATE; // start the transition - transAnimation.start().then(() => { + transAnimation.play().then(() => { // transition has completed, update each item's state enteringItem.state = ACTIVE_STATE; diff --git a/ionic/ionic.js b/ionic/ionic.js index 6f65d45af2..37d56a166d 100644 --- a/ionic/ionic.js +++ b/ionic/ionic.js @@ -41,7 +41,7 @@ export * from 'ionic/engine/engine' export * from 'ionic/engine/cordova/cordova' export * from 'ionic/engine/electron/electron' -export * from 'ionic/collide/animation' +export * from 'ionic/animations/animation' export * from 'ionic/transitions/transition' export * from 'ionic/transitions/none-transition' export * from 'ionic/transitions/ios-transition' diff --git a/ionic/transitions/ios-transition.js b/ionic/transitions/ios-transition.js index 2bb75cd7dd..ff335e904f 100644 --- a/ionic/transitions/ios-transition.js +++ b/ionic/transitions/ios-transition.js @@ -1,17 +1,21 @@ -import {Animation} from '../collide/animation'; -import {addEasing} from '../collide/easing'; +import {Animation} from '../animations/animation'; import {rafPromise} from '../util/dom' import {Transition} from './transition' const EASING_FN = [.36, .66, .04, 1]; const DURATION = 500; -const OFF_RIGHT = '100%'; -const OFF_LEFT = '-33%'; + +const OPACITY = 'opacity'; +const TRANSFORM = 'transform'; + +const CENTER = 'none'; +const OFF_RIGHT = 'translate3d(100%,0px,0px)'; +const OFF_LEFT = 'translate3d(-33%,0px,0px)'; const OFF_OPACITY = 0.8; -const TRANSLATE_X = 'translateX'; -const OPACITY = 'opacity'; +const SHOW_TOOLBAR_CSS = 'show-toolbar'; +const SHOW_NAV_ITEM_CSS = 'show-nav-item'; class IOSTransition extends Animation { @@ -21,7 +25,6 @@ class IOSTransition extends Animation { // global duration and easing for all child animations this.duration(DURATION); - this.easing('ios'); // get the entering and leaving items this.enteringItem = navCtrl.getStagedEnteringItem(); @@ -50,72 +53,73 @@ class IOSTransition extends Animation { // entering item moves to center // before starting, set enteringItem to display: block enteringContent - .addStartClass('show-nav-item') - .to(TRANSLATE_X, 0) + .beforePlay.addClass(SHOW_NAV_ITEM_CSS) + .to(TRANSFORM, CENTER) .to(OPACITY, 1); enteringTitle - .to(TRANSLATE_X, 0) + .to(TRANSFORM, CENTER) .to(OPACITY, 1); enteringToolbars - .addStartClass('show-toolbar'); + .beforePlay.addClass(SHOW_TOOLBAR_CSS); // leaving view moves off screen // when completed, set leavingItem to display: none leavingContent - .removeEndClass('show-nav-item') - .from(TRANSLATE_X, 0) + .afterFinish.removeClass(SHOW_NAV_ITEM_CSS) + .from(TRANSFORM, CENTER) .from(OPACITY, 1); leavingToolbars - .removeEndClass('show-toolbar'); + .afterFinish.removeClass(SHOW_TOOLBAR_CSS); leavingTitle - .from(TRANSLATE_X, 0) + .from(TRANSFORM, CENTER) .from(OPACITY, 1); // set properties depending on direction if (opts.direction === 'back') { // back direction enteringContent - .from(TRANSLATE_X, OFF_LEFT) + .from(TRANSFORM, OFF_LEFT) .from(OPACITY, OFF_OPACITY) .to(OPACITY, 1); enteringTitle - .from(TRANSLATE_X, OFF_LEFT) + .from(TRANSFORM, OFF_LEFT) .from(OPACITY, 0) .to(OPACITY, 1); leavingContent - .to(TRANSLATE_X, OFF_RIGHT) + .to(TRANSFORM, OFF_RIGHT) .to(OPACITY, 1); leavingTitle - .to(TRANSLATE_X, OFF_RIGHT) + .to(TRANSFORM, OFF_RIGHT) .to(OPACITY, 1); } else { // forward direction enteringContent - .from(TRANSLATE_X, OFF_RIGHT) + .from(TRANSFORM, OFF_RIGHT) .from(OPACITY, 1); enteringTitle - .from(TRANSLATE_X, OFF_RIGHT); + .from(TRANSFORM, OFF_RIGHT); leavingContent - .to(TRANSLATE_X, OFF_LEFT) + .to(TRANSFORM, OFF_LEFT) .to(OPACITY, OFF_OPACITY); leavingTitle - .to(TRANSLATE_X, OFF_LEFT) + .to(TRANSFORM, OFF_LEFT) .to(OPACITY, 0); } // set child animations - this.setChildren([enteringContent, enteringToolbars, enteringTitle, leavingContent, leavingToolbars, leavingTitle]); + this.children(enteringContent, enteringToolbars, enteringTitle, leavingContent, leavingToolbars, leavingTitle); + } stage() { @@ -124,6 +128,4 @@ class IOSTransition extends Animation { } -addEasing('ios', EASING_FN); - Transition.register('ios', IOSTransition); diff --git a/ionic/transitions/none-transition.js b/ionic/transitions/none-transition.js index 78ac4d9bdd..6d7a334ca8 100644 --- a/ionic/transitions/none-transition.js +++ b/ionic/transitions/none-transition.js @@ -1,6 +1,10 @@ import {Transition} from './transition' +const SHOW_TOOLBAR_CSS = 'show-toolbar'; +const SHOW_NAV_ITEM_CSS = 'show-nav-item'; + + class NoneTransition { constructor(navCtrl) { @@ -10,26 +14,24 @@ class NoneTransition { // show entering contet let enteringContent = enteringItem.getContent(); - enteringContent.classList.add('show-nav-item'); - enteringContent.style.transform = 'translateX(0%)'; + enteringContent.classList.add(SHOW_NAV_ITEM_CSS); // show entering headers let enteringToolbars = enteringItem.getToolbars(); for (let i = 0; i < enteringToolbars.length; i++) { - enteringToolbars[i].classList.add('show-toolbar'); - enteringToolbars[i].style.transform = 'translateX(0%)'; + enteringToolbars[i].classList.add(SHOW_TOOLBAR_CSS); } // hide the leaving item if (leavingItem) { let leavingContent = leavingItem.getContent(); if (leavingContent) { - leavingContent.classList.remove('show-nav-item'); + leavingContent.classList.remove(SHOW_NAV_ITEM_CSS); } let leavingToolbars = leavingItem.getToolbars(); for (let i = 0; i < leavingToolbars.length; i++) { - leavingToolbars[i].classList.remove('show-toolbar'); + leavingToolbars[i].classList.remove(SHOW_TOOLBAR_CSS); } } } @@ -39,7 +41,7 @@ class NoneTransition { return Promise.resolve(); } - start() { + play() { // immediately resolve return Promise.resolve(); } diff --git a/scripts/e2e/angular.template.html b/scripts/e2e/angular.template.html index b21b603540..b2b393bc75 100644 --- a/scripts/e2e/angular.template.html +++ b/scripts/e2e/angular.template.html @@ -11,7 +11,11 @@ Loading... + $SCRIPTS$ + + +