Frontend Development 13 min read

How to Boost H5 Map Performance with AMap JS SDK: A Real‑World Optimization Guide

This article analyzes the performance bottlenecks of an H5 map built with the AMap JS‑SDK, explains the causes of frame drops on low‑end devices, and presents a step‑by‑step refactor using data‑fetching tweaks, DistrictCluster aggregation, and conditional rendering to achieve smoother interactions.

Goodme Frontend Team
Goodme Frontend Team
Goodme Frontend Team
How to Boost H5 Map Performance with AMap JS SDK: A Real‑World Optimization Guide

1. Background

During a project refactor we needed to improve the performance of an H5 map built with the AMap JS‑SDK that renders massive point data. The original implementation suffered noticeable lag on low‑end devices, so performance enhancement became a key goal.

2. Historical Implementation

1. Page Interpretation

Below is the original page layout, with a network request board showing API calls after each map move.

Interpretation : The map renders various entity types. To ensure real‑time data, the view triggers dynamic API requests on pan/zoom, followed by local calculations that decide whether to display clustered or scattered markers using

MarkerClusterer

. Clustering reduces DOM nodes and improves performance, but issues remain.

2. Performance Analysis

Page animation diagram

Performance

Panel (many frame drops, red and yellow sections)

Overall

Even with clustering, low‑end devices still experience stutter because:

The map view change triggers a 500 ms debounce, visible as a fixed red bar in the Performance panel.

Parallel data requests take 100–300 ms.

After data retrieval,

MarkerClusterer

performs local calculations, adding ~60 ms on a PC; on low‑end phones this can be much higher.

On a good PC the total latency is roughly 500 ms + (100‑300 ms) + (~60 ms) = 66 ms to 1 s, meaning a single drag/zoom cycle takes about 1 s to respond. On phones it can exceed 2 s, causing repeated user actions and severe lag.

In summary, three main factors affect performance: the 500 ms debounce, real‑time data fetching, and frequent local calculations. Each map interaction repeats this costly process.

After analysis, targeted optimizations can be applied.

3. Historical Code

Some

hook

logic is encapsulated in an internal SDK; the core code is shown below.

<code>import { useInitMap } from '@guming/amap';
import { useState , useRef } from 'react';

export default () => {
  const ClusterRef = useRef();
  const {Map, mapRef, mapInstance, currentZoom, currentBound} = useInitMap(config);
  // Debounce on view change
  useDebounceEffect(() => {
    fetchShopDataAndDraw(currentBound);
    // ...fetch other types
  }, [currentBound]);

  const fetchShopDataAndDraw = async (bounds) => {
    const massData = await fetchShopData({bounds});
    drawMarkerOrCluster(massData)
  };

  const drawMarkerOrCluster = (massData = []) => {
    if (!ClusterRef.current) {
      ClusterRef.current = new AMap.MarkerClusterer(mapRef.current, massData, {
        gridSize: 150,
        minClusterSize: 50,
        averageCenter: true,
        maxZoom: 17,
        renderClusterMarker(context) {
          const { marker, count } = context;
          marker.setAnchor('center');
          marker.setContent(`<div class="cluster-circle ${type}">${count}</div>`);
          marker.setzIndex(10);
        },
        renderMarker(context) {
          const { marker, data } = context;
          const markerData = data[0];
          if ((mapRef.current?.getZoom() || currentZoom) >= showNameZoom) {
            marker.setContent(createdMarker.getContent());
          } else {
            marker.setIcon(...);
          }
          marker.on('click', () => handleClickMarker(markerData, marker));
        },
      });
      ClusterRef.current.on('click', doSomeThing);
    } else {
      ClusterRef.current.setData(massData)
    }
  };

  return <Map />
}
</code>

3. Optimization Implementation

1. Plan

1.1 Data Acquisition

The original approach fetched data only for the current view. The new strategy loads the full dataset initially and reduces unnecessary requests.

On first page load, fetch all entity data; when the page becomes active, skip real‑time fetching for large, low‑priority competitor data and reuse cached data.

