概述
TouchZoom
是 Map
类的一个扩展方法,是 Leaflet 实现触摸设备双指缩放的处理器,用于在移动端通过双指手势控制地图的缩放。
源码分析
源码实现
TouchZoom
的源码实现如下:
Map.mergeOptions({touchZoom: Browser.touch, // 仅在触摸设备上启用bounceAtZoomLimits: true, // 缩放超出限制时是否回弹
});export var TouchZoom = Handler.extend({addHooks: function () {// 添加 CSS 类(可能用于样式或标记状态)DomUtil.addClass(this._map._container, "leaflet-touch-zoom");// 监听触摸开始事件DomEvent.on(this._map._container, "touchstart", this._onTouchStart, this);},removeHooks: function () {// 移除 CSS 类和事件监听DomUtil.removeClass(this._map._container, "leaflet-touch-zoom");DomEvent.off(this._map._container, "touchstart", this._onTouchStart, this);},_onTouchStart: function (e) {var map = this._map;// 检查是否双指触摸,且地图未在动画中if (!e.touches ||e.touches.length !== 2 ||map._animating ||this._zooming) {return;}// 计算两指初始位置(转换为地图容器坐标)var p1 = map.mouseEventToContainerPoint(e.touches[0]),p2 = map.mouseEventToContainerPoint(e.touches[1]);// 确定缩放中心点this._centerPoint = map.getSize()._divideBy(2); // 地图中心this._startLatLng = map.containerPointToLatLng(this._centerPoint);// 如果非center模式,则以双指中点为中心if (map.options.touchZoom !== "center") {this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));}// 记录初始距离和缩放级别this._startDist = p1.distanceTo(p2);this._startZoom = map.getZoom();this._moved = false;this._zooming = true;// 停止地图当前动画map._stop();// 绑定文档级触摸事件DomEvent.on(document, "touchmove", this._onTouchMove, this);DomEvent.on(document, "touchend touchcancel", this._onTouchEnd, this);DomEvent.preventDefault(e); // 阻止默认行为},_onTouchMove: function (e) {// 计算当前两指距离,计算缩放比例if (!e.touches || e.touches.length !== 2 || !this._zooming) {return;}var map = this._map,p1 = map.mouseEventToContainerPoint(e.touches[0]),p2 = map.mouseEventToContainerPoint(e.touches[1]),scale = p1.distanceTo(p2) / this._startDist;// 根据比例计算目标缩放级别this._zoom = map.getScaleZoom(scale, this._startZoom);// 处理缩放限制(若不允许回弹,则强制限制在min/max)if (!map.options.bounceAtZoomLimits &&((this._zoom < map.getMinZoom() && scale < 1) ||(this._zoom > map.getMaxZoom() && scale > 1))) {this._zoom = map._limitZoom(this._zoom);}// 根据模式计算新的中心点if (map.options.touchZoom === "center") {this._center = this._startLatLng;if (scale === 1) {return;}} else {// 计算双指中点偏移量,重新投影到目标缩放级别下的坐标var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);if (scale === 1 && delta.x === 0 && delta.y === 0) {return;}this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta),this._zoom);}// 触发地图移动(若未移动过,先触发`_moveStart`方法)if (!this._moved) {map._moveStart(true, false);this._moved = true;}// 使用动画帧优化性能,平滑更新地图视图Util.cancelAnimFrame(this._animRequest);var moveFn = Util.bind(map._move,map,this._center,this._zoom,{ pinch: true, round: false },undefined);this._animRequest = Util.requestAnimFrame(moveFn, this, true);DomEvent.preventDefault(e);},_onTouchEnd: function () {// 若未移动或未在缩放状态,则直接返回if (!this._moved || !this._zooming) {this._zooming = false;return;}// 清理状态和事件监听this._zooming = false;Util.cancelAnimFrame(this._animRequest);DomEvent.off(document, "touchmove", this._onTouchMove, this);DomEvent.off(document, "touchend touchcancel", this._onTouchEnd, this);// 根据配置执行最终缩放动画或直接重置视图if (this._map.options.zoomAnimation) {this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),true,this._map.options.zoomSnap);} else {this._map._resetView(this._center, this._map._limitZoom(this._zoom));}},
});Map.addInitHook("addHandler", "touchZoom", TouchZoom);
源码详解
-
全局配置
touchZoom
:根据浏览器是否支持触摸事件启用功能bounceAtZoomLimits
:当缩放到最小/最大级别时是否允许短暂回弹(类似惯性效果)
-
处理器定义(
TouchZoom
)addHooks
: 绑定touchstart
事件到_onTouchStart
。removeHooks
: 清理操作,确保处理器禁用时解除绑定。
-
触摸开始(
_onTouchStart
)- 仅处理双指触摸
- 根据配置(
touchZoom
模式)计算缩放中心点center
模式:始终以地图中心缩放- 非
center
模式:以双指中点为中心
- 记录初始距离和缩放级别,为后续计算缩放比例做准备
- 绑定文档级事件,确保触摸点在移动时仍能触发
-
触摸移动(
_onTouchMove
)- 计算缩放比例:通过两指距离变化(
scale
)计算新的缩放级别 - 处理缩放限制:若
bounceAtZoomLimits
为false
,则强制限制在min/max
级别 - 计算中心点:
center
模式:保持地图中心不变- 非
center
模式:根据双指中点偏移量(delta
)重新投影到目标缩放级别下的坐标
- 优化渲染:使用
requestAnimFrame
避免频繁更新导致的卡顿
- 计算缩放比例:通过两指距离变化(
-
触摸结束(
_onTouchEnd
)- 状态清理:取消动画帧,解除事件监听
- 应用最终视图:
- 如果启用缩放动画,平滑过渡到目标缩放级别
- 否则直接重置视图
-
注册处理器
通过Map.addInitHook
方法将TouchZoom
处理器添加到地图的初始化流程中,当在触摸设备上时,会实例化TouchZoom
类,使其生效。
总结
-
双指中心点计算 :
- 支持两种模式:固定地图中心或动态跟随双指中点。
- 动态模式下,通过
project
和unproject
方法处理不同缩放级别的坐标转换。
-
性能优化 :
- 使用
requestAnimFrame
避免过度渲染。 - 仅在必要时触发
_moveStart
和_move
,减少计算量。
- 使用
-
边界处理 :
- 根据
bounceAtZoomLimits
决定是否允许短暂超出缩放限制。 - 最终缩放级别会通过
_limitZoom
确保在合法范围内。
- 根据
-
事件协作 :
与 Leaflet 内部方法(如_stop
,_animateZoom
)协作,确保与其他操作(如拖拽、其他缩放控件)互不冲突。
这段代码实现了流畅的触摸缩放交互,是 Leaflet 在移动端的重要功能之一。