一、 前言
最近在做海外地图可视化,采用 Leaflet + OpenStreetMap
作为地图展示框架。但是在数据量较大的情况下,为了提升用户体验,需要对地图上的标记点进行聚类处理。
经过一番搜索,发现了 Leaflet.markercluster
插件,它可以帮助我们实现标记点的聚合效果,使得地图上的标记点数量减少,同时也保持了标记点的可读性和交互性。
例如:演示示例渲染了 10000+ 点位,轻轻松松!
天地图效果

OpenStreetMap 地图效果

通过本文章,我将通过 Leaflet.markercluster
简单实现一个标记点聚类的案例,希望能够帮助大家更好地理解和使用该插件。
二、介绍
1. 什么是 Leaflet.markercluster?
首先,Leaflet
是一款开源的 JavaScript 地图库,它提供了丰富的地图交互功能,如缩放、平移、标记点、弹出框等。它的设计目标是简单易用,同时支持多种地图服务提供者,如 OpenStreetMap
、Google Maps
等。
关于如何使用 Leaflet
实现一个基本地图的流程,参考 从 0 到 1 快速实现海外地图接入

而 Leaflet.markercluster
是一款为 Leaflet
地图库开发的标记点聚类插件,旨在提供美观且动画效果的标记聚类功能。它通过将地图上靠近的标记分组来提高用户体验,特别是在标记数量众多的情况下,如下图所示:

2. 为什么需要标记点聚类?
在地图上显示大量标记点时,会导致以下问题:
- 地图变得拥挤,信息重叠
- 性能下降,影响用户体验
- 难以快速定位和查看特定标记
- 视觉混乱,降低地图可读性
标记点聚类可以将这些标记点聚合在一起,形成一个聚类点,同时显示该聚类点的数量或其他信息,从而提高地图的可读性和交互性。
3. Leaflet.markercluster 的优势
- 性能优化:通过聚类减少实际渲染的标记点数量
- 交互友好:提供平滑的动画效果和直观的交互方式
- 高度可定制:支持自定义样式、行为和事件处理
- 兼容性好:支持各种地图服务提供商
- 维护活跃:持续更新和 bug 修复
三、如何使用 Leaflet.markercluster
1. 使用 npm 安装
适用于 Node.js
项目,通过 npm
安装 Leaflet
和 Leaflet.markercluster
,同时也可以引入类型定义文件,用于 TypeScript
项目。
bash
# 安装 Leaflet 和 Leaflet.markercluster
npm install leaflet leaflet.markercluster
# 安装类型定义文件,用于 TypeScript 项目
npm install @types/leaflet @types/leaflet.markercluster
通过以上的命令可以在项目中引入 Leaflet
和 Leaflet.markercluster
,同时也可以引入类型定义文件,用于 TypeScript
项目。
在项目中引入:
js
import L from 'leaflet'
import 'leaflet.markercluster'
import 'leaflet/dist/leaflet.css'
import 'leaflet.markercluster/dist/MarkerCluster.css'
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
本文使用的
Leaflet
相关版本是:[email protected]
[email protected]
2. 或使用 CDN 引入
适用于不需要本地安装的场景,直接引入 CDN 链接,在 HTML 中引入相关的样式和脚本即可。
html
<!-- 引入leaflet样式和js -->
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/leaflet.css"
/>
<script src="https://unpkg.com/[email protected]/dist/leaflet-src.js"></script>
<!-- 引入leaflet.markercluster样式和js -->
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/MarkerCluster.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/MarkerCluster.Default.css"
/>
<script src="https://unpkg.com/[email protected]/dist/leaflet.markercluster-src.js"></script>
3. 在项目中使用
通过上面的步骤将插件引入成功后,接下来可以初始化地图和聚合图层
javascript
// 初始化地图
var map = L.map('map').setView([38, -8], 7)
// 添加瓦片图层
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map)
// 创建聚合图层
var markers = L.markerClusterGroup()
// 添加标记点
for (let i = 0; i < 1000; i++) {
const marker = L.marker([getRandom(37, 39), getRandom(-9.5, -6.5)])
markers.addLayer(marker)
}
// 将聚合图层添加到地图
map.addLayer(markers)
// 随机坐标生成函数
function getRandom(min, max) {
return Math.random() * (max - min) + min
}
四、可选配置选项
Leaflet.MarkerCluster
的常用配置选项有很多,在这里我用 Cursor 罗列了几个常用的配置项,在项目中体验一下如何进行配置


