You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Leaflet扩展CircleMarker的自定义标记如何实现分组、高亮及弹窗绑定?

Leaflet自定义标记交互与弹窗异常修复方案

问题表现

在GeoJSON多边形上渲染CustomPoint样式的自定义CircleMarker时,出现两个异常:

  • 所有标记弹窗内容完全一致
  • 鼠标悬停时触发组内全部标记高亮,而非仅当前交互标记
    选中标记时的效果
    弹窗效果

问题原因

  • 事件与弹窗绑定到了整个L.featureGroup容器上,而非每个单独的CustomPoint实例,触发事件时会作用于全组元素,且弹窗内容会被最后一次遍历的feature属性覆盖
  • 生成的CustomPoint实例未关联对应GeoJSON的feature属性,高亮逻辑调用时无法读取对应要素数据
  • onEachFeature遍历逻辑中重复对featureGroup绑定事件,导致事件多次触发

修复方案

  1. 创建CustomPoint实例时手动关联对应feature属性
  2. 将鼠标交互事件、弹窗绑定到每个单独的CustomPoint实例上,移除对容器组的全局绑定
  3. 保留featureGroup仅作为标记容器使用,不承担交互绑定逻辑

修正后完整代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
html, body {
height: 100%;
margin: 0;
}
#map {
width: 100%;
height: 100%;
}
</style>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet.js"></script>
 <!--Load plugin -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-ajax/2.1.0/leaflet.ajax.min.js"></script>
</head>
<body>
<div id="map"></div>
<script>
L.Canvas.include({
_updateCustomPoint: function (layer) {
    if (!this._drawing || layer._empty()) { return; }
    var p = layer._point,
        ctx = this._ctx,
        r = Math.max(Math.round(layer._radius), 1);
    this._drawnLayers[layer._leaflet_id] = layer;
   ctx.beginPath()
    ctx.moveTo(p.x, p.y)
    ctx.lineTo(p.x - 0.58 * r, p.y - r)
    ctx.arc(p.x, p.y - 2 * r, r, -Math.PI * 1.161, Math.PI * 0.161)
    ctx.closePath()
    this._fillStroke(ctx, layer)
},
});
var CustomPoint = L.CircleMarker.extend({
_updatePath: function () {
    this._renderer._updateCustomPoint(this);
},
 _containsPoint: function(p) {
    let r = this._radius
    let insideCircle =
        p.add([0, r * 2]).distanceTo(this._point) <= r + this._clickTolerance()
    let a = this._point,
        b = a.subtract([0.58 * r, r]),
        c = a.subtract([-0.58 * r, r])
    let insideTriangle = true
    let ap_x = p.x - a.x
    let ap_y = p.y - a.y
    let p_ab = (b.x - a.x) * ap_y - (b.y - a.y) * ap_x > 0
    let p_ac = (c.x - a.x) * ap_y - (c.y - a.y) * ap_x > 0
    let p_bc = (c.x - b.x) * (p.y - b.y) - (c.y - b.y) * (p.x - b.x) > 0
    if (p_ac === p_ab) {
        insideTriangle = false
    }
    if (p_bc !== p_ab) {
        insideTriangle = false
    }
    return insideTriangle || insideCircle
},
});
var myRenderer;
var minZoom = 0,
    maxZoom = 15;
var map = L.map('map', {
    minZoom: minZoom,
    maxZoom: maxZoom
}).setView([49.79, 19], 15);
L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
    continuousWorld: false,
    minZoom: 0,
    }).addTo(map);
myRenderer = L.canvas({ padding: 0.5 });
function style(feature) {
return {
    radius: 13,
    fillColor: 'green',
    color: 'white',
    weight: 1,
    opacity: 1,
    fillOpacity: 1,
    renderer: myRenderer
};
}
function highlightStyle(feature) {
return {
    radius: 13,
    fillColor: 'red',
    color: "#FFCE00",
    weight: 1,
    opacity: 1,
    fillOpacity: 0.9
};
}
function resetHighlight(e) {
var layer = e.target;
dotStyleDefault = style(layer.feature);
layer.setStyle(dotStyleDefault);
}
function highlight(e) {
var layer = e.target;
dotStyleHighlight = highlightStyle(layer.feature);
layer.setStyle(dotStyleHighlight);
if (!L.Browser.ie && !L.Browser.opera) {
    layer.bringToFront();
}
}
var Features = "https://api.npoint.io/b3db1a36dc59740cb056";
var featureGroup = new L.GeoJSON.AJAX(Features, {
            onEachFeature: onEachFeature,
}).addTo(map);
var markerGroup = L.featureGroup().addTo(map);
function onEachFeature(feature, layer) {
  const center = layer.getBounds().getCenter()
  if (1 == feature.properties.N) {
    // 创建自定义标记并关联对应feature
    const customMarker = new CustomPoint(center, style(feature))
    customMarker.feature = feature
    // 单独绑定当前标记的交互事件
    customMarker.on({
        mouseover: highlight,
        mouseout: resetHighlight
    })
    // 单独绑定当前标记的专属弹窗
    const popup = '<table style="width:110px"><tbody><tr><td><div><b>Marker: </b> </div></td><td><div>' + feature.properties.cena +
        '</div></td></tr><tr class><td><div><b>Group:</b></div></td><td> <div>' + feature.properties.area +
        '</div></td></tr><tr><td><div><b>X:</b></div></td><td><div>' +
        '</div></td></tr><tr><td><div><b>Y:</b></div></td><td><div>'  +
        '</div></td></tr></tbody></table>'
    customMarker.bindPopup(popup)
    customMarker.addTo(markerGroup)
  }
};
</script>
</body>
</html>

内容的提问来源于stack exchange,提问作者Mateusz Kruk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.17 15:24:04