mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 14:01:20 +08:00
perf(activator): improve activator response
This commit is contained in:

committed by
Manu Mtz.-Almeida

parent
06938b64a5
commit
bb800339f8
@ -7,11 +7,29 @@ export class Activator {
|
|||||||
protected _css: string;
|
protected _css: string;
|
||||||
protected _queue: HTMLElement[] = [];
|
protected _queue: HTMLElement[] = [];
|
||||||
protected _active: HTMLElement[] = [];
|
protected _active: HTMLElement[] = [];
|
||||||
|
protected _activeRafDefer: Function;
|
||||||
|
|
||||||
constructor(protected app: App, config: Config) {
|
constructor(protected app: App, config: Config) {
|
||||||
this._css = config.get('activatedClass') || 'activated';
|
this._css = config.get('activatedClass') || 'activated';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clickAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
||||||
|
// a click happened, so immediately deactive all activated elements
|
||||||
|
this._clearDeferred();
|
||||||
|
this._queue.length = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < this._active.length; i++) {
|
||||||
|
this._active[i].classList.remove(this._css);
|
||||||
|
}
|
||||||
|
this._active.length = 0;
|
||||||
|
|
||||||
|
// then immediately activate this element
|
||||||
|
if (activatableEle && activatableEle.parentNode) {
|
||||||
|
this._active.push(activatableEle);
|
||||||
|
activatableEle.classList.add(this._css);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
||||||
// the user just pressed down
|
// the user just pressed down
|
||||||
if (this.disableActivated(ev)) {
|
if (this.disableActivated(ev)) {
|
||||||
@ -21,7 +39,7 @@ export class Activator {
|
|||||||
// queue to have this element activated
|
// queue to have this element activated
|
||||||
this._queue.push(activatableEle);
|
this._queue.push(activatableEle);
|
||||||
|
|
||||||
rafFrames(6, () => {
|
this._activeRafDefer = rafFrames(6, () => {
|
||||||
let activatableEle: HTMLElement;
|
let activatableEle: HTMLElement;
|
||||||
for (let i = 0; i < this._queue.length; i++) {
|
for (let i = 0; i < this._queue.length; i++) {
|
||||||
activatableEle = this._queue[i];
|
activatableEle = this._queue[i];
|
||||||
@ -31,18 +49,22 @@ export class Activator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._queue.length = 0;
|
this._queue.length = 0;
|
||||||
|
this._clearDeferred();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the user was pressing down, then just let up
|
||||||
upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
||||||
// the user was pressing down, then just let up
|
this._clearDeferred();
|
||||||
|
|
||||||
rafFrames(CLEAR_STATE_DEFERS, () => {
|
rafFrames(CLEAR_STATE_DEFERS, () => {
|
||||||
this.clearState();
|
this.clearState();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all states should return to normal
|
||||||
clearState() {
|
clearState() {
|
||||||
// all states should return to normal
|
|
||||||
if (!this.app.isEnabled()) {
|
if (!this.app.isEnabled()) {
|
||||||
// the app is actively disabled, so don't bother deactivating anything.
|
// the app is actively disabled, so don't bother deactivating anything.
|
||||||
// this makes it easier on the GPU so it doesn't have to redraw any
|
// this makes it easier on the GPU so it doesn't have to redraw any
|
||||||
@ -57,18 +79,28 @@ export class Activator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove the active class from all active elements
|
||||||
deactivate() {
|
deactivate() {
|
||||||
// remove the active class from all active elements
|
this._clearDeferred();
|
||||||
|
|
||||||
this._queue.length = 0;
|
this._queue.length = 0;
|
||||||
|
|
||||||
rafFrames(2, () => {
|
rafFrames(2, () => {
|
||||||
for (var i = 0; i < this._active.length; i++) {
|
for (var i = 0; i < this._active.length; i++) {
|
||||||
this._active[i].classList.remove(this._css);
|
this._active[i].classList.remove(this._css);
|
||||||
}
|
}
|
||||||
this._active = [];
|
this._active.length = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_clearDeferred() {
|
||||||
|
// Clear any active deferral
|
||||||
|
if (this._activeRafDefer) {
|
||||||
|
this._activeRafDefer();
|
||||||
|
this._activeRafDefer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
disableActivated(ev: any) {
|
disableActivated(ev: any) {
|
||||||
if (ev.defaultPrevented) {
|
if (ev.defaultPrevented) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -13,38 +13,34 @@ export class RippleActivator extends Activator {
|
|||||||
super(app, config);
|
super(app, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clickAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
||||||
|
this.downAction(ev, activatableEle, startCoord);
|
||||||
|
this.upAction(ev, activatableEle, startCoord);
|
||||||
|
}
|
||||||
|
|
||||||
downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
downAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
||||||
if (this.disableActivated(ev)) {
|
if (this.disableActivated(ev) || !activatableEle || !activatableEle.parentNode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// queue to have this element activated
|
this._active.push(activatableEle);
|
||||||
this._queue.push(activatableEle);
|
|
||||||
|
|
||||||
for (var i = 0; i < this._queue.length; i++) {
|
var j = activatableEle.childElementCount;
|
||||||
var queuedEle = this._queue[i];
|
while (j--) {
|
||||||
if (queuedEle && queuedEle.parentNode) {
|
var rippleEle: any = activatableEle.children[j];
|
||||||
this._active.push(queuedEle);
|
if (rippleEle.classList.contains('button-effect')) {
|
||||||
|
// DOM READ
|
||||||
// DOM WRITE
|
var clientRect = activatableEle.getBoundingClientRect();
|
||||||
queuedEle.classList.add(this._css);
|
rippleEle.$top = clientRect.top;
|
||||||
|
rippleEle.$left = clientRect.left;
|
||||||
var j = queuedEle.childElementCount;
|
rippleEle.$width = clientRect.width;
|
||||||
while (j--) {
|
rippleEle.$height = clientRect.height;
|
||||||
var rippleEle: any = queuedEle.children[j];
|
break;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._queue = [];
|
|
||||||
|
// DOM WRITE
|
||||||
|
activatableEle.classList.add(this._css);
|
||||||
}
|
}
|
||||||
|
|
||||||
upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
upAction(ev: UIEvent, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
||||||
@ -53,6 +49,7 @@ export class RippleActivator extends Activator {
|
|||||||
while (i--) {
|
while (i--) {
|
||||||
var rippleEle: any = activatableEle.children[i];
|
var rippleEle: any = activatableEle.children[i];
|
||||||
if (rippleEle.classList.contains('button-effect')) {
|
if (rippleEle.classList.contains('button-effect')) {
|
||||||
|
// DOM WRITE
|
||||||
this.startRippleEffect(rippleEle, activatableEle, startCoord);
|
this.startRippleEffect(rippleEle, activatableEle, startCoord);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -63,6 +60,10 @@ export class RippleActivator extends Activator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startRippleEffect(rippleEle: any, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
startRippleEffect(rippleEle: any, activatableEle: HTMLElement, startCoord: PointerCoordinates) {
|
||||||
|
if (!startCoord) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let clientPointerX = (startCoord.x - rippleEle.$left);
|
let clientPointerX = (startCoord.x - rippleEle.$left);
|
||||||
let clientPointerY = (startCoord.y - rippleEle.$top);
|
let clientPointerY = (startCoord.y - rippleEle.$top);
|
||||||
|
|
||||||
@ -82,6 +83,7 @@ export class RippleActivator extends Activator {
|
|||||||
diameter = Math.round(diameter);
|
diameter = Math.round(diameter);
|
||||||
|
|
||||||
// Reset ripple
|
// Reset ripple
|
||||||
|
// DOM WRITE
|
||||||
rippleEle.style.opacity = '';
|
rippleEle.style.opacity = '';
|
||||||
rippleEle.style[CSS.transform] = `translate3d(${clientPointerX}px, ${clientPointerY}px, 0px) scale(0.001)`;
|
rippleEle.style[CSS.transform] = `translate3d(${clientPointerX}px, ${clientPointerY}px, 0px) scale(0.001)`;
|
||||||
rippleEle.style[CSS.transition] = '';
|
rippleEle.style[CSS.transition] = '';
|
||||||
@ -106,14 +108,12 @@ export class RippleActivator extends Activator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deactivate() {
|
deactivate() {
|
||||||
// remove the active class from all active elements
|
|
||||||
this._queue = [];
|
|
||||||
|
|
||||||
rafFrames(2, () => {
|
rafFrames(2, () => {
|
||||||
for (var i = 0; i < this._active.length; i++) {
|
for (var i = 0; i < this._active.length; i++) {
|
||||||
|
// DOM WRITE
|
||||||
this._active[i].classList.remove(this._css);
|
this._active[i].classList.remove(this._css);
|
||||||
}
|
}
|
||||||
this._active = [];
|
this._active.length = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,20 +73,20 @@ export class TapClick {
|
|||||||
if (!this.startCoord) {
|
if (!this.startCoord) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.usePolyfill && type === PointerEventType.TOUCH && this.app.isEnabled()) {
|
|
||||||
this.handleTapPolyfill(ev);
|
|
||||||
}
|
|
||||||
if (this.activator) {
|
if (this.activator) {
|
||||||
let activatableEle = getActivatableTarget(ev.target);
|
let activatableEle = getActivatableTarget(ev.target);
|
||||||
if (activatableEle) {
|
if (activatableEle) {
|
||||||
this.activator.upAction(ev, activatableEle, this.startCoord);
|
this.activator.upAction(ev, activatableEle, this.startCoord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.usePolyfill && type === PointerEventType.TOUCH && this.app.isEnabled()) {
|
||||||
|
this.handleTapPolyfill(ev);
|
||||||
|
}
|
||||||
this.startCoord = null;
|
this.startCoord = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointerCancel(ev: UIEvent) {
|
pointerCancel(ev: UIEvent) {
|
||||||
console.debug('pointerCancel from ' + ev.type + ' ' + Date.now());
|
console.debug(`pointerCancel from ${ev.type} ${Date.now()}`);
|
||||||
this.startCoord = null;
|
this.startCoord = null;
|
||||||
this.activator && this.activator.clearState();
|
this.activator && this.activator.clearState();
|
||||||
this.pointerEvents.stop();
|
this.pointerEvents.stop();
|
||||||
@ -103,9 +103,18 @@ export class TapClick {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (preventReason !== null) {
|
if (preventReason !== null) {
|
||||||
console.debug('click prevent ' + preventReason + ' ' + Date.now());
|
// darn, there was a reason to prevent this click, let's not allow it
|
||||||
|
console.debug(`click prevent ${preventReason} ${Date.now()}`);
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
} else if (this.activator) {
|
||||||
|
// cool, a click is gonna happen, let's tell the activator
|
||||||
|
// so the element can get the given "active" style
|
||||||
|
const activatableEle = getActivatableTarget(ev.target);
|
||||||
|
if (activatableEle) {
|
||||||
|
this.activator.clickAction(ev, activatableEle, this.startCoord);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +126,7 @@ export class TapClick {
|
|||||||
let endCoord = pointerCoord(ev);
|
let endCoord = pointerCoord(ev);
|
||||||
|
|
||||||
if (hasPointerMoved(POINTER_TOLERANCE, this.startCoord, endCoord)) {
|
if (hasPointerMoved(POINTER_TOLERANCE, this.startCoord, endCoord)) {
|
||||||
console.debug('click from touch prevented by pointer moved');
|
console.debug(`click from touch prevented by pointer moved`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// prevent native mouse click events for XX amount of time
|
// prevent native mouse click events for XX amount of time
|
||||||
@ -125,11 +134,11 @@ export class TapClick {
|
|||||||
|
|
||||||
if (this.app.isScrolling()) {
|
if (this.app.isScrolling()) {
|
||||||
// do not fire off a click event while the app was scrolling
|
// do not fire off a click event while the app was scrolling
|
||||||
console.debug('click from touch prevented by scrolling ' + Date.now());
|
console.debug(`click from touch prevented by scrolling ${Date.now()}`);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// dispatch a mouse click event
|
// dispatch a mouse click event
|
||||||
console.debug('create click from touch ' + Date.now());
|
console.debug(`create click from touch ${Date.now()}`);
|
||||||
|
|
||||||
let clickEvent: any = document.createEvent('MouseEvents');
|
let clickEvent: any = document.createEvent('MouseEvents');
|
||||||
clickEvent.initMouseEvent('click', true, true, window, 1, 0, 0, endCoord.x, endCoord.y, false, false, false, false, 0, null);
|
clickEvent.initMouseEvent('click', true, true, window, 1, 0, 0, endCoord.x, endCoord.y, false, false, false, false, 0, null);
|
||||||
|
@ -36,17 +36,31 @@ export const cancelRaf = window.cancelAnimationFrame.bind(window);
|
|||||||
export const nativeTimeout = window[window['Zone']['__symbol__']('setTimeout')]['bind'](window);
|
export const nativeTimeout = window[window['Zone']['__symbol__']('setTimeout')]['bind'](window);
|
||||||
export const clearNativeTimeout = window[window['Zone']['__symbol__']('clearTimeout')]['bind'](window);
|
export const clearNativeTimeout = window[window['Zone']['__symbol__']('clearTimeout')]['bind'](window);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a function in an animation frame after waiting `framesToWait` frames.
|
||||||
|
*
|
||||||
|
* @param framesToWait number how many frames to wait
|
||||||
|
* @param callback Function the function call to defer
|
||||||
|
* @return Function a function to call to cancel the wait
|
||||||
|
*/
|
||||||
export function rafFrames(framesToWait: number, callback: Function) {
|
export function rafFrames(framesToWait: number, callback: Function) {
|
||||||
framesToWait = Math.ceil(framesToWait);
|
framesToWait = Math.ceil(framesToWait);
|
||||||
|
let rafId: any;
|
||||||
|
let timeoutId: any;
|
||||||
|
|
||||||
if (framesToWait < 2) {
|
if (framesToWait < 2) {
|
||||||
nativeRaf(callback);
|
rafId = nativeRaf(callback);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
nativeTimeout(() => {
|
timeoutId = nativeTimeout(() => {
|
||||||
nativeRaf(callback);
|
rafId = nativeRaf(callback);
|
||||||
}, (framesToWait - 1) * 16.6667);
|
}, (framesToWait - 1) * 16.6667);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
clearNativeTimeout(timeoutId);
|
||||||
|
cancelRaf(raf);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: DRY rafFrames and zoneRafFrames
|
// TODO: DRY rafFrames and zoneRafFrames
|
||||||
@ -210,10 +224,13 @@ export function pointerCoord(ev: any): PointerCoordinates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function hasPointerMoved(threshold: number, startCoord: PointerCoordinates, endCoord: PointerCoordinates) {
|
export function hasPointerMoved(threshold: number, startCoord: PointerCoordinates, endCoord: PointerCoordinates) {
|
||||||
let deltaX = (startCoord.x - endCoord.x);
|
if (startCoord && endCoord) {
|
||||||
let deltaY = (startCoord.y - endCoord.y);
|
const deltaX = (startCoord.x - endCoord.x);
|
||||||
let distance = deltaX * deltaX + deltaY * deltaY;
|
const deltaY = (startCoord.y - endCoord.y);
|
||||||
return distance > (threshold * threshold);
|
const distance = deltaX * deltaX + deltaY * deltaY;
|
||||||
|
return distance > (threshold * threshold);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isActive(ele: HTMLElement) {
|
export function isActive(ele: HTMLElement) {
|
||||||
|
Reference in New Issue
Block a user