perf(ripple): md ripple effect update to not affect layout

This commit is contained in:
Manu Mtz.-Almeida
2016-09-20 19:52:21 +02:00
committed by Adam Bradley
parent 156223edba
commit 14a3ea2ef8
2 changed files with 82 additions and 74 deletions

View File

@ -423,6 +423,8 @@ $button-md-fab-box-shadow-activated: 0 5px 15px 0 rgba(0, 0, 0, .4),
.button-effect { .button-effect {
position: absolute; position: absolute;
top: 0;
left: 0;
z-index: 0; z-index: 0;
display: none; 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; background-color: $button-md-ripple-background-color;
opacity: .2; opacity: .2;
transform-origin: center center;
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
pointer-events: none; pointer-events: none;

View File

@ -1,6 +1,6 @@
import { Activator } from './activator'; import { Activator } from './activator';
import { App } from '../app/app'; 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'; import { Config } from '../../config/config';
@ -14,102 +14,107 @@ export class RippleActivator extends Activator {
} }
downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) { downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
let self = this; if (this.disableActivated(ev)) {
if (self.disableActivated(ev)) {
return; return;
} }
// queue to have this element activated // queue to have this element activated
self._queue.push(activatableEle); this._queue.push(activatableEle);
nativeRaf(function() { for (var i = 0; i < this._queue.length; i++) {
for (var i = 0; i < self._queue.length; i++) { var queuedEle = this._queue[i];
var queuedEle = self._queue[i]; if (queuedEle && queuedEle.parentNode) {
if (queuedEle && queuedEle.parentNode) { this._active.push(queuedEle);
self._active.push(queuedEle);
// DOM WRITE // DOM WRITE
queuedEle.classList.add(self._css); queuedEle.classList.add(this._css);
var j = queuedEle.childElementCount; var j = queuedEle.childElementCount;
while (j--) { while (j--) {
var rippleEle: any = queuedEle.children[j]; var rippleEle: any = queuedEle.children[j];
if (rippleEle.classList.contains('button-effect')) { if (rippleEle.classList.contains('button-effect')) {
// DOM WRITE // DOM READ
rippleEle.style.left = '-9999px'; var clientRect = activatableEle.getBoundingClientRect();
rippleEle.style.opacity = ''; rippleEle.$top = clientRect.top;
rippleEle.style[CSS.transform] = 'scale(0.001) translateZ(0px)'; rippleEle.$left = clientRect.left;
rippleEle.style[CSS.transition] = ''; rippleEle.$width = clientRect.width;
rippleEle.$height = clientRect.height;
// DOM READ break;
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) { upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
if (!hasPointerMoved(6, startCoord, pointerCoord(ev))) { if (hasPointerMoved(6, startCoord, pointerCoord(ev))) {
let i = activatableEle.childElementCount; return;
}
while (i--) { let i = activatableEle.childElementCount;
var rippleEle: any = activatableEle.children[i]; while (i--) {
if (rippleEle.classList.contains('button-effect')) { var rippleEle: any = activatableEle.children[i];
var clientPointerX = (startCoord.x - rippleEle.$left); if (rippleEle.classList.contains('button-effect')) {
var clientPointerY = (startCoord.y - rippleEle.$top); this.startRippleEffect(rippleEle, activatableEle, startCoord);
break;
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';
}
} }
} }
super.upAction(ev, activatableEle, startCoord); 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() { deactivate() {
// remove the active class from all active elements // remove the active class from all active elements
let self = this; this._queue = [];
self._queue = [];
rafFrames(2, function() { rafFrames(2, () => {
for (var i = 0; i < self._active.length; i++) { for (var i = 0; i < this._active.length; i++) {
self._active[i].classList.remove(self._css); this._active[i].classList.remove(this._css);
} }
self._active = []; this._active = [];
}); });
} }