Leaflet.markercluster使用

313 阅读4分钟

Leaflet.markercluster 超实用指南!Vue/HTML 直接抄作业 🗺️

Leaflet.markercluster 这插件真的香到爆!专门解决地图上标记点堆成“马赛克”看不清的问题,把密集标记自动“打包”成簇,缩放地图还能智能展开/合并,做地图可视化项目直接省一半力~ 这篇用大白话讲透用法,Vue 和 HTML 都能直接抄代码,还补了效果图描述和表情包,新手也能轻松上手!

一、先搞定安装:Vue 项目/HTML 直接用 🛠️

1. Vue 项目(推荐 npm 安装)

打开终端,在你的 Vue 项目根目录敲这行,直接装依赖(记得同时装 Leaflet 核心库,少一个都跑不起来~):

npm install leaflet leaflet.markercluster --save

2. HTML 直接用(不用装,抄 CDN 就行)

不想搞复杂安装?直接粘这些链接,一秒引入所有资源,省事儿!

<!-- 先引样式:顺序不能乱!先 Leaflet 再 MarkerCluster -->
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.Default.css">

<!-- 再引 JS:同样先核心后插件 -->
<script src="https://unpkg.com/leaflet"></script>
<script src="https://unpkg.com/leaflet.markercluster/dist/leaflet.markercluster.js"></script>

关键提醒 ⚠️

  • 引入顺序千万别搞反!先装 Leaflet 再装 MarkerCluster,不然会报“找不到 xxx”的错,踩过坑的都懂~
  • Vue 项目里样式没生效?大概率是漏引 CSS 文件了,下面 Vue 示例里会重点讲!

二、基础用法:3 步实现标记聚合(附效果图描述)✨

核心逻辑超简单:创建地图 → 建“聚合容器” → 丢标记 → 挂地图,直接上能跑的代码,还有效果说明!

1. HTML 版(复制到 .html 文件就能运行)

<!DOCTYPE html>
<html>
<head>
    <title>地图标记聚合示例</title>
    <!-- 引入 CDN 资源 -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.Default.css">
    <!-- 给地图设大小,不然看不见! -->
    <style>
        #map { height: 600px; width: 100%; border: 1px solid #eee; }
    </style>
</head>
<body>
    <div id="map"></div>

    <script src="https://unpkg.com/leaflet"></script>
    <script src="https://unpkg.com/leaflet.markercluster/dist/leaflet.markercluster.js"></script>
    <script>
        // 1. 初始化地图:中心点设为伦敦,缩放级别13(数字越大越近)
        const map = L.map('map').setView([51.505, -0.09], 13);

        // 2. 添加地图底图(用免费开源的 OpenStreetMap,加载快还稳定)
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

        // 3. 建“聚合容器”:所有标记丢这里,自动聚合
        const markers = L.markerClusterGroup();

        // 4. 生成 100 个随机标记(实际项目换你的真实坐标)
        for (let i = 0; i < 100; i++) {
            const lat = 51.5 + Math.random() * 0.1; // 纬度随机偏移
            const lng = -0.09 + Math.random() * 0.1; // 经度随机偏移
            const marker = L.marker([lat, lng]).bindPopup(`这是第 ${i+1} 个标记`);
            markers.addLayer(marker);
        }

        // 5. 挂到地图上,搞定!
        map.addLayer(markers);
    </script>
</body>
</html>
效果描述 📸

9c6fea69-8471-48f5-b2b2-71445b5abb1b.png

2. Vue 版(Vue2/Vue3 都兼容,亲测能用)

<template>
  <!-- 地图容器,必须设高度! -->
  <div id="vue-map" class="map-container"></div>
</template>

<script>
// 引入核心库和插件
import L from 'leaflet';
// 千万别漏引 CSS!不然簇的样式会乱成一团
import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
import 'leaflet.markercluster';

