Skip to content

Commit 14a3ea2

Browse files
manucorporatadamdbradley
authored andcommitted
perf(ripple): md ripple effect update to not affect layout
1 parent 156223e commit 14a3ea2

File tree

2 files changed

+83
-75
lines changed

2 files changed

+83
-75
lines changed

src/components/button/button.md.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,8 @@ $button-md-fab-box-shadow-activated: 0 5px 15px 0 rgba(0, 0, 0, .4),
423423

424424
.button-effect {
425425
position: absolute;
426+
top: 0;
427+
left: 0;
426428
z-index: 0;
427429
display: none;
428430

@@ -431,6 +433,7 @@ $button-md-fab-box-shadow-activated: 0 5px 15px 0 rgba(0, 0, 0, .4),
431433
background-color: $button-md-ripple-background-color;
432434
opacity: .2;
433435

436+
transform-origin: center center;
434437
transition-timing-function: ease-in-out;
435438

436439
pointer-events: none;

src/components/tap-click/ripple.ts

Lines changed: 80 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Activator } from './activator';
22
import { App } from '../app/app';
3-
import { PointerCoordinates, CSS, hasPointerMoved, nativeRaf, pointerCoord, rafFrames } from '../../util/dom';
3+
import { PointerCoordinates, CSS, hasPointerMoved, pointerCoord, rafFrames } from '../../util/dom';
44
import { Config } from '../../config/config';
55

66

@@ -14,102 +14,107 @@ export class RippleActivator extends Activator {
1414
}
1515

