Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 框架

  • core模块

  • dom模块

  • control

  • geometry

  • geo

  • layer

  • Map

    • Map类
    • Map类扩展方法之BoxZoom
      • 概述
      • 源码分析
        • 源码实现
        • 源码详细分析
        • BoxZoom类
        • Map类
      • 总结
    • Map类扩展方法之DoubleClickZoom
    • Map类扩展方法之Drag
    • Map类扩展方法之Keyboard
    • Map类扩展方法之ScrollWheelZoom
    • Map类扩展方法之TapHold
    • Map类扩展方法之TouchZoom
  • 《Leaflet源码》笔记
  • Map
东流
2025-04-07
目录

Map类扩展方法之BoxZoom

# 概述

Map.BoxZoom是Map类的一个扩展方法,用于实现地图框选缩放功能,属于Leaflet 内部的事件处理器之一。

# 源码分析

# 源码实现

Map.BoxZoom源码实现如下:

Map.mergeOPtions({
  boxZoom: true, //合并配置,默认启用框选缩放功能。
});

// 继承于`Handler`类,用于处理地图事件。

export var BoxZoom = Handler.extend({
  initialize: function (map) {
    this._map = map; // 关联地图实例
    this._container = map._container; // 地图容器元素
    this._pane = map._panes.overlayPane; // 覆盖层面板
    this._resetStateTimeout = 0; // 重置状态的计时器
    map.on("unload", this._destroy, this); // 地图卸载时触发清理方法
  },
  addHooks: function () {
    DomEvent.on(this._container, "mousedown", this._onMouseDown, this);
  },
  removeHooks: function () {
    DomEvent.off(this._container, "mousedown", this._onMouseDown, this);
  },
  moved: function () {
    return this._moved;
  },
  _destroy: function () {
    DomUtil.remove(this._pane); //地图卸载时清理覆盖层
    delete this._pane;
  },
  _resetState: function () {
    this._resetStateTimeout = 0;
    this._moved = false;
  },
  _clearDeferredResetState: function () {
    if (this._resetStateTimeout !== 0) {
      clearTimeout(this._resetStateTimeout);
      this._resetStateTimeout = 0;
    }
  },
  _onMouseDown: function (e) {
    if (!e.shiftKey || (e.which !== 1 && e.button !== 1)) { // 判断是否按下了Shift键和鼠标左键
      return false;
    }
    //重置状态
    this._clearDeferredResetState();
    this._resetState();

    DomUtil.disableTextSelection(); // 禁用文本选择
    DomUtil.disableImageDrag(); // 禁用图片拖拽

    this._startPoint = this._map.mouseEventToContainerPoint(e); // 记录起始位置

    // 绑定全局鼠标移动、抬起、按键事件
    DomEvent.on(
      document,
      {
        contextmenu: DomEvent.stop,
        mousemove: this._onMouseMove,
        mouseup: this._onMouseUp,
        keydown: this._onKeyDown,
      },
      this
    );
  },
  _onMouseMove: function (e) {
    if (!this._moved) {
      this._moved = true;

      this._box = DomUtil.create("div", "leaflet-zoom-box", this._container); // 创建框选矩形
      DomUtil.addClass(this._container, "leaflet-crosshair"); //添加十字光标样式

      this._map.fire("boxzoomstart"); // 触发框选开始事件
    }

    // 更新框选矩形的位置和大小
    this._point = this._map.mouseEventToContainerPoint(e);

    var bounds = new Bounds(this._point, this._startPoint),
      size = bounds.getSize();

    DomUtil.setPosition(this._box, bounds.min);

    this._box.style.width = size.x + "px";
    this._box.style.height = size.y + "px";
  },
  _finish: function () {
    if (this._moved) {
      DomUtil.remove(this._box);
      DomUtil.removeClass(this._container, "leaflet-crosshair");
    }

    DomUtil.enableTextSelection();
    DomUtil.enableImageDrag();

    DomEvent.off(
      document,
      {
        contextmenu: DomEvent.stop,
        mousemove: this._onMouseMove,
        mouseup: this._onMouseUp,
        keydown: this._onKeyDown,
      },
      this
    );
  },
  _onMouseUp: function (e) {
    if (e.which !== 1 && e.button !== 1) {
      return;
    }
    
    // 清理DOM和事件
    this._finish();

    if (!this._moved) {
      return;
    }
    this._clearDeferredResetState();
    this._resetStateTimeout = setTimeout(Util.bind(this._resetState, this), 0);

    var bounds = new LatLngBounds(
      this._map.containerPointToLatLng(this._startPoint),
      this._map.containerPointToLatLng(this._point)
    );
    
    // 缩放地图,触发boxzoomend事件
    this._map.fitBounds(bounds).fire("boxzoomend", { boxZoomBounds: bounds });
  },
  _onKeyDown: function (e) {
    if (e.keyCode === 27) { // ESC键
      this._finish();
      this._clearDeferredResetState();
      this._resetState();
    }
  },
});

Map.addInitHook("addHandler", "boxZoom", BoxZoom);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

# 源码详细分析

# BoxZoom类

  1. 初始化initialize

initialize方法用于初始化BoxZoom类的实例,接受一个map参数,该参数是Map类的实例,表示要绑定事件的地图对象。在initialize方法中,首先将map对象赋值给this._map属性,然后获取地图容器元素和覆盖层元素,并将其赋值给this._container和this._pane属性。接着,将unload事件绑定到_destroy方法上,用于在地图卸载时销毁BoxZoom类的实例。

  1. 钩子函数addHooks/removeHooks

