-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf(material/core): delegate trigger events (#26147)
Ripples are used a lot throughout our components which makes them very performance-sensitive. These changes aim to reduce the cost of setting them up by delegating the `mousedown` and `touchstart` handlers. This shaved off 15 to 20 percent of the creation cost from buttons. I decided to only delegate the `mousedown` and `touchstart` events, rather than all ripple-related events, because ripples listen to some very frequent events like `mouseleave` and the cost of matching events to their targets would've offset any gains we would've gotten from delegating them. The code is written in a way where we can easily delegate them later if we change our minds. (cherry picked from commit efb7f2e)
- Loading branch information
Showing
3 changed files
with
120 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {normalizePassiveListenerOptions, _getEventTarget} from '@angular/cdk/platform'; | ||
import {NgZone} from '@angular/core'; | ||
|
||
/** Options used to bind a passive capturing event. */ | ||
const passiveCapturingEventOptions = normalizePassiveListenerOptions({ | ||
passive: true, | ||
capture: true, | ||
}); | ||
|
||
/** Manages events through delegation so that as few event handlers as possible are bound. */ | ||
export class RippleEventManager { | ||
private _events = new Map<string, Map<HTMLElement, Set<EventListenerObject>>>(); | ||
|
||
/** Adds an event handler. */ | ||
addHandler(ngZone: NgZone, name: string, element: HTMLElement, handler: EventListenerObject) { | ||
const handlersForEvent = this._events.get(name); | ||
|
||
if (handlersForEvent) { | ||
const handlersForElement = handlersForEvent.get(element); | ||
|
||
if (handlersForElement) { | ||
handlersForElement.add(handler); | ||
} else { | ||
handlersForEvent.set(element, new Set([handler])); | ||
} | ||
} else { | ||
this._events.set(name, new Map([[element, new Set([handler])]])); | ||
|
||
ngZone.runOutsideAngular(() => { | ||
document.addEventListener(name, this._delegateEventHandler, passiveCapturingEventOptions); | ||
}); | ||
} | ||
} | ||
|
||
/** Removes an event handler. */ | ||
removeHandler(name: string, element: HTMLElement, handler: EventListenerObject) { | ||
const handlersForEvent = this._events.get(name); | ||
|
||
if (!handlersForEvent) { | ||
return; | ||
} | ||
|
||
const handlersForElement = handlersForEvent.get(element); | ||
|
||
if (!handlersForElement) { | ||
return; | ||
} | ||
|
||
handlersForElement.delete(handler); | ||
|
||
if (handlersForElement.size === 0) { | ||
handlersForEvent.delete(element); | ||
} | ||
|
||
if (handlersForEvent.size === 0) { | ||
this._events.delete(name); | ||
document.removeEventListener(name, this._delegateEventHandler, passiveCapturingEventOptions); | ||
} | ||
} | ||
|
||
/** Event handler that is bound and which dispatches the events to the different targets. */ | ||
private _delegateEventHandler = (event: Event) => { | ||
const target = _getEventTarget(event); | ||
|
||
if (target) { | ||
this._events.get(event.type)?.forEach((handlers, element) => { | ||
if (element === target || element.contains(target as Node)) { | ||
handlers.forEach(handler => handler.handleEvent(event)); | ||
} | ||
}); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters