From 14a3ea2ef8ebdf20387e1c0edfb9e05d9df758f2 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Tue, 20 Sep 2016 19:52:21 +0200 Subject: [PATCH] perf(ripple): md ripple effect update to not affect layout --- src/components/button/button.md.scss | 3 + src/components/tap-click/ripple.ts | 153 ++++++++++++++------------- 2 files changed, 82 insertions(+), 74 deletions(-) diff --git a/src/components/button/button.md.scss b/src/components/button/button.md.scss index f70587f12f..d80d54131c 100644 --- a/src/components/button/button.md.scss +++ b/src/components/button/button.md.scss @@ -423,6 +423,8 @@ $button-md-fab-box-shadow-activated: 0 5px 15px 0 rgba(0, 0, 0, .4), .button-effect { position: absolute; + top: 0; + left: 0; z-index: 0; display: none; @@ -431,6 +433,7 @@ $button-md-fab-box-shadow-activated: 0 5px 15px 0 rgba(0, 0, 0, .4), background-color: $button-md-ripple-background-color; opacity: .2; + transform-origin: center center; transition-timing-function: ease-in-out; pointer-events: none; diff --git a/src/components/tap-click/ripple.ts b/src/components/tap-click/ripple.ts index 79749764a6..9a798cfa9e 100644 --- a/src/components/tap-click/ripple.ts +++ b/src/components/tap-click/ripple.ts @@ -1,6 +1,6 @@ import { Activator } from './activator'; import { App } from '../app/app'; -import { PointerCoordinates, CSS, hasPointerMoved, nativeRaf, pointerCoord, rafFrames } from '../../util/dom'; +import { PointerCoordinates, CSS, hasPointerMoved, pointerCoord, rafFrames } from '../../util/dom'; import { Config } from '../../config/config'; @@ -14,102 +14,107 @@ export class RippleActivator extends Activator { } downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) { - let self = this; - if (self.disableActivated(ev)) { + if (this.disableActivated(ev)) { return; } // queue to have this element activated - self._queue.push(activatableEle); + this._queue.push(activatableEle); - nativeRaf(function() { - for (var i = 0; i < self._queue.length; i++) { - var queuedEle = self._queue[i]; - if (queuedEle && queuedEle.parentNode) { - self._active.push(queuedEle); + for (var i = 0; i < this._queue.length; i++) { + var queuedEle = this._queue[i]; + if (queuedEle && queuedEle.parentNode) { + this._active.push(queuedEle); - // DOM WRITE - queuedEle.classList.add(self._css); + // DOM WRITE + queuedEle.classList.add(this._css); - var j = queuedEle.childElementCount; - while (j--) { - var rippleEle: any = queuedEle.children[j]; - if (rippleEle.classList.contains('button-effect')) { - // DOM WRITE - rippleEle.style.left = '-9999px'; - rippleEle.style.opacity = ''; - rippleEle.style[CSS.transform] = 'scale(0.001) translateZ(0px)'; - rippleEle.style[CSS.transition] = ''; - - // DOM READ - var clientRect = activatableEle.getBoundingClientRect(); - rippleEle.$top = clientRect.top; - rippleEle.$left = clientRect.left; - rippleEle.$width = clientRect.width; - rippleEle.$height = clientRect.height; - break; - } + var j = queuedEle.childElementCount; + while (j--) { + var rippleEle: any = queuedEle.children[j]; + if (rippleEle.classList.contains('button-effect')) { + // DOM READ + var clientRect = activatableEle.getBoundingClientRect(); + rippleEle.$top = clientRect.top; + rippleEle.$left = clientRect.left; + rippleEle.$width = clientRect.width; + rippleEle.$height = clientRect.height; + break; } } } - self._queue = []; - }); + } + this._queue = []; } upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) { - if (!hasPointerMoved(6, startCoord, pointerCoord(ev))) { - let i = activatableEle.childElementCount; - - while (i--) { - var rippleEle: any = activatableEle.children[i]; - if (rippleEle.classList.contains('button-effect')) { - var clientPointerX = (startCoord.x - rippleEle.$left); - var clientPointerY = (startCoord.y - rippleEle.$top); - - var x = Math.max(Math.abs(rippleEle.$width - clientPointerX), clientPointerX) * 2; - var y = Math.max(Math.abs(rippleEle.$height - clientPointerY), clientPointerY) * 2; - var diameter = Math.min(Math.max(Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), 64), 240); - - if (activatableEle.hasAttribute('ion-item')) { - diameter = Math.min(diameter, 140); - } - - var radius = Math.sqrt(rippleEle.$width + rippleEle.$height); - - var scaleTransitionDuration = Math.max(1600 * Math.sqrt(radius / TOUCH_DOWN_ACCEL) + 0.5, 260); - var opacityTransitionDuration = scaleTransitionDuration * 0.7; - var opacityTransitionDelay = scaleTransitionDuration - opacityTransitionDuration; - - // DOM WRITE - rippleEle.style.width = rippleEle.style.height = diameter + 'px'; - rippleEle.style.marginTop = rippleEle.style.marginLeft = -(diameter / 2) + 'px'; - rippleEle.style.left = clientPointerX + 'px'; - rippleEle.style.top = clientPointerY + 'px'; - rippleEle.style.opacity = '0'; - rippleEle.style[CSS.transform] = 'scale(1) translateZ(0px)'; - rippleEle.style[CSS.transition] = 'transform ' + - scaleTransitionDuration + - 'ms,opacity ' + - opacityTransitionDuration + - 'ms ' + - opacityTransitionDelay + 'ms'; - } + if (hasPointerMoved(6, startCoord, pointerCoord(ev))) { + return; + } + let i = activatableEle.childElementCount; + while (i--) { + var rippleEle: any = activatableEle.children[i]; + if (rippleEle.classList.contains('button-effect')) { + this.startRippleEffect(rippleEle, activatableEle, startCoord); + break; } } super.upAction(ev, activatableEle, startCoord); } + startRippleEffect(rippleEle: any, activatableEle: HTMLElement, startCoord: PointerCoordinates) { + let clientPointerX = (startCoord.x - rippleEle.$left); + let clientPointerY = (startCoord.y - rippleEle.$top); + + let x = Math.max(Math.abs(rippleEle.$width - clientPointerX), clientPointerX) * 2; + let y = Math.max(Math.abs(rippleEle.$height - clientPointerY), clientPointerY) * 2; + let diameter = Math.min(Math.max(Math.hypot(x, y), 64), 240); + + if (activatableEle.hasAttribute('ion-item')) { + diameter = Math.min(diameter, 140); + } + + clientPointerX -= diameter / 2; + clientPointerY -= diameter / 2; + + clientPointerX = Math.round(clientPointerX); + clientPointerY = Math.round(clientPointerY); + diameter = Math.round(diameter); + + // Reset ripple + rippleEle.style.opacity = ''; + rippleEle.style[CSS.transform] = `translate3d(${clientPointerX}px, ${clientPointerY}px, 0px) scale(0.001)`; + rippleEle.style[CSS.transition] = ''; + + // Start ripple animation + let radius = Math.sqrt(rippleEle.$width + rippleEle.$height); + let scaleTransitionDuration = Math.max(1600 * Math.sqrt(radius / TOUCH_DOWN_ACCEL) + 0.5, 260); + let opacityTransitionDuration = Math.round(scaleTransitionDuration * 0.7); + let opacityTransitionDelay = Math.round(scaleTransitionDuration - opacityTransitionDuration); + scaleTransitionDuration = Math.round(scaleTransitionDuration); + + let transform = `translate3d(${clientPointerX}px, ${clientPointerY}px, 0px) scale(1)`; + let transition = `transform ${scaleTransitionDuration}ms,opacity ${opacityTransitionDuration}ms ${opacityTransitionDelay}ms`; + + rafFrames(2, () => { + // DOM WRITE + rippleEle.style.width = rippleEle.style.height = diameter + 'px'; + rippleEle.style.opacity = '0'; + rippleEle.style[CSS.transform] = transform; + rippleEle.style[CSS.transition] = transition; + }); + } + deactivate() { // remove the active class from all active elements - let self = this; - self._queue = []; + this._queue = []; - rafFrames(2, function() { - for (var i = 0; i < self._active.length; i++) { - self._active[i].classList.remove(self._css); + rafFrames(2, () => { + for (var i = 0; i < this._active.length; i++) { + this._active[i].classList.remove(this._css); } - self._active = []; + this._active = []; }); }