export default {
  mounted() {
    // 组件挂载后再初始化地图(DOM 得先存在才行)
    this.initMap();
  },
  methods: {
    initMap() {
      // 1. 初始化地图:中心点换成北京([39.9042, 116.4074]),缩放级别12
      const map = L.map('vue-map').setView([39.9042, 116.4074], 12);

      // 2. 添加底图(也可以换天地图、高德底图,看项目需求)
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

      // 3. 创建聚合容器(后面会讲怎么加配置)
      const markers = L.markerClusterGroup();

      // 4. 加真实标记(实际项目里循环接口返回的数据就行)
      const markerData = [
        { lat: 39.9042, lng: 116.4074, name: '天安门' },
        { lat: 39.9142, lng: 116.4174, name: '故宫' },
        { lat: 39.9242, lng: 116.4274, name: '王府井' },
        { lat: 39.9842, lng: 116.3874, name: '颐和园' },
        // 可以加更多点位...
      ];

      markerData.forEach(item => {
        const marker = L.marker([item.lat, item.lng]).bindPopup(item.name);
        markers.addLayer(marker);
      });

      // 5. 挂载到地图
      map.addLayer(markers);
      
      // 存一下地图实例,后面销毁用
      this.map = map;
    }
  },
  beforeUnmount() {
    // 组件卸载时销毁地图,避免内存泄漏(重要!)
    if (this.map) {
      this.map.remove();
    }
  }
};
</script>

<style scoped>
.map-container {
  height: 700px;
  width: 100%;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1); // 加个阴影,颜值更高
}
</style>
效果描述 📸

Vue 页面加载后,北京地图上会显示几个灰色簇,比如“天安门”“故宫”“王府井”因为离得近,会被聚合成一个簇(显示数字“3”),“颐和园”单独一个簇(显示数字“1”)~ 点击簇会自动缩放地图,展开里面的单个标记;点击标记会弹出“天安门”这类名称,交互超丝滑!

三、实用配置:按需调整聚合行为(不用死记,抄就行)⚙️

创建“聚合容器”时加配置,能改簇的大小、点击效果这些,我把常用的配置整理好了,注释写得明明白白:

const markers = L.markerClusterGroup({
  showCoverageOnHover: false, // 鼠标放簇上,不显示覆盖范围(默认显示,关了更清爽)
  zoomToBoundsOnClick: true, // 点击簇,自动缩放到簇的范围(默认 true,很好用)
  spiderfyOnMaxZoom: true, // 缩到最大级别,簇自动变“蜘蛛状”(避免标记叠在一起)
  spiderLegPolylineOptions: {
    weight: 2, // 蜘蛛腿的粗细(默认 1.5,调粗点更明显)
    color: '#ff6666', // 蜘蛛腿颜色(默认深灰色,换个红色更显眼)
    opacity: 0.7 // 透明度(0-1,看个人喜好)
  },
  maxClusterRadius: 70, // 簇的最大半径(像素),值越大,一个簇里的标记越多
  disableClusteringAtZoom: 16 // 缩放级别超过 16,就不聚合了(直接显示所有标记)
});
效果描述 📸

加了配置后,鼠标放簇上不会出现半透明的矩形(清爽多了);点击簇会平滑缩放到刚好能看到所有标记的范围;缩到 16 级时,所有簇都会消失,直接显示单个标记;“蜘蛛腿”变成红色粗线,展开的标记一眼就能看清~

四、自定义样式:让簇图标不单调(颜值党必看)🎨

默认的簇是灰色气泡,不好看?可以自己改颜色、形状,甚至加文字,直接抄这段代码:

const markers = L.markerClusterGroup({
  iconCreateFunction: function(cluster) {
    // cluster.getChildCount() 能拿到簇里的标记数量
    const count = cluster.getChildCount();
    
    // 按数量分颜色:少→绿,中→黄,多→红(直观区分密度)
    let color = '#2ecc71'; // 绿色(<20 个标记)
    if (count > 50) color = '#e74c3c'; // 红色(>50 个标记)
    else if (count > 20) color = '#f39c12'; // 黄色(20-50 个标记)

    // 自定义图标:圆形、带数字、有阴影,颜值拉满
    return L.divIcon({
      className: 'my-custom-cluster',
      html: `<div style="background: ${color}; color: white; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2);">${count}</div>`,
      iconSize: [36, 36], // 图标大小(宽高一致才是正圆)
      iconAnchor: [18, 18] // 图标中心点(不然会偏移到一边)
    });
  }
});
效果描述 📸

改完后,簇图标变成了彩色圆形:标记少的是绿色(比如“颐和园”单独一个,绿色圆里写“1”),中等数量是黄色(比如 25 个标记,黄色圆里写“25”),数量多的是红色(比如 60 个标记,红色圆里写“60”)~ 圆上还有阴影,显得有立体感,比默认灰色气泡好看太多!

五、进阶功能:分类聚合(不同类型标记用不同图标)📦

