From c44f6b6f2e771910734141d03a806d707b00aac1 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Wed, 16 Nov 2016 19:48:35 +0100 Subject: [PATCH] perf(animation): improves _progress() hot function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - progress() is the function where more time is spent during any swipe gesture - replace iterating over the _fx properties, using an array instead - optimize pointerCoord(), profiler showed it’s one of the most called functions --- src/animations/animation.ts | 145 ++++++++++-------- src/components/menu/menu.scss | 2 +- .../refresher/test/refresher.spec.ts | 3 +- src/gestures/drag-gesture.ts | 3 +- src/util/dom.ts | 16 +- 5 files changed, 93 insertions(+), 76 deletions(-) diff --git a/src/animations/animation.ts b/src/animations/animation.ts index 2003585b677..068e73b0259 100644 --- a/src/animations/animation.ts +++ b/src/animations/animation.ts @@ -10,7 +10,7 @@ export class Animation { private _cL: number; private _e: HTMLElement[]; private _eL: number; - private _fx: {[key: string]: EffectProperty}; + private _fx: EffectProperty[]; private _dur: number = null; private _es: string = null; private _bfSty: { [property: string]: any; }; @@ -159,26 +159,39 @@ export class Animation { * @private * NO DOM */ + + private _getProp(name: string): EffectProperty { + if (this._fx) { + return this._fx.find((prop) => prop.name === name); + } else { + this._fx = []; + } + return null; + } + private _addProp(state: string, prop: string, val: any): EffectProperty { - this._fx = this._fx || {}; - let fxProp = this._fx[prop]; + let fxProp = this._getProp(prop); if (!fxProp) { // first time we've see this EffectProperty - fxProp = this._fx[prop] = { - trans: (TRANSFORMS[prop] === 1) - }; + var shouldTrans = (TRANSFORMS[prop] === 1); + fxProp = { + name: prop, + trans: shouldTrans, - // add the will-change property for transforms or opacity - fxProp.wc = (fxProp.trans ? CSS.transform : prop); + // add the will-change property for transforms or opacity + wc: (shouldTrans ? CSS.transform : prop) + }; + this._fx.push(fxProp); } // add from/to EffectState to the EffectProperty - let fxState: EffectState = (fxProp)[state] = { + let fxState: EffectState = { val: val, num: null, unit: '', }; + fxProp[state] = fxState; if (typeof val === 'string' && val.indexOf(' ') < 0) { let r = val.match(CSS_VALUE_REGEX); @@ -594,72 +607,72 @@ export class Animation { */ _progress(stepValue: number) { // bread 'n butter - var val: any; + let val: any; + let effects = this._fx; + let nuElements = this._eL; - if (this._fx && this._eL) { - // flip the number if we're going in reverse - if (this._rv) { - stepValue = ((stepValue * -1) + 1); - } - var transforms: string[] = []; - var effects = this._fx; - var elements = this._e; - for (var prop in effects) { - var fx = effects[prop]; - - if (fx.from && fx.to) { - var fromNum = fx.from.num; - var toNum = fx.to.num; - var tweenEffect = (fromNum !== toNum); - if (tweenEffect) { - this._twn = true; - } + if (!effects || !nuElements) { + return; + } - if (stepValue === 0) { - // FROM - val = fx.from.val; + // flip the number if we're going in reverse + if (this._rv) { + stepValue = ((stepValue * -1) + 1); + } + var i, j; + var finalTransform: string = ''; + var elements = this._e; + for (i = 0; i < effects.length; i++) { + var fx = effects[i]; + + if (fx.from && fx.to) { + var fromNum = fx.from.num; + var toNum = fx.to.num; + var tweenEffect = (fromNum !== toNum); + if (tweenEffect) { + this._twn = true; + } - } else if (stepValue === 1) { - // TO - val = fx.to.val; + if (stepValue === 0) { + // FROM + val = fx.from.val; - } else if (tweenEffect) { - // EVERYTHING IN BETWEEN - val = (((toNum - fromNum) * stepValue) + fromNum) + fx.to.unit; + } else if (stepValue === 1) { + // TO + val = fx.to.val; - } else { - val = null; - } + } else if (tweenEffect) { + // EVERYTHING IN BETWEEN + val = (((toNum - fromNum) * stepValue) + fromNum) + fx.to.unit; + } - if (val !== null) { - if (fx.trans) { - transforms.push(prop + '(' + val + ')'); + if (val !== null) { + var prop = fx.name; + if (fx.trans) { + finalTransform += prop + '(' + val + ') '; - } else { - for (var i = 0; i < this._eL; i++) { - // ******** DOM WRITE **************** - elements[i].style[prop] = val; - } + } else { + for (j = 0; j < nuElements; j++) { + // ******** DOM WRITE **************** + elements[j].style[prop] = val; } } } } + } - // place all transforms on the same property - if (transforms.length) { - if (!this._rv && stepValue !== 1 || this._rv && stepValue !== 0) { - transforms.push('translateZ(0px)'); - } + // place all transforms on the same property + if (finalTransform.length) { + if (!this._rv && stepValue !== 1 || this._rv && stepValue !== 0) { + finalTransform += 'translateZ(0px)'; + } - var transformString = transforms.join(' '); - var cssTransform = CSS.transform; - for (var i = 0; i < this._eL; i++) { - // ******** DOM WRITE **************** - elements[i].style[cssTransform] = transformString; - } + var cssTransform = CSS.transform; + for (i = 0; i < elements.length; i++) { + // ******** DOM WRITE **************** + elements[i].style[cssTransform] = finalTransform; } } - } /** @@ -900,15 +913,16 @@ export class Animation { */ _willChg(addWillChange: boolean) { let wc: string[]; - - if (addWillChange) { + var effects = this._fx; + if (addWillChange && effects) { wc = []; - for (var prop in this._fx) { - if (this._fx[prop].wc === 'webkitTransform') { + for (var i = 0; i < effects.length; i++) { + var propWC = effects[i].wc; + if (propWC === 'webkitTransform') { wc.push('transform', '-webkit-transform'); } else { - wc.push(this._fx[prop].wc); + wc.push(propWC); } } } @@ -1164,6 +1178,7 @@ export interface PlayOptions { } export interface EffectProperty { + name: string; trans: boolean; wc?: string; to?: EffectState; diff --git a/src/components/menu/menu.scss b/src/components/menu/menu.scss index ad5d3c2eba3..2dcce6bfc17 100644 --- a/src/components/menu/menu.scss +++ b/src/components/menu/menu.scss @@ -51,7 +51,7 @@ ion-menu ion-backdrop { z-index: -1; display: none; - opacity: .1; + opacity: .01; } .menu-content { diff --git a/src/components/refresher/test/refresher.spec.ts b/src/components/refresher/test/refresher.spec.ts index b530200c62a..a51e6e6e546 100644 --- a/src/components/refresher/test/refresher.spec.ts +++ b/src/components/refresher/test/refresher.spec.ts @@ -232,7 +232,8 @@ describe('Refresher', () => { function touchEv(y: number) { return { type: 'mockTouch', - touches: [{clientY: y}], + pageX: 0, + pageY: y, preventDefault: function(){} }; } diff --git a/src/gestures/drag-gesture.ts b/src/gestures/drag-gesture.ts index 1d666d6d6f7..04923c93946 100644 --- a/src/gestures/drag-gesture.ts +++ b/src/gestures/drag-gesture.ts @@ -117,7 +117,7 @@ export class PanGesture { let coord = pointerCoord(ev); if (this.detector.detect(coord)) { - if (this.detector.pan() !== 0 && this.canCapture(ev) && + if (this.detector.pan() !== 0 && (!this.gestute || this.gestute.capture())) { this.onDragStart(ev); this.captured = true; @@ -156,7 +156,6 @@ export class PanGesture { // Implemented in a subclass canStart(ev: any): boolean { return true; } - canCapture(ev: any): boolean { return true; } onDragStart(ev: any) { } onDragMove(ev: any) { } onDragEnd(ev: any) { } diff --git a/src/util/dom.ts b/src/util/dom.ts index a5ff02f07f6..d0e0a383d20 100644 --- a/src/util/dom.ts +++ b/src/util/dom.ts @@ -195,16 +195,18 @@ export function windowLoad(callback?: Function) { export function pointerCoord(ev: any): PointerCoordinates { // get coordinates for either a mouse click // or a touch depending on the given event - let c = { x: 0, y: 0 }; if (ev) { - const touches = ev.touches && ev.touches.length ? ev.touches : [ev]; - const e = (ev.changedTouches && ev.changedTouches[0]) || touches[0]; - if (e) { - c.x = e.clientX || e.pageX || 0; - c.y = e.clientY || e.pageY || 0; + var changedTouches = ev.changedTouches; + if (changedTouches && changedTouches.length > 0) { + var touch = changedTouches[0]; + return { x: touch.clientX, y: touch.clientY }; + } + var pageX = ev.pageX; + if (pageX !== undefined) { + return { x: pageX, y: ev.pageY }; } } - return c; + return { x: 0, y: 0 }; } export function hasPointerMoved(threshold: number, startCoord: PointerCoordinates, endCoord: PointerCoordinates) {