百度地图API实战:精准定位与性能优化技巧分享

UI阳阳 交互 阅读 744
赞 7 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

上个月接了个需求,要在移动端展示门店分布,用户能点进地图看具体位置、导航,还能筛选不同类型的门店。老板一开始说“用高德吧”,但后端同事顺手查了下百度地图的API文档,发现他们有个现成的聚合点功能,能自动把密集的点合并成一个数字气泡,点开再展开——这对我们这种几百家门店的场景太香了。再加上之前项目里用过百度地图,SDK加载也快,就拍板用了。

百度地图API实战:精准定位与性能优化技巧分享

其实现在想想,选型时有点草率。没仔细看移动端的交互限制,也没压测性能,结果后面折腾了整整三天才搞定。

最大的坑:touchmove滚动失效

页面结构很简单:顶部是筛选栏,中间是地图容器,下面是一堆门店卡片。问题来了——当用户手指在地图区域滑动时,整个页面的垂直滚动直接被锁死了。安卓机上尤其严重,iOS还好点,但偶尔也会卡住。

开始我以为是百度地图默认阻止了原生事件,查了文档发现它确实会监听 touchmove 并调用 preventDefault()。官方建议是在初始化时加个 { enableScrollWheelZoom: false } 之类的配置,但试了根本没用。

折腾了半天,翻到一篇老外的博客(中文社区基本没人提这问题),说要手动给地图容器加 CSS 属性:

.map-container {
  touch-action: none;
}

加上去之后,页面滚动恢复了!但新问题又来了:地图自己不能拖动了……因为 touch-action: none 把所有手势都禁了。

最后灵机一动:能不能只在用户快速滑动时允许页面滚动,慢速拖动时交给地图?于是写了个判断逻辑:监听 touchstarttouchend,计算滑动速度和方向。如果垂直方向位移大、速度快,就放行原生滚动;否则让地图处理。

代码大概长这样:

let startY = 0;
let isMapDragging = false;

const mapContainer = document.getElementById('map');
mapContainer.addEventListener('touchstart', (e) => {
  startY = e.touches[0].clientY;
  isMapDragging = false;
});

mapContainer.addEventListener('touchmove', (e) => {
  const currentY = e.touches[0].clientY;
  const deltaY = currentY - startY;
  
  // 如果垂直滑动距离超过10px,且不是地图主动拖拽(比如点击后拖动)
  if (Math.abs(deltaY) > 10 && !isMapDragging) {
    // 允许页面滚动
    return;
  }
  
  // 否则,阻止默认行为,让地图处理
  e.preventDefault();
});

// 百度地图内部拖动时会触发这个事件
map.addEventListener('moving', () => {
  isMapDragging = true;
});

亲测有效!虽然有点 hack,但至少用户不会骂“怎么划不动了”。

聚合点 + 自定义气泡的兼容性问题

百度地图的 MarkerClusterer 插件挺好用,但自定义气泡样式时踩了大坑。我想把聚合点的背景换成圆角矩形,文字居中,结果在 iOS Safari 上文字偏移得离谱。

查了好久才发现,百度地图内部用的是绝对定位 + transform 来控制气泡位置,而某些机型对 transform 的子元素 line-height 计算有 bug。最后妥协方案:不用 line-height,改用 flex 布局,并且给容器加固定宽高。

// 自定义聚合样式
const clusterStyle = [{
  url: 'data:image/svg+xml;utf8,<svg ...></svg>', // 用 SVG 内联避免跨域
  size: new BMap.Size(40, 40),
  anchor: new BMap.Size(20, 20)
}];

const markerCluster = new BMapLib.MarkerClusterer(map, {
  markers: allMarkers,
  styles: clusterStyle
});

这里注意我踩过好几次坑:别用外部图片 URL,否则 HTTPS 页面会报混合内容警告。直接用 data:image 或 base64 最稳。

数据加载的优化:别一次性塞500个点

测试时后端直接返回了 482 个门店坐标,页面直接卡死。Chrome DevTools 显示 JS 执行时间超过 3 秒,用户看到的就是白屏。

后来调整了方案:先加载当前可视区域内的点,滚动或缩放地图时再动态请求周边数据。百度地图提供了 getBounds() 方法,可以拿到当前视口的经纬度范围:

function loadMarkersInViewport() {
  const bounds = map.getBounds();
  const ne = bounds.getNorthEast();
  const sw = bounds.getSouthWest();
  
  fetch(https://jztheme.com/api/stores?ne_lat=${ne.lat}&amp;ne_lng=${ne.lng}&amp;sw_lat=${sw.lat}&amp;sw_lng=${sw.lng})
    .then(res => res.json())
    .then(data => {
      // 清除旧标记,添加新标记
      // 注意:这里要防抖,避免频繁触发
    });
}

// 监听地图移动结束
map.addEventListener('dragend', debounce(loadMarkersInViewport, 300));
map.addEventListener('zoomend', debounce(loadMarkersInViewport, 300));

加了防抖之后,体验好多了。不过有个小问题一直没解决:当地图快速缩放时,偶尔会漏掉一些边界点。但影响不大,用户一般不会注意到,就没再深究。

回顾与反思

整体来说,百度地图在基础功能上够用,文档也还行,但移动端的细节处理真的需要自己填坑。做得好的地方:

  • 聚合点功能省了大量前端计算
  • API 调用简单,几行代码就能显示标记
  • 国内定位精度比高德差不太多

但也有明显不足:

  • 移动端手势冲突处理几乎没有说明
  • 自定义 UI 的灵活性差,很多样式靠覆盖 CSS 实现
  • 海量点渲染性能不如 Mapbox(但后者要自己搭服务,成本高)

如果重做一次,可能会考虑用腾讯地图——听说他们对移动端滚动支持更好。不过这次项目时间紧,能跑起来就行。

以上是我踩坑后的总结,希望对你有帮助。如果你有更好的解决方案,比如如何完美解决 touchmove 冲突,或者聚合点边界遗漏的问题,欢迎评论区交流!

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论