经纬志
首页 / 实战手册 / 交互

移动端地图手势冲突与滚动穿透

先看结论

手机上想拖地图,结果整页跟着滚;想双指缩放,页面被放大;地图在页面中间,永远滚不过去。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-27 · 引用本文

拖地图带动页面滚动

浏览器默认把单指拖动当作页面滚动。地图库虽然阻止了默认行为, 但前提是它的事件监听能拿到触摸事件并被允许调用 preventDefault。 如果外层容器设了 touch-action: pan-y 之类的属性, 浏览器会先接管手势,地图就拿不到了。

解决办法是给地图容器明确声明它自己处理所有手势:

#map {
  touch-action: none;
}

touch-action: none 会让容器内所有手势都交给 JS 处理, 页面在这个区域不再滚动。代价是用户在地图上没法滚动页面, 所以地图不应该占据整个屏幕高度的全部空间,要留出可滚动的区域。

双指缩放变成页面缩放

需要同时做两件事:禁用页面级的双指缩放,以及确保地图库能收到双指事件。

<meta name="viewport"
      content="width=device-width, initial-scale=1,
               maximum-scale=1, user-scalable=no">

user-scalable=no 会影响可访问性,只在对交互精度要求高的地图页使用。 更温和的做法是保留页面缩放,只给地图容器加 touch-action: none。

滚到地图位置就卡住

地图占满一屏宽度、又吃掉了所有手势,用户的手指落在上面就无法纵向滚动页面。 这是「地图墙」问题,在移动端非常影响体验。

做法说明
限制地图高度不超过视口高度的一半,留出滚动空间
单指滚动、双指操作地图Leaflet 可配置拖拽手势的判定条件
加「展开」按钮默认静态图,点击后进入全屏交互模式

Leaflet 的手势配置

var map = L.map('map', {
  dragging: !L.Browser.mobile,          // 移动端关闭单指拖拽
  tap: false,
  touchZoom: true
});

// 移动端允许双指拖动地图
if (L.Browser.mobile) {
  map.dragging.enable();
}

更精细的控制可以给 dragging 加自定义判定, 比如只有在双指按下时才允许拖动,单指留给页面滚动。

地图在弹层里滚动穿透

全屏地图弹层打开后,手指在地图外拖动,背后的页面仍在滚动。 这是滚动穿透,需要在弹层打开时锁住 body。

function lockScroll(on) {
  document.body.style.overflow = on ? 'hidden' : '';
  // iOS 需要额外处理
  document.body.style.position = on ? 'fixed' : '';
  document.body.style.width = on ? '100%' : '';
}

iOS Safari 对 overflow: hidden 不生效,必须配合 position: fixed。但这样会丢失滚动位置, 需要在解锁时恢复 scrollTop。

点击延迟与 300 毫秒

老版本移动浏览器为了判断双击缩放,会在 click 前等 300 毫秒。 现在的主流浏览器在有正确 viewport 设置时已经取消了这个延迟。 如果还遇到点击迟钝,检查 viewport 是否写了 width=device-width。

排查顺序

  1. 确认地图容器有没有 touch-action 限制
  2. 确认地图高度是否占满整屏导致无法滚动
  3. 区分是「页面缩放」还是「地图缩放」,前者改 viewport,后者查手势配置
  4. 弹层场景检查 body 是否锁住了滚动
  5. 真机测试,不要只用浏览器模拟器
引用本文

经纬志(2026)。《移动端地图手势冲突与滚动穿透》。https://www.jingweizhi.com/cookbook/mobile-gesture.html(访问日期 2026-11-03)。