From 22c32f3ab58ec7dbccf7543f8c550a00092fc547 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Wed, 17 Feb 2016 23:02:31 -0600 Subject: [PATCH] fix(animations): remove inline styles when finished Allows for one animation to control multiple elements. Optionally removes inline styles when the animation finishes. Fixes checking for the will-change property. Does not always apply translateZ on ion-page. Closes #5130 --- ionic/animations/animation.ts | 324 +++++++++++++++++----------- ionic/components/app/structure.scss | 1 - ionic/config/modes.ts | 2 +- ionic/transitions/transition-ios.ts | 28 +-- ionic/transitions/transition-md.ts | 4 +- 5 files changed, 215 insertions(+), 144 deletions(-) diff --git a/ionic/animations/animation.ts b/ionic/animations/animation.ts index 79268caf995..3af99448656 100644 --- a/ionic/animations/animation.ts +++ b/ionic/animations/animation.ts @@ -8,15 +8,15 @@ import {assign, isDefined} from '../util/util'; export class Animation { private _parent: Animation; private _c: Array; - private _el: HTMLElement; - private _opts; - private _fx; + private _el: Array; + private _opts: AnimationOptions; + private _fx: {[key: string]: EffectProperty}; private _dur: number; private _easing: string; - private _bfSty; + private _bfSty: any; private _bfAdd: Array; private _bfRmv: Array; - private _afSty; + private _afSty: any; private _afAdd: Array; private _afRmv: Array; private _pFns: Array; @@ -25,13 +25,12 @@ export class Animation { private _wChg: boolean = false; private _rv: boolean; private _unregTrans: Function; - private _tmr; + private _tmr: any; public isPlaying: boolean; public hasTween: boolean; - public meta; - constructor(ele?, opts: AnimationOptions = {}) { + constructor(ele?: any, opts: AnimationOptions = {}) { this._reset(); this.element(ele); @@ -41,6 +40,7 @@ export class Animation { } _reset() { + this._el = []; this._c = []; this._fx = {}; @@ -58,28 +58,45 @@ export class Animation { this._clearAsync(); this.isPlaying = this.hasTween = this._rv = false; - this._el = this._easing = this._dur = null; + this._easing = this._dur = null; } - element(ele): Animation { + element(ele: any): Animation { + var i; + if (ele) { - if (ele.nativeElement) { - ele = ele.nativeElement + if (Array.isArray(ele)) { + for (i = 0; i < ele.length; i++) { + this._addEle(ele[i]); + } } else if (typeof ele === 'string') { ele = doc.querySelector(ele); - } - - if (ele && ele.nodeType === 1) { - this._el = ele; + for (i = 0; i < ele.length; i++) { + this._addEle(ele[i]); + } - // does this element suport will-change property? - this._wChg = ('opacity' in ele.style); + } else { + this._addEle(ele); } } + return this; } + private _addEle(ele: any) { + if (ele.nativeElement) { + ele = ele.nativeElement; + } + + if (ele.nodeType === 1) { + this._el.push(ele); + + // does this element suport will-change property? + this._wChg = (typeof ele.style.willChange !== 'undefined'); + } + } + parent(parentAnimation: Animation): Animation { this._parent = parentAnimation; return this; @@ -109,33 +126,48 @@ export class Animation { return this; } - from(prop: string, val): Animation { - return this._addProp('from', prop, val); + from(prop: string, val: any): Animation { + this._addProp('from', prop, val); + return this; } - to(prop: string, val): Animation { - return this._addProp('to', prop, val); + to(prop: string, val: any, clearProperyAfterTransition?: boolean): Animation { + var fx = this._addProp('to', prop, val); + + if (clearProperyAfterTransition) { + // if this effect is a transform then clear the transform effect + // otherwise just clear the actual property + this.after.clearStyles([ fx.trans ? CSS.transform : prop]); + } + + return this; } - fromTo(prop: string, fromVal, toVal): Animation { - return this.from(prop, fromVal).to(prop, toVal); + fromTo(prop: string, fromVal: any, toVal: any, clearProperyAfterTransition?: boolean): Animation { + return this.from(prop, fromVal).to(prop, toVal, clearProperyAfterTransition); } - private _addProp(state: string, prop: string, val: string): Animation { - if (!this._fx[prop]) { - this._fx[prop] = { - trans: (TRANSFORMS.indexOf(prop) > -1) - } + private _addProp(state: string, prop: string, val: any): EffectProperty { + var fxProp = this._fx[prop]; - if (this._fx[prop].trans) { - this._fx[prop].wc = 'transform'; + if (!fxProp) { + // first time we've see this EffectProperty + fxProp = this._fx[prop] = { + trans: (typeof TRANSFORMS[prop] !== 'undefined'), + wc: '' + }; + + // add the will-change property fo transforms or opacity + if (fxProp.trans) { + fxProp.wc = CSS.transform; } else if (prop === 'opacity') { - this._fx[prop].wc = prop; + fxProp.wc = prop; } } - var fx = this._fx[prop][state] = { + // add from/to EffectState to the EffectProperty + var fxState = fxProp[state] = { val: val, num: null, unit: '', @@ -146,19 +178,19 @@ export class Animation { let num = parseFloat(r[1]); if (!isNaN(num)) { - fx.num = num; + fxState.num = num; } - fx.unit = (r[0] != r[2] ? r[2] : ''); + fxState.unit = (r[0] != r[2] ? r[2] : ''); } else if (typeof val === 'number') { - fx.num = val; + fxState.num = val; } - return this; + return fxProp; } fadeIn(): Animation { - return this.fromTo('opacity', 0.001, 1); + return this.fromTo('opacity', 0.001, 1, true); } fadeOut(): Animation { @@ -178,6 +210,12 @@ export class Animation { setStyles: (styles): Animation => { this._bfSty = styles; return this; + }, + clearStyles: (propertyNames: Array) => { + for (var i = 0; i < propertyNames.length; i++) { + this._bfSty[propertyNames[i]] = ''; + } + return this; } } } @@ -192,9 +230,15 @@ export class Animation { this._afRmv.push(className); return this; }, - setStyles: (styles): Animation => { + setStyles: (styles: any): Animation => { this._afSty = styles; return this; + }, + clearStyles: (propertyNames: Array) => { + for (var i = 0; i < propertyNames.length; i++) { + this._afSty[propertyNames[i]] = ''; + } + return this; } } } @@ -255,7 +299,7 @@ export class Animation { // wait a few moments again to wait for the transition // info to take hold in the DOM - raf(function() { + rafFrames(2, function() { // browser had some time to render everything in place // and the transition duration/easing is set // now set the TO properties @@ -347,7 +391,7 @@ export class Animation { this._c[i]._progress(stepValue); } - if (this._el) { + if (this._el.length) { // flip the number if we're going in reverse if (this._rv) { stepValue = ((stepValue * -1) + 1); @@ -355,42 +399,43 @@ export class Animation { transforms = []; for (prop in this._fx) { - if (this._fx.hasOwnProperty(prop)) { - fx = this._fx[prop]; + fx = this._fx[prop]; - if (fx.from && fx.to) { + if (fx.from && fx.to) { - tweenEffect = (fx.from.num !== fx.to.num); - if (tweenEffect) { - this.hasTween = true; - } + tweenEffect = (fx.from.num !== fx.to.num); + if (tweenEffect) { + this.hasTween = true; + } - if (stepValue === 0) { - // FROM - val = fx.from.val; + if (stepValue === 0) { + // FROM + val = fx.from.val; - } else if (stepValue === 1) { - // TO - val = fx.to.val; + } else if (stepValue === 1) { + // TO + val = fx.to.val; - } else if (tweenEffect) { - // EVERYTHING IN BETWEEN - val = (((fx.to.num - fx.from.num) * stepValue) + fx.from.num) + fx.to.unit; + } else if (tweenEffect) { + // EVERYTHING IN BETWEEN + val = (((fx.to.num - fx.from.num) * stepValue) + fx.from.num) + fx.to.unit; - } else { - val = null; - } + } else { + val = null; + } - if (val !== null) { - if (fx.trans) { - transforms.push(prop + '(' + val + ')'); + if (val !== null) { + if (fx.trans) { + transforms.push(prop + '(' + val + ')'); - } else { - this._el.style[prop] = val; + } else { + for (i = 0; i < this._el.length; i++) { + this._el[i].style[prop] = val; } } } } + } // place all transforms on the same property @@ -401,27 +446,33 @@ export class Animation { transforms.push('translateZ(0px)'); } - this._el.style[CSS.transform] = transforms.join(' '); + for (i = 0; i < this._el.length; i++) { + this._el[i].style[CSS.transform] = transforms.join(' '); + } } } } - _setTrans(duration: number, forcedLinearEasing) { + _setTrans(duration: number, forcedLinearEasing: boolean) { + var i, easing; + // set the TRANSITION properties inline on the element - for (var i = 0; i < this._c.length; i++) { + for (i = 0; i < this._c.length; i++) { this._c[i]._setTrans(duration, forcedLinearEasing); } - if (this._el && Object.keys(this._fx).length) { - // all parent/child animations should have the same duration - this._el.style[CSS.transitionDuration] = duration + 'ms'; + if (Object.keys(this._fx).length) { + for (i = 0; i < this._el.length; i++) { + // all parent/child animations should have the same duration + this._el[i].style[CSS.transitionDuration] = duration + 'ms'; - // each animation can have a different easing - let easing = (forcedLinearEasing ? 'linear' : this.getEasing()); - if (easing) { - this._el.style[CSS.transitionTimingFn] = easing; + // each animation can have a different easing + easing = (forcedLinearEasing ? 'linear' : this.getEasing()); + if (easing) { + this._el[i].style[CSS.transitionTimingFn] = easing; + } } } } @@ -434,20 +485,18 @@ export class Animation { } if (this._wChg) { + wc = []; if (addWillChange) { - wc = []; for (prop in this._fx) { - if (this._fx.hasOwnProperty(prop)) { - if (this._fx[prop].wc !== '') { - wc.push(this._fx[prop].wc); - } + if (this._fx[prop].wc !== '') { + wc.push(this._fx[prop].wc); } } - this._el.style['willChange'] = wc.join(','); + } - } else { - this._el.style['willChange'] = ''; + for (i = 0; i < this._el.length; i++) { + this._el[i].style['willChange'] = wc.join(','); } } } @@ -455,28 +504,30 @@ export class Animation { _before() { // before the RENDER_DELAY // before the animations have started - var i, prop; + var i, j, prop, ele; // stage all of the child animations for (i = 0; i < this._c.length; i++) { this._c[i]._before(); } - if (!this._rv && this._el) { - // css classes to add before the animation - for (i = 0; i < this._bfAdd.length; i++) { - this._el.classList.add(this._bfAdd[i]); - } + if (!this._rv) { + for (i = 0; i < this._el.length; i++) { + ele = this._el[i]; - // css classes to remove before the animation - for (i = 0; i < this._bfRmv.length; i++) { - this._el.classList.remove(this._bfRmv[i]); - } + // css classes to add before the animation + for (j = 0; j < this._bfAdd.length; j++) { + ele.classList.add(this._bfAdd[j]); + } - // inline styles to add before the animation - for (prop in this._bfSty) { - if (this._bfSty.hasOwnProperty(prop)) { - this._el.style[prop] = this._bfSty[prop]; + // css classes to remove before the animation + for (j = 0; j < this._bfRmv.length; j++) { + ele.classList.remove(this._bfRmv[j]); + } + + // inline styles to add before the animation + for (prop in this._bfSty) { + ele.style[prop] = this._bfSty[prop]; } } } @@ -484,55 +535,53 @@ export class Animation { _after() { // after the animations have finished - var i, prop; + var i, j, prop, ele; for (i = 0; i < this._c.length; i++) { this._c[i]._after(); } - if (this._el) { + for (i = 0; i < this._el.length; i++) { + ele = this._el[i]; + // remove the transition duration/easing - this._el.style[CSS.transitionDuration] = ''; - this._el.style[CSS.transitionTimingFn] = ''; + ele.style[CSS.transitionDuration] = ''; + ele.style[CSS.transitionTimingFn] = ''; if (this._rv) { // finished in reverse direction // css classes that were added before the animation should be removed - for (i = 0; i < this._bfAdd.length; i++) { - this._el.classList.remove(this._bfAdd[i]); + for (j = 0; j < this._bfAdd.length; j++) { + ele.classList.remove(this._bfAdd[j]); } // css classes that were removed before the animation should be added - for (i = 0; i < this._bfRmv.length; i++) { - this._el.classList.add(this._bfRmv[i]); + for (j = 0; j < this._bfRmv.length; j++) { + ele.classList.add(this._bfRmv[j]); } // inline styles that were added before the animation should be removed for (prop in this._bfSty) { - if (this._bfSty.hasOwnProperty(prop)) { - this._el.style[prop] = ''; - } + ele.style[prop] = ''; } } else { // finished in forward direction // css classes to add after the animation - for (i = 0; i < this._afAdd.length; i++) { - this._el.classList.add(this._afAdd[i]); + for (j = 0; j < this._afAdd.length; j++) { + ele.classList.add(this._afAdd[j]); } // css classes to remove after the animation - for (i = 0; i < this._afRmv.length; i++) { - this._el.classList.remove(this._afRmv[i]); + for (j = 0; j < this._afRmv.length; j++) { + ele.classList.remove(this._afRmv[j]); } // inline styles to add after the animation for (prop in this._afSty) { - if (this._afSty.hasOwnProperty(prop)) { - this._el.style[prop] = this._afSty[prop]; - } + ele.style[prop] = this._afSty[prop]; } } } @@ -560,6 +609,7 @@ export class Animation { if (this._rv) { stepValue = ((stepValue * -1) + 1); } + this._progress(stepValue); } @@ -592,12 +642,12 @@ export class Animation { } } - onPlay(callback: Function) { + onPlay(callback: Function): Animation { this._pFns.push(callback); return this; } - onFinish(callback: Function, onceTimeCallback: boolean = false, clearOnFinishCallacks: boolean = false) { + onFinish(callback: Function, onceTimeCallback: boolean = false, clearOnFinishCallacks: boolean = false): Animation { if (clearOnFinishCallacks) { this._fFns = []; this._fOnceFns = []; @@ -624,7 +674,7 @@ export class Animation { this._fOnceFns = []; } - reverse(shouldReverse: boolean = true) { + reverse(shouldReverse: boolean = true): Animation { for (var i = 0; i < this._c.length; i++) { this._c[i].reverse(shouldReverse); } @@ -633,12 +683,17 @@ export class Animation { } destroy(removeElement?: boolean) { - for (var i = 0; i < this._c.length; i++) { + var i, ele; + + for (i = 0; i < this._c.length; i++) { this._c[i].destroy(removeElement); } - if (removeElement && this._el) { - this._el.parentNode && this._el.parentNode.removeChild(this._el); + if (removeElement) { + for (i = 0; i < this._el.length; i++) { + ele = this._el[i]; + ele.parentNode && ele.parentNode.removeChild(ele); + } } this._reset(); @@ -646,7 +701,7 @@ export class Animation { _transEl(): HTMLElement { // get the lowest level element that has an Animation - var targetEl, i; + var i, targetEl; for (i = 0; i < this._c.length; i++) { targetEl = this._c[i]._transEl(); @@ -655,7 +710,7 @@ export class Animation { } } - return (this.hasTween ? this._el : null); + return (this.hasTween && this._el.length ? this._el[0] : null); } /* @@ -688,9 +743,26 @@ export interface PlayOptions { stepValue?: number; } +interface EffectProperty { + trans: boolean; + wc: string; + to?: EffectState; + from?: EffectState; +} + +interface EffectState { + val: any; + num: number; + unit: string; +} + const doc: any = document; -const TRANSFORMS = [ - 'translateX', 'translateY', 'translateZ', 'scale', 'scaleX', 'scaleY', 'scaleZ', - 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'skewX', 'skewY', 'perspective']; + +const TRANSFORMS = { + 'translateX':1, 'translateY':1, 'translateZ':1, + 'scale':1, 'scaleX':1, 'scaleY':1, 'scaleZ':1, + 'rotate':1, 'rotateX':1, 'rotateY':1, 'rotateZ':1, + 'skewX':1, 'skewY':1, 'perspective':1 +}; let AnimationRegistry = {}; diff --git a/ionic/components/app/structure.scss b/ionic/components/app/structure.scss index 43f07a7184a..0bb88cfc2aa 100644 --- a/ionic/components/app/structure.scss +++ b/ionic/components/app/structure.scss @@ -113,7 +113,6 @@ ion-page { left: 0; width: 100%; height: 100%; - transform: translateZ(0); &.show-page { display: flex; diff --git a/ionic/config/modes.ts b/ionic/config/modes.ts index 2fb4b42e860..8cba1a0ccaf 100644 --- a/ionic/config/modes.ts +++ b/ionic/config/modes.ts @@ -50,7 +50,7 @@ Config.setModeConfig('md', { modalLeave: 'modal-md-slide-out', pageTransition: 'md-transition', - pageTransitionDelay: 120, + pageTransitionDelay: 96, tabbarHighlight: true, tabbarPlacement: 'top', diff --git a/ionic/transitions/transition-ios.ts b/ionic/transitions/transition-ios.ts index 84b28726d7e..d98b4838ac2 100644 --- a/ionic/transitions/transition-ios.ts +++ b/ionic/transitions/transition-ios.ts @@ -39,14 +39,14 @@ class IOSTransition extends Transition { if (backDirection) { // entering content, back direction enteringContent - .fromTo(TRANSLATEX, OFF_LEFT, CENTER) - .fromTo(OPACITY, OFF_OPACITY, 1); + .fromTo(TRANSLATEX, OFF_LEFT, CENTER, true) + .fromTo(OPACITY, OFF_OPACITY, 1, true); } else { // entering content, forward direction enteringContent - .fromTo(TRANSLATEX, OFF_RIGHT, CENTER) - .fromTo(OPACITY, 1, 1); + .before.clearStyles([OPACITY]) + .fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true); } if (enteringHasNavbar) { @@ -71,7 +71,7 @@ class IOSTransition extends Transition { // set properties depending on direction if (backDirection) { // entering navbar, back direction - enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER); + enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true); if (enteringView.enableBack()) { // back direction, entering page has a back button @@ -82,21 +82,21 @@ class IOSTransition extends Transition { } else { // entering navbar, forward direction - enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER); + enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true); if (leavingHasNavbar) { // entering navbar, forward direction, and there's a leaving navbar // should just fade in, no sliding enteringNavbarBg - .fromTo(TRANSLATEX, CENTER, CENTER) + .before.clearStyles([TRANSLATEX]) .fadeIn(); } else { // entering navbar, forward direction, and there's no leaving navbar // should just slide in, no fading in enteringNavbarBg - .fromTo(TRANSLATEX, OFF_RIGHT, CENTER) - .fromTo(OPACITY, 1, 1); + .before.clearStyles([OPACITY]) + .fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true); } @@ -125,8 +125,8 @@ class IOSTransition extends Transition { if (backDirection) { // leaving content, back direction leavingContent - .fromTo(TRANSLATEX, CENTER, '100%') - .fromTo(OPACITY, 1, 1); + .before.clearStyles([OPACITY]) + .fromTo(TRANSLATEX, CENTER, '100%'); } else { // leaving content, forward direction @@ -163,15 +163,15 @@ class IOSTransition extends Transition { // leaving navbar, back direction, and there's an entering navbar // should just fade out, no sliding leavingNavbarBg - .fromTo(TRANSLATEX, CENTER, CENTER) + .before.clearStyles([TRANSLATEX]) .fadeOut(); } else { // leaving navbar, back direction, and there's no entering navbar // should just slide out, no fading out leavingNavbarBg - .fromTo(TRANSLATEX, CENTER, '100%') - .fromTo(OPACITY, 1, 1); + .before.clearStyles([OPACITY]) + .fromTo(TRANSLATEX, CENTER, '100%'); } let leavingBackBtnText = new Animation(leavingView.backBtnTextRef()); diff --git a/ionic/transitions/transition-md.ts b/ionic/transitions/transition-md.ts index e3909649ea0..633b1bcd5b5 100644 --- a/ionic/transitions/transition-md.ts +++ b/ionic/transitions/transition-md.ts @@ -27,12 +27,12 @@ class MDTransition extends Transition { if (backDirection) { this.duration(opts.duration || 200).easing('cubic-bezier(0.47,0,0.745,0.715)'); - enteringPage.fromTo(TRANSLATEY, CENTER, CENTER); + enteringPage.before.clearStyles([TRANSLATEY]); } else { this.duration(opts.duration || 280).easing('cubic-bezier(0.36,0.66,0.04,1)'); enteringPage - .fromTo(TRANSLATEY, OFF_BOTTOM, CENTER) + .fromTo(TRANSLATEY, OFF_BOTTOM, CENTER, true) .fadeIn(); }