For large, low‑priority data, perform silent parallel paginated requests and gradually display them.

<code>const handleQueryClue = () => handleQuery('clue');
const handleQueryCrawler = () => handleQuery('crawler');

const queryAllTypeExcludeCarwlersData = async () => {
  // High‑frequency data
  handleQueryClue();
  // ...other entities
};

useDidShow(() => {
  queryAllTypeExcludeCarwlersData();
  if (!typesDataAndApiConfig.current.crawler.hasLoaded) {
    handleQueryCrawler();
  }
});
</code>

1.2 Rendering

Use

DistrictCluster

, which requires no frequent calculations, to render clustered points with minimal DOM nodes.

<code>// Instantiate DistrictCluster
clusterRef.current = new DistrictCluster({
  map: mapInstance,
  zIndex: 11,
  topAdcodes: [330100],
  getPosition: function(item) {
    return !item ? null : [item.lng, item.lat];
  },
  boundsQuerySupport: true,
  renderOptions: {
    getClusterMarkerPosition: DistrictCluster.ClusterMarkerPositionStrategy.AVERAGE_POINTS_POSITION,
    getClusterMarker: function(feature, dataItems, recycledMarker) {
      if (!dataItems.length) return null;
      const counts = dataItems.reduce((ret, { dataItem }) => {
        ret[dataItem.type] = (ret[dataItem.type] || 0) + 1;
        return ret;
      }, {});
      const content = `${feature.properties.name} :${Object.keys(counts).map(type => `<div>${type}:<span style="color:red">${counts[type]}</span></div>`).join('')}`;
      const label = { offset: new AMap.Pixel(0, 0), content };
      if (recycledMarker) {
        recycledMarker.setLabel(label);
        return recycledMarker;
      }
      return new AMap.Marker({ /* marker config */ });
    },
  },
});

// Configure scattered state rendering
const { clearAll: clearPoints, renderAll: renderPoints } = useRenderLabelMarker({
  mapInstance,
  dataKey: 'id',
  labelMarkerConfig({ dataItem }) { return { /* config */ }; },
});

const { clearAll: clearText, renderAll: renderText } = useRenderText({
  mapInstance,
  dataKey: 'id',
  labelMarkerConfig({ dataItem }) { return { /* config */ }; },
});
</code>

Gradually switch from clustered to scattered rendering as the number of points in view decreases, and render names only when the count is low.

<code>useDebounceEffect(() => {
  if (!currentBound) return;
  const inViewDatas = clusterRef.current?.getDataItemsInView() || [];
  clearPoints();
  clearText();
  if (inViewDatas.length >= 500) {
    clusterRef.current?.show();
  } else {
    clusterRef.current?.hide();
    renderPoints(inViewDatas.map(({ dataItem }) => dataItem));
    if (inViewDatas.length <= 150) {
      renderText();
    }
  }
}, [currentBound, currentZoom], { wait: 280 });
</code>

2. Results

2.1 Optimization Effect

The following animation shows the transition from 6,267 data points in a large‑view cluster to scattered states, along with the Performance panel.

The red sections in the Performance panel stay under

16 ms

during the transition.

Further panels illustrate frame drops when moving from small clusters to scattered state and during dynamic drawing as the view changes.

2.2 Before‑After Comparison

The following image compares a full user interaction flow before and after the optimizations.

Result: The same interaction now completes in less time, delivering a smoother rendering experience.

4. Summary

Main optimization directions:

Reduce data request frequency to avoid fetching on every view change.

Minimize massive calculations by using

DistrictCluster

for rendering and disabling heavy computations in specific scenarios.

Limit unnecessary visual updates; render icons only when the view contains many points, and add text labels only for a smaller subset.

Overall, coordinated adjustments at each layer prevent limited compute resources from being wasted on low‑value tasks, resulting in markedly better map performance.

frontendperformanceClusteringreactmapAMap
Goodme Frontend Team
Written by

Goodme Frontend Team

Regularly sharing the team's insights and expertise in the frontend field

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.