如果地图上有多种标记(比如景点、公园、学校),想让不同类型的簇用不同图标?安排!参考 CSDN 博主的实现思路,核心是按标记类型自定义簇图标:

// 1. 先给标记加“类型”属性(比如 type:1=景点,2=公园,3=学校)
const markerData = [
  { lat: 39.9042, lng: 116.4074, name: '天安门', type: 1 }, // 景点
  { lat: 39.9342, lng: 116.3974, name: '奥林匹克森林公园', type: 2 }, // 公园
  { lat: 39.9542, lng: 116.4374, name: '北京大学', type: 3 }, // 学校
  // 更多标记...
];

// 2. 创建聚合容器时,按类型生成不同图标
const markers = L.markerClusterGroup({
  iconCreateFunction: function(cluster) {
    // 先拿到簇里所有标记的类型(假设一个簇里只有一种类型,实际可加判断)
    const allMarkers = cluster.getAllChildMarkers();
    const markerType = allMarkers[0].options.type; // 取第一个标记的类型
    
    // 按类型定义图标内容(用文字或图片区分)
    let iconContent, color;
    switch(markerType) {
      case 1: // 景点
        iconContent = '景';
        color = '#3498db'; // 蓝色
        break;
      case 2: // 公园
        iconContent = '园';
        color = '#2ecc71'; // 绿色
        break;
      case 3: // 学校
        iconContent = '校';
        color = '#9b59b6'; // 紫色
        break;
    }

    // 生成对应类型的簇图标
    return L.divIcon({
      className: 'category-cluster',
      html: `<div style="background: ${color}; color: white; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold;">${iconContent}</div>`,
      iconSize: [36, 36],
      iconAnchor: [18, 18]
    });
  }
});

// 3. 循环添加标记时,把“类型”存到 marker 的 options 里
markerData.forEach(item => {
  const marker = L.marker([item.lat, item.lng], {
    type: item.type // 存类型
  }).bindPopup(`${item.name}(${item.type === 1 ? '景点' : item.type === 2 ? '公园' : '学校'})`);
  markers.addLayer(marker);
});
效果描述 📸

这样一来,地图上会出现三种颜色的簇:蓝色带“景”字的是景点簇,绿色带“园”字的是公园簇,紫色带“校”字的是学校簇~ 一眼就能区分不同类型的标记,再也不用点进去看详情了,超方便!

六、常见问题:踩过的坑都给你填好了 🚫

1. Vue 里报错“spiderfy is not a function”

  • 原因:你把 spiderfy() 用到了“聚合容器”上,这个方法是给单个簇用的,不是给容器用的!
  • 解决:通过点击事件拿到簇对象再调用,比如:
markers.on('clusterclick', function(event) {
  const cluster = event.layer; // 这才是单个簇对象
  cluster.spiderfy(); // 现在就能正常展开了
});

2. 点击标记,簇自动收缩了(巨烦!)

  • 原因:标记的点击事件“冒泡”到了簇上,触发了收缩
  • 解决:创建标记时加 bubblingMouseEvents: false,阻止事件冒泡:
// 普通标记
L.marker([lat, lng], { bubblingMouseEvents: false }).bindPopup('点击不收缩');
// 圆形标记
L.circleMarker([lat, lng], { bubblingMouseEvents: false }).bindPopup('点击不收缩');

3. 想知道总共加了多少个标记

  • 直接用这个方法,不管聚没聚合都能拿到总数:
const total = markers.getLayers().length;
console.log('总共的标记数:', total); // 比如输出 100,就是加了 100 个标记

4. 标记太多(比如 1 万个),地图卡顿

  • 优化技巧(亲测有效!):

    • 关掉 showCoverageOnHover: false(少渲染一个覆盖层,省性能)
    • 调大 maxClusterRadius(比如设为 100,减少簇的数量)
    • disableClusteringAtZoom: 14(缩放级别高了就不聚合,减少计算)
    • 分批加载:只加载当前地图视野内的标记(用 Leaflet 的 moveend 事件监听视野变化)

七、必备资源:官方文档+参考链接 📚

总结

Leaflet.markercluster 真的是地图项目的“救星”!核心就是“聚合容器”这个概念,把标记丢进去,剩下的聚合逻辑它全帮你搞定~ Vue 和 HTML 都能直接抄我给的代码,改改坐标和样式就能用,有问题看官方文档或者评论区问我~ 赶紧试试,让你的地图再也不“乱糟糟”! 🎉