1616
downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
17-
let self = this;
18-
if (self.disableActivated(ev)) {
17+
if (this.disableActivated(ev)) {
1918
return;
2019
}
2120

2221
// queue to have this element activated
23-
self._queue.push(activatableEle);
24-
25-
nativeRaf(function() {
26-
for (var i = 0; i < self._queue.length; i++) {
27-
var queuedEle = self._queue[i];
28-
if (queuedEle && queuedEle.parentNode) {
29-
self._active.push(queuedEle);
30-
31-
// DOM WRITE
32-
queuedEle.classList.add(self._css);
33-
34-
var j = queuedEle.childElementCount;
35-
while (j--) {
36-
var rippleEle: any = queuedEle.children[j];
37-
if (rippleEle.classList.contains('button-effect')) {
38-
// DOM WRITE
39-
rippleEle.style.left = '-9999px';
40-
rippleEle.style.opacity = '';
41-
rippleEle.style[CSS.transform] = 'scale(0.001) translateZ(0px)';
42-
rippleEle.style[CSS.transition] = '';
43-
44-
// DOM READ
45-
var clientRect = activatableEle.getBoundingClientRect();
46-
rippleEle.$top = clientRect.top;
47-
rippleEle.$left = clientRect.left;
48-
rippleEle.$width = clientRect.width;
49-
rippleEle.$height = clientRect.height;
50-
break;
51-
}
22+
this._queue.push(activatableEle);
23+
24+
for (var i = 0; i < this._queue.length; i++) {
25+
var queuedEle = this._queue[i];
26+
if (queuedEle && queuedEle.parentNode) {
27+
this._active.push(queuedEle);
28+
29+
// DOM WRITE
30+
queuedEle.classList.add(this._css);
31+
32+
var j = queuedEle.childElementCount;
33+
while (j--) {
34+
var rippleEle: any = queuedEle.children[j];
35+
if (rippleEle.classList.contains('button-effect')) {
36+
// DOM READ
37+
var clientRect = activatableEle.getBoundingClientRect();
38+
rippleEle.$top = clientRect.top;
39+
rippleEle.$left = clientRect.left;
40+
rippleEle.$width = clientRect.width;
41+
rippleEle.$height = clientRect.height;
42+
break;
5243
}
5344
}
5445
}
55-
self._queue = [];
56-
});
46+
}
47+
this._queue = [];
5748
}
5849

5950
upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
60-
if (!hasPointerMoved(6, startCoord, pointerCoord(ev))) {
61-
let i = activatableEle.childElementCount;
51+
if (hasPointerMoved(6, startCoord, pointerCoord(ev))) {
52+
return;
53+
}
54+
let i = activatableEle.childElementCount;
55+
while (i--) {
56+
var rippleEle: any = activatableEle.children[i];
57+
if (rippleEle.classList.contains('button-effect')) {
58+
this.startRippleEffect(rippleEle, activatableEle, startCoord);
59+
break;
60+
}
61+
}
6262

63-
while (i--) {
64-
var rippleEle: any = activatableEle.children[i];
65-
if (rippleEle.classList.contains('button-effect')) {
66-
var clientPointerX = (startCoord.x - rippleEle.$left);
67-
var clientPointerY = (startCoord.y - rippleEle.$top);
63+
super.upAction(ev, activatableEle, startCoord);
64+
}
6865

69-
var x = Math.max(Math.abs(rippleEle.$width - clientPointerX), clientPointerX) * 2;
70-
var y = Math.max(Math.abs(rippleEle.$height - clientPointerY), clientPointerY) * 2;
71-
var diameter = Math.min(Math.max(Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), 64), 240);
66+
startRippleEffect(rippleEle: any, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
67+
let clientPointerX = (startCoord.x - rippleEle.$left);
68+
let clientPointerY = (startCoord.y - rippleEle.$top);
7269

73-
if (activatableEle.hasAttribute('ion-item')) {
74-
diameter = Math.min(diameter, 140);
75-
}
70+
let x = Math.max(Math.abs(rippleEle.$width - clientPointerX), clientPointerX) * 2;
71+
let y = Math.max(Math.abs(rippleEle.$height - clientPointerY), clientPointerY) * 2;
72+
let diameter = Math.min(Math.max(Math.hypot(x, y), 64), 240);
7673

77-
var radius = Math.sqrt(rippleEle.$width + rippleEle.$height);
78-
79-
var scaleTransitionDuration = Math.max(1600 * Math.sqrt(radius / TOUCH_DOWN_ACCEL) + 0.5, 260);
80-
var opacityTransitionDuration = scaleTransitionDuration * 0.7;
81-
var opacityTransitionDelay = scaleTransitionDuration - opacityTransitionDuration;
82-
83-
// DOM WRITE
84-
rippleEle.style.width = rippleEle.style.height = diameter + 'px';
85-
rippleEle.style.marginTop = rippleEle.style.marginLeft = -(diameter / 2) + 'px';
86-
rippleEle.style.left = clientPointerX + 'px';
87-
rippleEle.style.top = clientPointerY + 'px';
88-
rippleEle.style.opacity = '0';
89-
rippleEle.style[CSS.transform] = 'scale(1) translateZ(0px)';
90-
rippleEle.style[CSS.transition] = 'transform ' +
91-
scaleTransitionDuration +
92-
'ms,opacity ' +
93-
opacityTransitionDuration +
94-
'ms ' +
95-
opacityTransitionDelay + 'ms';
96-
}
97-
}
74+
if (activatableEle.hasAttribute('ion-item')) {
75+
diameter = Math.min(diameter, 140);
9876
}
9977

100-
super.upAction(ev, activatableEle, startCoord);
78+
clientPointerX -= diameter / 2;
79+
clientPointerY -= diameter / 2;
80+
81+
clientPointerX = Math.round(clientPointerX);
82+
clientPointerY = Math.round(clientPointerY);
83+
diameter = Math.round(diameter);
84+
85+
// Reset ripple
86+
rippleEle.style.opacity = '';
87+
rippleEle.style[CSS.transform] = `translate3d(${clientPointerX}px, ${clientPointerY}px, 0px) scale(0.001)`;
88+
rippleEle.style[CSS.transition] = '';
89+
90+
// Start ripple animation
91+
let radius = Math.sqrt(rippleEle.$width + rippleEle.$height);
92+
let scaleTransitionDuration = Math.max(1600 * Math.sqrt(radius / TOUCH_DOWN_ACCEL) + 0.5, 260);
93+
let opacityTransitionDuration = Math.round(scaleTransitionDuration * 0.7);
94+
let opacityTransitionDelay = Math.round(scaleTransitionDuration - opacityTransitionDuration);
95+
scaleTransitionDuration = Math.round(scaleTransitionDuration);
96+
97+
let transform = `translate3d(${clientPointerX}px, ${clientPointerY}px, 0px) scale(1)`;
98+
let transition = `transform ${scaleTransitionDuration}ms,opacity ${opacityTransitionDuration}ms ${opacityTransitionDelay}ms`;
99+
100+
rafFrames(2, () => {
101+
// DOM WRITE
102+
rippleEle.style.width = rippleEle.style.height = diameter + 'px';
103+
rippleEle.style.opacity = '0';
104+
rippleEle.style[CSS.transform] = transform;
105+
rippleEle.style[CSS.transition] = transition;
106+
});
101107
}
102108

103109
deactivate() {
104110
// remove the active class from all active elements
105-
let self = this;
106-
self._queue = [];
111+
this._queue = [];
107112

108-
rafFrames(2, function() {
109-
for (var i = 0; i < self._active.length; i++) {
110-
self._active[i].classList.remove(self._css);
113+
rafFrames(2, () => {
114+
for (var i = 0; i < this._active.length; i++) {
115+
this._active[i].classList.remove(this._css);
111116
}
112-
self._active = [];
117+
this._active = [];
113118
});
114119
}
115120

0 commit comments

Comments
 (0)