From e0f18a5de2b4d0a90d10d9c8dc71f47b2d54c36d Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Mon, 9 Nov 2015 13:55:25 -0600 Subject: [PATCH] perf(ripple): improve ripple perf --- ionic/animations/animation.ts | 34 ++++++++++++++++----- ionic/components/tap-click/ripple.ts | 40 ++++++++++++++----------- ionic/components/tap-click/tap-click.ts | 30 ++++++++++++------- ionic/platform/registry.ts | 4 ++- 4 files changed, 71 insertions(+), 37 deletions(-) diff --git a/ionic/animations/animation.ts b/ionic/animations/animation.ts index bad3ecdc60..680425f17d 100644 --- a/ionic/animations/animation.ts +++ b/ionic/animations/animation.ts @@ -27,7 +27,7 @@ export class Animation { constructor(ele, opts={}) { this.reset(); this._opts = extend({ - renderDelay: 36 + renderDelay: 16 }, opts); this.elements(ele); @@ -238,15 +238,31 @@ export class Animation { // stage all animations and child animations at their starting point self.stage(); - // synchronously call all onPlay()'s before play() - self._onPlay(); + let resolve; + let promise = new Promise(res => { resolve = res; }); + + function kickoff() { + // synchronously call all onPlay()'s before play() + self._onPlay(); - return new Promise(resolve => { beginPlay().then(() => { self._onFinish(); resolve(); }); - }); + } + + if (self._duration > 16) { + // begin each animation when everything is rendered in their starting point + // give the browser some time to render everything in place before starting + setTimeout(kickoff, this._opts.renderDelay); + + } else { + // no need to render everything in there place before animating in + // just kick it off immediately to render them in their "to" locations + kickoff(); + } + + return promise; } // this is a child animation, it is told exactly when to @@ -586,11 +602,13 @@ class Animate { // lock in where the element will stop at // if the playbackRate is negative then it needs to return // to its "from" effects - inlineStyle(self.ele, self.rate < 0 ? self.fromEffect : self.toEffect); + if (self.ani) { + inlineStyle(self.ele, self.rate < 0 ? self.fromEffect : self.toEffect); - self.ani = null; + self.ani = self.ani.onfinish = null; - done && done(); + done && done(); + } }; } diff --git a/ionic/components/tap-click/ripple.ts b/ionic/components/tap-click/ripple.ts index 162437998e..72f9d261a1 100644 --- a/ionic/components/tap-click/ripple.ts +++ b/ionic/components/tap-click/ripple.ts @@ -65,14 +65,15 @@ export class RippleActivator extends Activator { upAction(forceFadeOut) { this.deactivate(); - let ripple; - for (let rippleId in this.ripples) { + let rippleId, ripple; + for (rippleId in this.ripples) { ripple = this.ripples[rippleId]; if (!ripple.fade || forceFadeOut) { // ripple has not been let up yet - // speed up the rate if the animation is still going - setTimeout(() => { + clearTimeout(ripple.fadeStart); + ripple.fadeStart = setTimeout(() => { + // speed up the rate if the animation is still going ripple.expand && ripple.expand.playbackRate(EXPAND_OUT_PLAYBACK_RATE); ripple.fade = new Animation(ripple.ele); ripple.fade @@ -87,7 +88,7 @@ export class RippleActivator extends Activator { }) .play(); - }, 16); + }); } } @@ -95,15 +96,15 @@ export class RippleActivator extends Activator { } next(forceComplete) { - let ripple, rippleEle; - for (let rippleId in this.ripples) { + let rippleId, ripple; + for (rippleId in this.ripples) { ripple = this.ripples[rippleId]; - if ((ripple.expanded && ripple.faded && ripple.ele) || - forceComplete || parseInt(rippleId) + 5000 < Date.now()) { + if ((ripple.expanded && ripple.faded && ripple.ele) || forceComplete) { // finished expanding and the user has lifted the pointer + ripple.remove = true; raf(() => { - this.remove(rippleId); + this.remove(); }); } } @@ -114,14 +115,17 @@ export class RippleActivator extends Activator { this.next(true); } - remove(rippleId) { - let ripple = this.ripples[rippleId]; - if (ripple) { - ripple.expand && ripple.expand.dispose(); - ripple.fade && ripple.fade.dispose(); - removeElement(ripple.ele); - ripple.ele = ripple.expand = ripple.fade = null; - delete this.ripples[rippleId]; + remove() { + let rippleId, ripple; + for (rippleId in this.ripples) { + ripple = this.ripples[rippleId]; + if (ripple.remove || parseInt(rippleId, 10) + 4000 < Date.now()) { + ripple.expand && ripple.expand.dispose(); + ripple.fade && ripple.fade.dispose(); + removeElement(ripple.ele); + ripple.ele = ripple.expand = ripple.fade = null; + delete this.ripples[rippleId]; + } } } diff --git a/ionic/components/tap-click/tap-click.ts b/ionic/components/tap-click/tap-click.ts index 940d525658..4044ebbb2f 100644 --- a/ionic/components/tap-click/tap-click.ts +++ b/ionic/components/tap-click/tap-click.ts @@ -10,7 +10,7 @@ let lastActivated = 0; let disableNativeClickTime = 0; let disableNativeClickLimit = 1000; let activator = null; -let isEnabled = false; +let isTapPolyfill = false; let app = null; let config = null; let win = null; @@ -24,13 +24,15 @@ export function initTapClick(windowInstance, documentInstance, appInstance, conf config = configInstance; activator = (config.get('mdRipple') ? new RippleActivator(app, config) : new Activator(app, config)); - isEnabled = (config.get('tapPolyfill') !== false); + isTapPolyfill = (config.get('tapPolyfill') === true); addListener('click', click, true); - addListener('touchstart', touchStart); - addListener('touchend', touchEnd); - addListener('touchcancel', touchCancel); + if (isTapPolyfill) { + addListener('touchstart', touchStart); + addListener('touchend', touchEnd); + addListener('touchcancel', touchCancel); + } addListener('mousedown', mouseDown, true); addListener('mouseup', mouseUp, true); @@ -45,7 +47,7 @@ function touchStart(ev) { function touchEnd(ev) { touchAction(); - if (isEnabled && startCoord && app.isEnabled()) { + if (startCoord && app.isEnabled()) { let endCoord = pointerCoord(ev); if (!hasPointerMoved(pointerTolerance, startCoord, endCoord)) { @@ -111,8 +113,8 @@ function pointerStart(ev) { } function pointerEnd(ev) { - activator.upAction(); moveListeners(false); + activator.upAction(); } function pointerMove(ev) { @@ -131,16 +133,22 @@ function pointerCancel(ev) { } function moveListeners(shouldAdd) { - removeListener('touchmove', pointerMove); + if (isTapPolyfill) { + removeListener('touchmove', pointerMove); + } removeListener('mousemove', pointerMove); if (shouldAdd) { - addListener('touchmove', pointerMove); + if (isTapPolyfill) { + addListener('touchmove', pointerMove); + } addListener('mousemove', pointerMove); } } function setDisableNativeClick() { - disableNativeClickTime = Date.now() + disableNativeClickLimit; + if (isTapPolyfill) { + disableNativeClickTime = Date.now() + disableNativeClickLimit; + } } function isDisabledNativeClick() { @@ -161,6 +169,8 @@ function click(ev) { console.debug('click prevent', preventReason); ev.preventDefault(); ev.stopPropagation(); + } else { + activator.upAction(); } } diff --git a/ionic/platform/registry.ts b/ionic/platform/registry.ts index e212fae69c..4f9b1b2845 100644 --- a/ionic/platform/registry.ts +++ b/ionic/platform/registry.ts @@ -74,10 +74,12 @@ Platform.register({ scrollAssist: function(p) { return /iphone|ipad|ipod/i.test(p.navigatorPlatform()); }, + tapPolyfill: function(p) { + return /iphone|ipad|ipod/i.test(p.navigatorPlatform()); + }, keyboardHeight: 290, hoverCSS: false, swipeBackEnabled: function(p) { - return true; // TODO: remove me! Force it to always work for iOS mode for now return /iphone|ipad|ipod/i.test(p.navigatorPlatform()); }, swipeBackThreshold: 40,