addHooks方法用于添加事件钩子,将mousedown事件绑定到_onMouseDown方法上。

removeHooks方法用于移除事件钩子,将mousedown事件从_onMouseDown方法上解绑。

  1. 鼠标状态moved方法

moved方法用于判断是否发生了移动操作,返回一个布尔值,表示是否发生了移动操作。

  1. 销毁方法_destroy

_destroy方法用于销毁BoxZoom类的实例,移除覆盖层元素和禁用文本选择和图像拖动。

  1. 重置状态_resetState

_resetState方法用于重置状态,将this._resetStateTimeout属性设置为0,将this._moved属性设置为false。

  1. 清除延迟重置状态_clearDeferredResetState

_clearDeferredResetState方法用于清除延迟重置状态,将this._resetStateTimeout属性设置为0。

  1. 鼠标按下事件_onMouseDown _onMouseDown方法用于处理鼠标按下事件,接受一个e参数,该参数是鼠标事件对象。 在_onMouseDown方法中,首先判断是否按下了Shift键和鼠标左键或右键,如果不满足条件,则返回false。 然后,调用_clearDeferredResetState方法清除延迟重置状态,调用_resetState方法重置状态。 接着,禁用文本选择和图像拖动,将鼠标按下的容器点赋值给this._startPoint属性。 最后,将contextmenu事件绑定到DomEvent.stop方法上,将mousemove事件绑定到_onMouseMove方法上,将mouseup事件绑定到_onMouseUp方法上,将keydown事件绑定到_onKeyDown方法上。

  2. 鼠标移动事件_onMouseMove _onMouseMove方法用于处理鼠标移动事件,接受一个e参数,该参数是鼠标事件对象。 在_onMouseMove方法中,首先判断是否发生了移动操作,如果没有发生,则将this._moved属性设置为true。 然后,创建一个div元素,设置其类名为leaflet-zoom-box,并将其添加到this._container元素中。 接着,将leaflet-crosshair类添加到this._container元素中。 最后,调用this._map.fire("boxzoomstart")方法触发boxzoomstart事件。

  3. 完成处理_finish

框选结束鼠标抬起时会触发_finish方法,用于进行完成操作,如移除div元素、禁用文本选择和图像拖动以及移除全局绑定的监听事件。

  1. 鼠标抬起事件_onMouseUp _onMouseUp方法用于处理鼠标抬起事件,接受一个e参数,该参数是鼠标事件对象。

在_onMouseUp方法中,首先判断是否抬起了鼠标左键或右键,如果不满足条件,则返回。然后,调用_finish方法完成操作,调用_clearDeferredResetState方法清除延迟重置状态,调用_resetStateTimeout方法设置延迟重置状态。最后,将鼠标抬起的容器点赋值给this._point属性,计算出矩形框的左上角和右下角的经纬度坐标,创建一个LatLngBounds对象,将其赋值给bounds变量。然后,调用this._map.fitBounds(bounds).fire("boxzoomend", { boxZoomBounds: bounds })方法将地图缩放到矩形框的范围内,并触发boxzoomend事件,将矩形框的边界传递给事件处理函数。

  1. _onKeyDown键盘按下事件 _onKeyDown方法用于处理键盘按下事件,接受一个e参数,该参数是键盘事件对象。 在_onKeyDown方法中,首先判断是否按下了Esc键,如果按下了,则调用_finish方法完成操作,调用_clearDeferredResetState方法清除延迟重置状态,调用_resetState方法重置状态。

# Map类

Map.addInitHook方法是将BoxZoom注册为Leaflet的默认处理器,使得地图初始化时会自动加载。

而Map.addInitHook方法的第一个参数addHandler就是用于添加处理器,它接受三个参数,分别是name、handler和context。而在上述中,name和handler对应的就是boxZoom字符串和BoxZoom类,首先判断是否存在this._handlers属性,如果不存在,则创建一个空对象。然后,将handler对象赋值给this._handlers[name]属性,即this._handlers.boxZoom= new BoxZoom(this)。在最开始合并选项时默认boxZoom为true,所以会执行this._handlers.boxZoom.enable()即启用处理器。enable()方法是在Handler基类中实现的,enable方法内部会调用this.addHooks()方法,即添加钩子函数,监听mousedown事件。

# 总结

Map.BoxZoom是通过继承 Handler 类,实现了框选缩放的核心交互逻辑:

  1. ​​事件监听​​:通过钩子函数绑定/解绑鼠标和键盘事件。
  2. ​​DOM 操作​​:动态创建/更新框选矩形,并管理相关样式。
  3. 地理计算​​:将屏幕坐标转换为地理边界,触发地图缩放。
  4. 状态管理​​:处理中途取消(ESC 键)、资源清理等边界条件。

它是 Leaflet 高可扩展性的典型示例,通过自定义 Handler 可灵活添加或修改交互行为。

编辑 (opens new window)
上次更新: 2025/04/11, 01:38:08
Map类
Map类扩展方法之DoubleClickZoom

← Map类 Map类扩展方法之DoubleClickZoom→

最近更新
01
GeoJSON
05-08
02
Circle
04-15
03
CircleMarker
04-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式