perf(animation): improves _progress() hot function

- 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
This commit is contained in:
Manu Mtz.-Almeida
2016-11-16 19:48:35 +01:00
parent 70f8a8e5eb
commit c44f6b6f2e
5 changed files with 95 additions and 78 deletions

View File

@ -10,7 +10,7 @@ export class Animation {
private _cL: number; private _cL: number;
private _e: HTMLElement[]; private _e: HTMLElement[];
private _eL: number; private _eL: number;
private _fx: {[key: string]: EffectProperty}; private _fx: EffectProperty[];
private _dur: number = null; private _dur: number = null;
private _es: string = null; private _es: string = null;
private _bfSty: { [property: string]: any; }; private _bfSty: { [property: string]: any; };
@ -159,26 +159,39 @@ export class Animation {
* @private * @private
* NO DOM * 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 { private _addProp(state: string, prop: string, val: any): EffectProperty {
this._fx = this._fx || {}; let fxProp = this._getProp(prop);
let fxProp = this._fx[prop];
if (!fxProp) { if (!fxProp) {
// first time we've see this EffectProperty // first time we've see this EffectProperty
fxProp = this._fx[prop] = { var shouldTrans = (TRANSFORMS[prop] === 1);
trans: (TRANSFORMS[prop] === 1) fxProp = {
}; name: prop,
trans: shouldTrans,
// add the will-change property for transforms or opacity // add the will-change property for transforms or opacity
fxProp.wc = (fxProp.trans ? CSS.transform : prop); wc: (shouldTrans ? CSS.transform : prop)
};
this._fx.push(fxProp);
} }
// add from/to EffectState to the EffectProperty // add from/to EffectState to the EffectProperty
let fxState: EffectState = (<any>fxProp)[state] = { let fxState: EffectState = {
val: val, val: val,
num: null, num: null,
unit: '', unit: '',
}; };
fxProp[state] = fxState;
if (typeof val === 'string' && val.indexOf(' ') < 0) { if (typeof val === 'string' && val.indexOf(' ') < 0) {
let r = val.match(CSS_VALUE_REGEX); let r = val.match(CSS_VALUE_REGEX);
@ -594,72 +607,72 @@ export class Animation {
*/ */
_progress(stepValue: number) { _progress(stepValue: number) {
// bread 'n butter // bread 'n butter
var val: any; let val: any;
let effects = this._fx;
let nuElements = this._eL;
if (this._fx && this._eL) { if (!effects || !nuElements) {
// flip the number if we're going in reverse return;
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) { // flip the number if we're going in reverse
var fromNum = fx.from.num; if (this._rv) {
var toNum = fx.to.num; stepValue = ((stepValue * -1) + 1);
var tweenEffect = (fromNum !== toNum); }
if (tweenEffect) { var i, j;
this._twn = true; var finalTransform: string = '';
} var elements = this._e;
for (i = 0; i < effects.length; i++) {
var fx = effects[i];
if (stepValue === 0) { if (fx.from && fx.to) {
// FROM var fromNum = fx.from.num;
val = fx.from.val; var toNum = fx.to.num;
var tweenEffect = (fromNum !== toNum);
if (tweenEffect) {
this._twn = true;
}
} else if (stepValue === 1) { if (stepValue === 0) {
// TO // FROM
val = fx.to.val; val = fx.from.val;
} else if (tweenEffect) { } else if (stepValue === 1) {
// EVERYTHING IN BETWEEN // TO
val = (((toNum - fromNum) * stepValue) + fromNum) + fx.to.unit; val = fx.to.val;
} else if (tweenEffect) {
// EVERYTHING IN BETWEEN
val = (((toNum - fromNum) * stepValue) + fromNum) + fx.to.unit;
}
if (val !== null) {
var prop = fx.name;
if (fx.trans) {
finalTransform += prop + '(' + val + ') ';
} else { } else {
val = null; for (j = 0; j < nuElements; j++) {
} // ******** DOM WRITE ****************
elements[j].style[prop] = val;
if (val !== null) {
if (fx.trans) {
transforms.push(prop + '(' + val + ')');
} else {
for (var i = 0; i < this._eL; i++) {
// ******** DOM WRITE ****************
elements[i].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)');
}
var transformString = transforms.join(' ');
var cssTransform = CSS.transform;
for (var i = 0; i < this._eL; i++) {
// ******** DOM WRITE ****************
elements[i].style[cssTransform] = transformString;
}
}
} }
// place all transforms on the same property
if (finalTransform.length) {
if (!this._rv && stepValue !== 1 || this._rv && stepValue !== 0) {
finalTransform += 'translateZ(0px)';
}
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) { _willChg(addWillChange: boolean) {
let wc: string[]; let wc: string[];
var effects = this._fx;
if (addWillChange) { if (addWillChange && effects) {
wc = []; wc = [];
for (var prop in this._fx) { for (var i = 0; i < effects.length; i++) {
if (this._fx[prop].wc === 'webkitTransform') { var propWC = effects[i].wc;
if (propWC === 'webkitTransform') {
wc.push('transform', '-webkit-transform'); wc.push('transform', '-webkit-transform');
} else { } else {
wc.push(this._fx[prop].wc); wc.push(propWC);
} }
} }
} }
@ -1164,6 +1178,7 @@ export interface PlayOptions {
} }
export interface EffectProperty { export interface EffectProperty {
name: string;
trans: boolean; trans: boolean;
wc?: string; wc?: string;
to?: EffectState; to?: EffectState;

View File

@ -51,7 +51,7 @@ ion-menu ion-backdrop {
z-index: -1; z-index: -1;
display: none; display: none;
opacity: .1; opacity: .01;
} }
.menu-content { .menu-content {

View File

@ -232,7 +232,8 @@ describe('Refresher', () => {
function touchEv(y: number) { function touchEv(y: number) {
return { return {
type: 'mockTouch', type: 'mockTouch',
touches: [{clientY: y}], pageX: 0,
pageY: y,
preventDefault: function(){} preventDefault: function(){}
}; };
} }

View File

@ -117,7 +117,7 @@ export class PanGesture {
let coord = pointerCoord(ev); let coord = pointerCoord(ev);
if (this.detector.detect(coord)) { if (this.detector.detect(coord)) {
if (this.detector.pan() !== 0 && this.canCapture(ev) && if (this.detector.pan() !== 0 &&
(!this.gestute || this.gestute.capture())) { (!this.gestute || this.gestute.capture())) {
this.onDragStart(ev); this.onDragStart(ev);
this.captured = true; this.captured = true;
@ -156,7 +156,6 @@ export class PanGesture {
// Implemented in a subclass // Implemented in a subclass
canStart(ev: any): boolean { return true; } canStart(ev: any): boolean { return true; }
canCapture(ev: any): boolean { return true; }
onDragStart(ev: any) { } onDragStart(ev: any) { }
onDragMove(ev: any) { } onDragMove(ev: any) { }
onDragEnd(ev: any) { } onDragEnd(ev: any) { }

View File

@ -195,16 +195,18 @@ export function windowLoad(callback?: Function) {
export function pointerCoord(ev: any): PointerCoordinates { export function pointerCoord(ev: any): PointerCoordinates {
// get coordinates for either a mouse click // get coordinates for either a mouse click
// or a touch depending on the given event // or a touch depending on the given event
let c = { x: 0, y: 0 };
if (ev) { if (ev) {
const touches = ev.touches && ev.touches.length ? ev.touches : [ev]; var changedTouches = ev.changedTouches;
const e = (ev.changedTouches && ev.changedTouches[0]) || touches[0]; if (changedTouches && changedTouches.length > 0) {
if (e) { var touch = changedTouches[0];
c.x = e.clientX || e.pageX || 0; return { x: touch.clientX, y: touch.clientY };
c.y = e.clientY || e.pageY || 0; }
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) { export function hasPointerMoved(threshold: number, startCoord: PointerCoordinates, endCoord: PointerCoordinates) {