Skip to content

Commit bb80033

Browse files
adamdbradleymanucorporat
authored andcommitted
perf(activator): improve activator response
1 parent 06938b6 commit bb80033

File tree

4 files changed

+108
-50
lines changed

4 files changed

+108
-50
lines changed

src/components/tap-click/activator.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,29 @@ export class Activator {
77
protected _css: string;
88
protected _queue: HTMLElement[] = [];
99
protected _active: HTMLElement[] = [];
10+
protected _activeRafDefer: Function;
1011

1112
constructor(protected app: App, config: Config) {
1213
this._css = config.get('activatedClass') || 'activated';
1314
}
1415

16+
clickAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
17+
// a click happened, so immediately deactive all activated elements
18+
this._clearDeferred();
19+
this._queue.length = 0;
20+
21+
for (var i = 0; i < this._active.length; i++) {
22+
this._active[i].classList.remove(this._css);
23+
}
24+
this._active.length = 0;
25+
26+
// then immediately activate this element
27+
if (activatableEle && activatableEle.parentNode) {
28+
this._active.push(activatableEle);
29+
activatableEle.classList.add(this._css);
30+
}
31+
}
32+
1533
downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
1634
// the user just pressed down
1735
if (this.disableActivated(ev)) {
@@ -21,7 +39,7 @@ export class Activator {
2139
// queue to have this element activated
2240
this._queue.push(activatableEle);
2341

24-
rafFrames(6, () => {
42+
this._activeRafDefer = rafFrames(6, () => {
2543
let activatableEle: HTMLElement;
2644
for (let i = 0; i < this._queue.length; i++) {
2745
activatableEle = this._queue[i];
@@ -31,18 +49,22 @@ export class Activator {
3149
}
3250
}
3351
this._queue.length = 0;
52+
this._clearDeferred();
3453
});
3554
}
3655

56+
// the user was pressing down, then just let up
3757
upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
38-
// the user was pressing down, then just let up
58+
this._clearDeferred();
59+
3960
rafFrames(CLEAR_STATE_DEFERS, () => {
4061
this.clearState();
4162
});
4263
}
4364

65+
// all states should return to normal
4466
clearState() {
45-
// all states should return to normal
67+
4668
if (!this.app.isEnabled()) {
4769
// the app is actively disabled, so don't bother deactivating anything.
4870
// this makes it easier on the GPU so it doesn't have to redraw any
@@ -57,18 +79,28 @@ export class Activator {
5779
}
5880
}
5981

82+
// remove the active class from all active elements
6083
deactivate() {
61-
// remove the active class from all active elements
84+
this._clearDeferred();
85+
6286
this._queue.length = 0;
6387

6488
rafFrames(2, () => {
6589
for (var i = 0; i < this._active.length; i++) {
6690
this._active[i].classList.remove(this._css);
6791
}
68-
this._active = [];
92+
this._active.length = 0;
6993
});
7094
}
7195

96+
_clearDeferred() {
97+
// Clear any active deferral
98+
if (this._activeRafDefer) {
99+
this._activeRafDefer();
100+
this._activeRafDefer = null;
101+
}
102+
}
103+
72104
disableActivated(ev: any) {
73105
if (ev.defaultPrevented) {
74106
return true;

src/components/tap-click/ripple.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,34 @@ export class RippleActivator extends Activator {
1313
super(app, config);
1414
}
1515

16+
clickAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
17+
this.downAction(ev, activatableEle, startCoord);
18+
this.upAction(ev, activatableEle, startCoord);
19+
}
20+
1621
downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
17-
if (this.disableActivated(ev)) {
22+
if (this.disableActivated(ev) || !activatableEle || !activatableEle.parentNode) {
1823
return;
1924
}
2025

21-
// queue to have this element activated
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;
43-
}
44-
}
26+
this._active.push(activatableEle);
27+
28+
var j = activatableEle.childElementCount;
29+
while (j--) {
30+
var rippleEle: any = activatableEle.children[j];
31+
if (rippleEle.classList.contains('button-effect')) {
32+
// DOM READ
33+
var clientRect = activatableEle.getBoundingClientRect();
34+
rippleEle.$top = clientRect.top;
35+
rippleEle.$left = clientRect.left;
36+
rippleEle.$width = clientRect.width;
37+
rippleEle.$height = clientRect.height;
38+
break;
4539
}
4640
}
47-
this._queue = [];
41+
42+
// DOM WRITE
43+
activatableEle.classList.add(this._css);
4844
}
4945

5046
upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
@@ -53,6 +49,7 @@ export class RippleActivator extends Activator {
5349
while (i--) {
5450
var rippleEle: any = activatableEle.children[i];
5551
if (rippleEle.classList.contains('button-effect')) {
52+
// DOM WRITE
5653
this.startRippleEffect(rippleEle, activatableEle, startCoord);
5754
break;
5855
}
@@ -63,6 +60,10 @@ export class RippleActivator extends Activator {
6360
}
6461

6562
startRippleEffect(rippleEle: any, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
63+
if (!startCoord) {
64+
return;
65+
}
66+
6667
let clientPointerX = (startCoord.x - rippleEle.$left);
6768
let clientPointerY = (startCoord.y - rippleEle.$top);
6869

@@ -82,6 +83,7 @@ export class RippleActivator extends Activator {
8283
diameter = Math.round(diameter);
8384

8485
// Reset ripple
86+
// DOM WRITE
8587
rippleEle.style.opacity = '';
8688
rippleEle.style[CSS.transform] = `translate3d(${clientPointerX}px, ${clientPointerY}px, 0px) scale(0.001)`;
8789
rippleEle.style[CSS.transition] = '';
@@ -106,14 +108,12 @@ export class RippleActivator extends Activator {
106108
}
107109

108110
deactivate() {
109-
// remove the active class from all active elements
110-
this._queue = [];
111-
112111
rafFrames(2, () => {
113112
for (var i = 0; i < this._active.length; i++) {
113+
// DOM WRITE
114114
this._active[i].classList.remove(this._css);
115115
}
116-
this._active = [];
116+
this._active.length = 0;
117117
});
118118
}
119119

src/components/tap-click/tap-click.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,20 @@ export class TapClick {
7373
if (!this.startCoord) {
7474
return;
7575
}
76-
if (this.usePolyfill && type === PointerEventType.TOUCH && this.app.isEnabled()) {
77-
this.handleTapPolyfill(ev);
78-
}
7976
if (this.activator) {
8077
let activatableEle = getActivatableTarget(ev.target);
8178
if (activatableEle) {
8279
this.activator.upAction(ev, activatableEle, this.startCoord);
8380
}
8481
}
82+
if (this.usePolyfill && type === PointerEventType.TOUCH && this.app.isEnabled()) {
83+
this.handleTapPolyfill(ev);
84+
}
8585
this.startCoord = null;
8686
}
8787

8888
pointerCancel(ev: UIEvent) {
89-
console.debug('pointerCancel from ' + ev.type + ' ' + Date.now());
89+
console.debug(`pointerCancel from ${ev.type} ${Date.now()}`);
9090
this.startCoord = null;
9191
this.activator && this.activator.clearState();
9292
this.pointerEvents.stop();
@@ -103,9 +103,18 @@ export class TapClick {
103103
}
104104

105105
if (preventReason !== null) {
106-
console.debug('click prevent ' + preventReason + ' ' + Date.now());
106+
// darn, there was a reason to prevent this click, let's not allow it
107+
console.debug(`click prevent ${preventReason} ${Date.now()}`);
107108
ev.preventDefault();
108109
ev.stopPropagation();
110+
111+
} else if (this.activator) {
112+
// cool, a click is gonna happen, let's tell the activator
113+
// so the element can get the given "active" style
114+
const activatableEle = getActivatableTarget(ev.target);
115+
if (activatableEle) {
116+
this.activator.clickAction(ev, activatableEle, this.startCoord);
117+
}
109118
}
110119
}
111120

@@ -117,19 +126,19 @@ export class TapClick {
117126
let endCoord = pointerCoord(ev);
118127

119128
if (hasPointerMoved(POINTER_TOLERANCE, this.startCoord, endCoord)) {
120-
console.debug('click from touch prevented by pointer moved');
129+
console.debug(`click from touch prevented by pointer moved`);
121130
return;
122131
}
123132
// prevent native mouse click events for XX amount of time
124133
this.disableClick = Date.now() + DISABLE_NATIVE_CLICK_AMOUNT;
125134

126135
if (this.app.isScrolling()) {
127136
// do not fire off a click event while the app was scrolling
128-
console.debug('click from touch prevented by scrolling ' + Date.now());
137+
console.debug(`click from touch prevented by scrolling ${Date.now()}`);
129138

130139
} else {
131140
// dispatch a mouse click event
132-
console.debug('create click from touch ' + Date.now());
141+
console.debug(`create click from touch ${Date.now()}`);
133142

134143
let clickEvent: any = document.createEvent('MouseEvents');
135144
clickEvent.initMouseEvent('click', true, true, window, 1, 0, 0, endCoord.x, endCoord.y, false, false, false, false, 0, null);

src/util/dom.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,31 @@ export const cancelRaf = window.cancelAnimationFrame.bind(window);
3636
export const nativeTimeout = window[window['Zone']['__symbol__']('setTimeout')]['bind'](window);
3737
export const clearNativeTimeout = window[window['Zone']['__symbol__']('clearTimeout')]['bind'](window);
3838

39+
/**
40+
* Run a function in an animation frame after waiting `framesToWait` frames.
41+
*
42+
* @param framesToWait number how many frames to wait
43+
* @param callback Function the function call to defer
44+
* @return Function a function to call to cancel the wait
45+
*/
3946
export function rafFrames(framesToWait: number, callback: Function) {
4047
framesToWait = Math.ceil(framesToWait);
48+
let rafId: any;
49+
let timeoutId: any;
4150

4251
if (framesToWait < 2) {
43-
nativeRaf(callback);
52+
rafId = nativeRaf(callback);
4453

4554
} else {
46-
nativeTimeout(() => {
47-
nativeRaf(callback);
55+
timeoutId = nativeTimeout(() => {
56+
rafId = nativeRaf(callback);
4857
}, (framesToWait - 1) * 16.6667);
4958
}
59+
60+
return function() {
61+
clearNativeTimeout(timeoutId);
62+
cancelRaf(raf);
63+
};
5064
}
5165

5266
// TODO: DRY rafFrames and zoneRafFrames
@@ -210,10 +224,13 @@ export function pointerCoord(ev: any): PointerCoordinates {
210224
}
211225

212226
export function hasPointerMoved(threshold: number, startCoord: PointerCoordinates, endCoord: PointerCoordinates) {
213-
let deltaX = (startCoord.x - endCoord.x);
214-
let deltaY = (startCoord.y - endCoord.y);
215-
let distance = deltaX * deltaX + deltaY * deltaY;
216-
return distance > (threshold * threshold);
227+
if (startCoord && endCoord) {
228+
const deltaX = (startCoord.x - endCoord.x);
229+
const deltaY = (startCoord.y - endCoord.y);
230+
const distance = deltaX * deltaX + deltaY * deltaY;
231+
return distance > (threshold * threshold);
232+
}
233+
return false;
217234
}
218235

219236
export function isActive(ele: HTMLElement) {

0 commit comments

Comments
 (0)