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 _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);
wc: (shouldTrans ? CSS.transform : prop)
};
this._fx.push(fxProp);
}
// add from/to EffectState to the EffectProperty
let fxState: EffectState = (<any>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,18 +607,23 @@ export class Animation {
*/
_progress(stepValue: number) {
// bread 'n butter
var val: any;
let val: any;
let effects = this._fx;
let nuElements = this._eL;
if (!effects || !nuElements) {
return;
}
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 i, j;
var finalTransform: string = '';
var elements = this._e;
for (var prop in effects) {
var fx = effects[prop];
for (i = 0; i < effects.length; i++) {
var fx = effects[i];
if (fx.from && fx.to) {
var fromNum = fx.from.num;
@ -626,19 +644,17 @@ export class Animation {
} else if (tweenEffect) {
// EVERYTHING IN BETWEEN
val = (((toNum - fromNum) * stepValue) + fromNum) + fx.to.unit;
} else {
val = null;
}
if (val !== null) {
var prop = fx.name;
if (fx.trans) {
transforms.push(prop + '(' + val + ')');
finalTransform += prop + '(' + val + ') ';
} else {
for (var i = 0; i < this._eL; i++) {
for (j = 0; j < nuElements; j++) {
// ******** DOM WRITE ****************
elements[i].style[prop] = val;
elements[j].style[prop] = val;
}
}
}
@ -646,22 +662,19 @@ export class Animation {
}
// place all transforms on the same property
if (transforms.length) {
if (finalTransform.length) {
if (!this._rv && stepValue !== 1 || this._rv && stepValue !== 0) {
transforms.push('translateZ(0px)');
finalTransform += 'translateZ(0px)';
}
var transformString = transforms.join(' ');
var cssTransform = CSS.transform;
for (var i = 0; i < this._eL; i++) {
for (i = 0; i < elements.length; i++) {
// ******** DOM WRITE ****************
elements[i].style[cssTransform] = transformString;
elements[i].style[cssTransform] = finalTransform;
}
}
}
}
/**
* @private
* DOM WRITE
@ -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;

View File

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

View File

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

View File

@ -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) { }

View File

@ -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) {