如何在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
相关产品推荐
相关产品推荐