1. 常用配置
javascript
const markerClusterLayer = L.markerClusterGroup({
showCoverageOnHover: false, // 鼠标悬停时是否显示覆盖范围
zoomToBoundsOnClick: true, // 点击聚合点时是否缩放至合适级别
spiderfyOnMaxZoom: true, // 最大缩放级别时是否展开聚合点
disableClusteringAtZoom: 18, // 超过该缩放级别时禁用聚合
maxClusterRadius: 50, // 聚合半径(像素)
chunkedLoading: true, // 是否启用分块加载
chunkInterval: 200, // 分块加载的时间间隔
chunkDelay: 50, // 分块加载的延迟时间
animate: true, // 是否启用动画效果
animateAddingMarkers: true, // 是否启用添加标记时的动画
removeOutsideVisibleBounds: true, // 是否移除可视区域外的标记
spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 } // 蜘蛛线样式
})
2. 自定义聚合样式
css
/* 自定义聚合点样式 */
.marker-cluster-small div {
background-color: rgba(4, 241, 205, 0.7) !important;
color: #fff;
}
.marker-cluster-medium div {
background-color: rgba(0, 167, 254, 0.7) !important;
color: #fff;
}
.marker-cluster-large div {
background-color: rgba(254, 179, 0, 0.7) !important;
color: #fff;
}
/* 自定义聚合点悬停效果 */
.marker-cluster-small:hover div {
background-color: rgba(4, 241, 205, 0.9) !important;
}
.marker-cluster-medium:hover div {
background-color: rgba(0, 167, 254, 0.9) !important;
}
.marker-cluster-large:hover div {
background-color: rgba(254, 179, 0, 0.9) !important;
}
配置完以上的代码,可以看到效果图更新了,如下图所示:

五、Vue 项目中使用
1. 安装依赖
bash
# 安装 Leaflet 和 Leaflet.markercluster
npm install leaflet leaflet.markercluster
# 安装类型定义文件,用于 TypeScript 项目
npm install @types/leaflet @types/leaflet.markercluster
2. 组件中引入
javascript
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
import 'leaflet.markercluster/dist/MarkerCluster.css'
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
import 'leaflet.markercluster'
3. Vue 组件示例
vue
<template>
<div id="map" style="height: 500px;"></div>
</template>
<script>
export default {
mounted() {
this.initMap()
},
methods: {
initMap() {
// 地图初始化代码
const map = L.map('map').setView([39.9042, 116.4074], 10)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(
map
)
const markers = L.markerClusterGroup()
// 添加标记点数据
// ...
map.addLayer(markers)
}
}
}
</script>
六. 完整源码示例
注意:源码加载的地图图层是 OpenStreetMap,要使用 🪜 才能正确渲染地图!也可以自己修改为天地图图层。
html
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import L from 'leaflet'
import 'leaflet.markercluster'
import 'leaflet/dist/leaflet.css'
import 'leaflet.markercluster/dist/MarkerCluster.css'
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
let map: L.Map | null = null
// 随机点位
const getRandomLatLng = (): L.LatLng => {
if (!map) return L.latLng(0, 0)
const bounds = map.getBounds()
const southWest = bounds.getSouthWest()
const northEast = bounds.getNorthEast()
const lngSpan = northEast.lng - southWest.lng
const latSpan = northEast.lat - southWest.lat
return L.latLng(
southWest.lat + latSpan * Math.random(),
southWest.lng + lngSpan * Math.random()
)
}
// 初始化地图
const initMap = () => {
const tiles = L.tileLayer(
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
{
maxZoom: 10,
minZoom: 1,
attribution:
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Points © 2012 LINZ'
}
)
const latlng = L.latLng(39.904_53, 116.424_391)
const baseLayers = L.layerGroup([tiles])
map = L.map('map', { center: latlng, zoom: 3 })
map.addLayer(baseLayers)
const markers = L.markerClusterGroup({
showCoverageOnHover: false,
zoomToBoundsOnClick: true,
spiderfyOnMaxZoom: true,
disableClusteringAtZoom: 18,
maxClusterRadius: 50,
chunkedLoading: true,
chunkInterval: 200,
chunkDelay: 50,
animate: true,
animateAddingMarkers: true,
removeOutsideVisibleBounds: true,
spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 }
})
// 随机生成2000个点位
for (let i = 0; i < 2000; i++) {
const latlng = getRandomLatLng()
const title = `第${i}个点`
const marker = L.marker(latlng, { title })
marker.bindPopup(title)
markers.addLayer(marker)
}
map.addLayer(markers)
}
onMounted(() => {
initMap()
})
onUnmounted(() => {
if (map) {
map.remove()
map = null
}
})
</script>
<template>
<div class="map-container">
<div id="map"></div>
</div>
</template>
<style scoped>
.map-container {
position: relative;
width: 100%;
height: 100%;
}
#map {
width: 100%;
height: 100%;
overflow: hidden;
}
/* 自定义聚合点样式 */
.marker-cluster-small div {
background-color: rgba(4, 241, 205, 0.7) !important;
color: #fff;
}
.marker-cluster-medium div {
background-color: rgba(0, 167, 254, 0.7) !important;
color: #fff;
}
.marker-cluster-large div {
background-color: rgba(254, 179, 0, 0.7) !important;
color: #fff;
}
/* 自定义聚合点悬停效果 */
.marker-cluster-small:hover div {
background-color: rgba(4, 241, 205, 0.9) !important;
}
.marker-cluster-medium:hover div {
background-color: rgba(0, 167, 254, 0.9) !important;
}
.marker-cluster-large:hover div {
background-color: rgba(254, 179, 0, 0.9) !important;
}
</style>
七、性能优化建议
- 数据加载优化
- 使用分块加载(chunkedLoading)
- 设置合适的加载间隔(chunkInterval)和延迟(chunkDelay)
- 考虑使用虚拟滚动或分页加载
- 渲染优化
- 移除可视区域外的标记(removeOutsideVisibleBounds)
- 使用合适的聚合半径(maxClusterRadius)
- 设置合适的缩放级别(disableClusteringAtZoom)
- 内存管理
- 及时清理不需要的标记点
- 使用 WeakMap 存储标记点数据
- 在组件卸载时清理地图实例
- 事件处理
- 使用事件委托
- 防抖和节流处理
- 及时移除事件监听器
八、总结
Leaflet.markercluster
是 Leaflet
地图库的一个强大扩展,通过简单的配置即可实现标记点的聚类功能。它不仅提升了大量标记点场景下的地图性能,还通过动画效果和自定义样式增强了用户体验。无论是在原生 JS 项目还是 Vue 等框架中,都能方便地集成和使用。
Leaflet
作为一款轻量级、高性能的开源地图库,凭借其简洁的 API 和丰富的插件生态,在 Web 地图开发领域广受青睐。而 Leaflet.markercluster
插件则进一步扩展了 Leaflet
的能力,通过智能聚合算法将空间上邻近的标记点合并为聚类组(Cluster),并随着地图缩放级别动态调整聚合状态------缩放级别较低时展示聚合组概览,缩放级别提高后自动拆分显示单个标记点。
这种方式不仅有效解决了标记点密集重叠的问题,还通过平滑的动画过渡和可定制的聚合样式,提升地图交互的直观性。