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

如何在mapbox-gl-js中为标记添加点击触发的脉冲动画半径圆

Mapbox 标记随地图缩放适配真实地理尺寸+脉冲动画实现方案

核心实现思路:

  • 抛弃CSS实现的固定像素脉冲圆,改用Mapbox原生circle图层实现脉冲范围,该图层支持地理单位匹配,缩放地图时会自动适配真实地理尺寸
  • 保留原有的HTML自定义标记逻辑,满足自定义图标嵌入的需求
  • 点击标记时通过更新GeoJSON数据源属性控制脉冲圆的显示与动画

1. 完整修改实现代码

你可以直接替换原有map.on('load')内的对应逻辑:

map.on('load', () => {
    // Insert the layer beneath any symbol layer.
    const layers = map.getStyle().layers;
    const labelLayerId = layers.find(
        (layer) => layer.type === 'symbol' && layer.layout['text-field']
    ).id;

    // Places JSON
    const geojson = {
        'type': 'FeatureCollection',
        'features': [
            {
                'type': 'Feature',
                'properties': {
                    'iconSize': [40, 40],
                    'icon': 'house',
                    'url': '1',
                    'isActive': false, // 新增激活状态字段
                    'description':
                        `
                        <div class="text ff--open-sans">
                            <h5 class="mb-1 fw--700">Продажа квартиры</h5>
                            <img src="/design/map_1/images/flat.jpg" class="img--responsive border-radius">
                            <p class="mb-2 mt-1">
                                Продается с мебелью и техникой,заходи и живи.
                            </p>
                            <span class="fs--14 color--dark">Характеристики</span>
                            <div class="d-flex color--gray gutter-y-5 fs--13 flex-column">
                                <div class="d-flex mb-0">
                                    <span>Агенство</span>
                                    <span class="mx-2">—</span>
                                    <span class="color--dark">Капитал</span>
                                </div>
                                <div class="d-flex mb-0">
                                    <span>Комнаты</span>
                                    <span class="mx-2">—</span>
                                    <span class="color--dark"> 2 </span>
                                </div>
                                <div class="d-flex mb-0">
                                    <span>Этаж</span>
                                    <span class="mx-2">—</span>
                                    <span class="color--dark"> 3 </span>
                                </div>
                                <div class="d-flex mb-0">
                                    <span>Ремонт</span>
                                    <span class="mx-2">—</span>
                                    <span class="color--dark"> Евро </span>
                                </div>
                            </div>
                        </div>
                        `
                },
                'geometry': {
                    'type': 'Point',
                    'coordinates': [28.83342580788326, 45.3389572391001]
                },
                'id': 0 // 新增唯一ID
            },
            {
                'type': 'Feature',
                'properties': {
                    'icon': 'flat',
                    'isActive': false, // 新增激活状态字段
                    'description':
                        `
                        <div class="text ff--open-sans">
                            <h5 class="mb-1 fw--700">Продажа квартиры</h5>
                            <img src="/design/map_1/images/flat.jpg" class="img--responsive border-radius">
                            <p class="mb-2 mt-1">
                                Продается с мебелью и техникой,заходи и живи.
                            </p>
                            <span class="fs--14 color--dark">Характеристики</span>
                            <div class="d-flex color--gray gutter-y-5 fs--13 flex-column">
                                <div class="d-flex mb-0">
                                    <span>Агенство</span>
                                    <span class="mx-2">—</span>
                                    <span class="color--dark">Капитал</span>
                                </div>
                                <div class="d-flex mb-0">
                                    <span>Комнаты</span>
                                    <span class="mx-2">—</span>
                                    <span class="color--dark"> 2 </span>
                                </div>
                                <div class="d-flex mb-0">
                                    <span>Этаж</span>
                                    <span class="mx-2">—</span>
                                    <span class="color--dark"> 3 </span>
                                </div>
                                <div class="d-flex mb-0">
                                    <span>Ремонт</span>
                                    <span class="mx-2">—</span>
                                    <span class="color--dark"> Евро </span>
                                </div>
                            </div>
                        </div>
                        `
                },
                'geometry': {
                    'type': 'Point',
                    'coordinates': [28.820403408543825, 45.35615240244837]
                },
                'id': 1 // 新增唯一ID
            }
        ]
    };

    // Add places on map
    map.addSource('places', {
        'type': 'geojson',
        'data': geojson
    });

    // 新增脉冲圆图层,放在标记下层
    map.addLayer({
      id: 'pulse-circle',
      type: 'circle',
      source: 'places',
      filter: ['==', ['get', 'isActive'], true], // 只显示选中标记的脉冲圆
      paint: {
        // 50米真实半径,随地图缩放自动转换为对应像素
        'circle-radius': ['*', 50, ['meters-to-pixels', 1]],
        'circle-color': '#2563eb',
        'circle-opacity': 0.3,
        'circle-pitch-alignment': 'map', // 倾斜地图时圆贴合地平面
        'circle-stroke-width': 2,
        'circle-stroke-color': '#2563eb',
        'circle-stroke-opacity': 0.6
      }
    }, labelLayerId);

    // 脉冲动画控制
    let animationFrame;
    function animatePulse() {
      // 动态修改透明度和半径实现扩散效果
      const timestamp = performance.now() / 1000;
      const opacity = 0.6 - (timestamp % 1) * 0.6;
      const radiusOffset = (timestamp % 1) * 20; // 扩散偏移量
      map.setPaintProperty('pulse-circle', 'circle-opacity', opacity);
      map.setPaintProperty('pulse-circle', 'circle-radius', ['*', ['+', 50, radiusOffset], ['meters-to-pixels', 1]]);
      animationFrame = requestAnimationFrame(animatePulse);
    }
    animatePulse();
    // 地图销毁时停止动画避免内存泄漏
    map.on('remove', () => {
      cancelAnimationFrame(animationFrame);
    });

    // Add 3D 'building' layer
    map.addLayer(
        {
            'id': 'add-3d-buildings',
            'source': 'composite',
            'source-layer': 'building',
            'filter': ['==', 'extrude', 'true'],
            'type': 'fill-extrusion',
            'minzoom': 15,
            'paint': {
                'fill-extrusion-color': '#aaa',
                'fill-extrusion-height': [
                    'interpolate',
                    ['linear'],
                    ['zoom'],
                    15,
                    0,
                    15.05,
                    ['get', 'height']
                ],
                'fill-extrusion-base': [
                    'interpolate',
                    ['linear'],
                    ['zoom'],
                    15,
                    0,
                    15.05,
                    ['get', 'min_height']
                ],
                'fill-extrusion-opacity': 0.6
            }
        },
        labelLayerId
    );

    // Set building number labels color
    map.setPaintProperty('building-number-label', 'text-color', 'black');

    // Set building number labels size
    map.setLayoutProperty('building-number-label', 'text-size', 16);

    // Custom html for places on the map.
    for (const marker of geojson.features) {

        // Create a DOM element for each marker.
        const el = document.createElement('div');
        const ico = marker.properties.icon;

        el.className = `map-marker marker-icon-${ico}`;
        el.style.width = `30px`;
        el.style.height = `30px`;

        // Add markers to the map.
        new mapboxgl.Marker(el)
            .setLngLat(marker.geometry.coordinates)
            .addTo(map);

        // 移除原有CSS脉冲元素,改用原生图层实现
        $(el).html('<div class="js-show-property marker-content" data-url="'+marker.properties.url+'"><div class="pin bounce"><div class="icon"></div></div></div>');

        // Add active marker
        el.addEventListener('click', () =>{
            // 重置所有标记激活状态
            geojson.features.forEach(f => f.properties.isActive = false);
            // 设置当前标记为激活
            marker.properties.isActive = true;
            // 更新数据源触发脉冲圆刷新
            map.getSource('places').setData(geojson);
            // 切换标记自身激活样式
            $('.map-marker').removeClass('active');
            $(el).addClass('active');
        });
    }
});

自定义调整说明

  • 脉冲圆真实半径:将代码中所有的50修改为你需要的米数即可
  • 脉冲效果:调整动画逻辑里的透明度、扩散偏移量参数可以修改动画速度、扩散幅度
  • 样式:修改pulse-circle图层的paint属性可以自定义圆的颜色、边框等样式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 15:57:02