Skip to content

Commit f0b6989

Browse files
committed
perf: add debounced render for cluster markers
1 parent f2e70f8 commit f0b6989

File tree

6 files changed

+111
-2
lines changed

6 files changed

+111
-2
lines changed

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = {
2626
"!src/**/__tests__/**",
2727
"!src/shims-*.ts",
2828
"!src/themes/**",
29+
"!src/components/DebouncedMarkerClusterer.ts",
2930
],
3031
coverageThreshold: {
3132
global: {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"dependencies": {
5858
"@googlemaps/js-api-loader": "^1.16.2",
5959
"@googlemaps/markerclusterer": "^2.4.0",
60+
"debounce": "^2.2.0",
6061
"fast-deep-equal": "^3.1.3"
6162
},
6263
"devDependencies": {

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { MarkerClusterer, MarkerClustererOptions } from "@googlemaps/markerclusterer";
2+
import debounce from "debounce";
3+
4+
export class DebouncedMarkerClusterer extends MarkerClusterer {
5+
private readonly debouncedRender: (() => void) & { clear(): void };
6+
7+
constructor(options: MarkerClustererOptions, debounceDelay = 10) {
8+
super(options);
9+
10+
this.debouncedRender = debounce(() => {
11+
super.render();
12+
}, debounceDelay);
13+
}
14+
15+
addMarker(marker: google.maps.Marker | google.maps.marker.AdvancedMarkerElement, noDraw?: boolean): void {
16+
super.addMarker(marker, true);
17+
if (!noDraw) {
18+
this.debouncedRender();
19+
}
20+
}
21+
22+
removeMarker(marker: google.maps.Marker | google.maps.marker.AdvancedMarkerElement, noDraw?: boolean): boolean {
23+
const result = super.removeMarker(marker, true);
24+
if (!noDraw) {
25+
this.debouncedRender();
26+
}
27+
return result;
28+
}
29+
30+
addMarkers(markers: (google.maps.Marker | google.maps.marker.AdvancedMarkerElement)[], noDraw?: boolean): void {
31+
super.addMarkers(markers, true);
32+
if (!noDraw) {
33+
this.debouncedRender();
34+
}
35+
}
36+
37+
removeMarkers(markers: (google.maps.Marker | google.maps.marker.AdvancedMarkerElement)[], noDraw?: boolean): boolean {
38+
const result = super.removeMarkers(markers, true);
39+
if (!noDraw) {
40+
this.debouncedRender();
41+
}
42+
return result;
43+
}
44+
45+
clearMarkers(noDraw?: boolean): void {
46+
super.clearMarkers(true);
47+
if (!noDraw) {
48+
this.debouncedRender();
49+
}
50+
}
51+
52+
render(): void {
53+
this.debouncedRender.clear();
54+
super.render();
55+
}
56+
57+
destroy(): void {
58+
this.debouncedRender.clear();
59+
}
60+
}

src/components/MarkerCluster.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
SuperClusterViewportAlgorithm,
77
} from "@googlemaps/markerclusterer";
88
import { mapSymbol, apiSymbol, markerClusterSymbol } from "../shared/index";
9+
import { DebouncedMarkerClusterer } from "./DebouncedMarkerClusterer";
910

1011
export interface IMarkerClusterExposed {
1112
markerCluster: Ref<MarkerClusterer | undefined>;
@@ -20,6 +21,10 @@ export default defineComponent({
2021
type: Object as PropType<MarkerClustererOptions>,
2122
default: () => ({}),
2223
},
24+
renderDebounceDelay: {
25+
type: Number,
26+
default: 10,
27+
},
2328
},
2429
emits: markerClusterEvents,
2530
setup(props, { emit, expose, slots }) {
@@ -34,13 +39,13 @@ export default defineComponent({
3439
() => {
3540
if (map.value) {
3641
markerCluster.value = markRaw(
37-
new MarkerClusterer({
42+
new DebouncedMarkerClusterer({
3843
map: map.value,
3944
// Better perf than the default `SuperClusterAlgorithm`. See:
4045
// https://github.com/googlemaps/js-markerclusterer/pull/640
4146
algorithm: new SuperClusterViewportAlgorithm(props.options.algorithmOptions ?? {}),
4247
...props.options,
43-
})
48+
}, props.renderDebounceDelay)
4449
);
4550

4651
markerClusterEvents.forEach((event) => {
@@ -58,6 +63,10 @@ export default defineComponent({
5863
api.value?.event.clearInstanceListeners(markerCluster.value);
5964
markerCluster.value.clearMarkers();
6065
markerCluster.value.setMap(null);
66+
67+
if (markerCluster.value instanceof DebouncedMarkerClusterer) {
68+
markerCluster.value.destroy();
69+
}
6170
}
6271
});
6372

src/components/__tests__/MarkerCluster.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,34 @@ jest.mock("@googlemaps/markerclusterer", () => {
3333
};
3434
});
3535

36+
let debouncedMarkerClustererConstructorSpy: jest.Mock | undefined;
37+
38+
jest.mock("../DebouncedMarkerClusterer", () => {
39+
return {
40+
DebouncedMarkerClusterer: class {
41+
addListener = jest.fn();
42+
clearMarkers = jest.fn();
43+
setMap = jest.fn();
44+
private debouncedRender: any;
45+
46+
constructor(options: MarkerClustererOptions) {
47+
if (debouncedMarkerClustererConstructorSpy) {
48+
debouncedMarkerClustererConstructorSpy(options);
49+
}
50+
Object.assign(this, options);
51+
this.debouncedRender = {
52+
clear: jest.fn(),
53+
};
54+
mockMarkerClustererInstances.push(this);
55+
}
56+
57+
destroy() {
58+
this.debouncedRender.clear();
59+
}
60+
},
61+
};
62+
});
63+
3664
describe("MarkerCluster Component", () => {
3765
let mockMap: google.maps.Map;
3866
let mockApi: typeof google.maps;
@@ -50,6 +78,7 @@ describe("MarkerCluster Component", () => {
5078
mockMap = new Map(null);
5179

5280
createMarkerClustererSpy = jest.fn();
81+
debouncedMarkerClustererConstructorSpy = createMarkerClustererSpy;
5382
createSuperClusterViewportAlgorithmSpy = jest.fn();
5483

5584
(MarkerClusterer as any) = class extends MarkerClusterer {

0 commit comments

Comments
 (0)