Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 框架

  • core模块

  • dom模块

  • control

  • geometry

  • geo

  • layer

    • marker

      • Marker
      • Marker.Drag
        • 概述
        • 源码分析
          • 源码实现
          • 源码详解
          • 核心逻辑
          • 扩展思考
        • 总结
      • Icon
      • Icon.Default
      • DivIcon
    • tile

    • vector

    • Layer
    • LayerGroup
    • FeatureGroup
    • DivOverlay
    • Popup
    • Tooltip
    • ImageOverlay
    • SVGOverlay
    • VideoOverlay
    • GeoJSON
  • Map

  • 《Leaflet源码》笔记
  • layer
  • marker
东流
2025-04-11
目录

Marker.Drag

# 概述

Marker.Drag 是 Leaflet 中用于实现标记拖动功能的插件。它允许用户通过鼠标或触摸设备拖动标记到地图上的新位置。

# 源码分析

# 源码实现

MarkerDrag类实现如下:

export var MarkerDrag = Handler.extend({
  initialize: function (marker) {
    this._marker = marker;
  },
  addHooks: function () {
    var icon = this._marker._icon;
    if (!this._draggable) {
      this._draggable = new Draggable(icon, icon, true); // 创建可拖拽对象
    }

    this._draggable
      .on(
        {
          dragstart: this._onDragStart,
          predrag: this._onPreDrag,
          drag: this._onDrag,
          dragend: this._onDragEnd,
        },
        this
      )
      .enable(); // 绑定事件并启用

    DomUtil.addClass(icon, "leaflet-marker-draggable"); // 添加拖拽样式
  },
  removeHooks: function () {
    this._draggable
      .off(
        {
          dragstart: this._onDragStart,
          predrag: this._onPreDrag,
          drag: this._onDrag,
          dragend: this._onDragEnd,
        },
        this
      )
      .disable(); // 解绑事件并禁用

    if (this._marker._icon) {
      DomUtil.removeClass(this._marker._icon, "leaflet-marker-draggable"); // 移除拖拽样式
    }
  },
  moved: function () {
    return this._draggable && this._draggable._moved; // 判断是否发生移动
  },
  _adjustPan: function (e) {
    var marker = this._marker,
      map = marker._map,
      speed = this._marker.options.autoPanSpeed,
      padding = this._marker.options.autoPanPadding,
      iconPos = DomUtil.getPosition(marker._icon),
      bounds = map.getPixelBounds(),
      origin = map.getPixelOrigin();

    var panBounds = toBounds(
      bounds.min._subtract(origin).add(padding),
      bounds.max._subtract(origin).subtract(padding)
    );
    
    // 计算 Marker位置是否超出地图边界
    if (!panBounds.contains(iconPos)) {
      // Compute incremental movement
      // 计算平移量并移动地图
      var movement = toPoint(
        (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) /
          (bounds.max.x - panBounds.max.x) -
          (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) /
            (bounds.min.x - panBounds.min.x),

        (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) /
          (bounds.max.y - panBounds.max.y) -
          (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) /
            (bounds.min.y - panBounds.min.y)
      ).multiplyBy(speed);

      map.panBy(movement, { animate: false });

      this._draggable._newPos._add(movement);
      this._draggable._startPos._add(movement);
      // 更新拖拽位置
      DomUtil.setPosition(marker._icon, this._draggable._newPos);
      this._onDrag(e);
      // 递归调用,实现持续平移效果   
      this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
    }
  },
  _onDragStart: function () {
    this._oldLatLng = this._marker.getLatLng(); // 记录原始位置
    this._marker.closePopup && this._marker.closePopup(); // 关闭弹框

    this._marker.fire("movestart").fire("dragstart"); // 触发事件
  },
  _onPreDrag: function (e) {
    if (this._marker.options.autoPan) {
      cancelAnimFrame(this._panRequest);
      this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
    }
  },
  _onDrag: function (e) {
    var marker = this._marker,
      shadow = marker._shadow,
      iconPos = DomUtil.getPosition(marker._icon),
      latlng = marker._map.layerPointToLatLng(iconPos); // 计算新经纬度

    if (shadow) {
      DomUtil.setPosition(shadow, iconPos);
    }

    marker._latlng = latlng; // 更新标记位置
    e.latlng - latlng;
    e.oldLatLng = this._oldLatLng;

    marker.fire("move", e).fire("drag", e); // 触发事件
  },
  _onDragEnd: function (e) {
    cancelAnimFrame(this._panRequest); // 停止自动平移
    delete this._oldLatLng;
    this._marker.fire("moveend").fire("dragend", e); // 触发事件
  },
});
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

# 源码详解

  1. 类定义与初始化

    • 作用:定义一个MarkerDrag的处理器,继承Handler类。
    • 关键点:
      • initialize方法:初始化MarkerDrag实例,接收一个marker参数。
      • _marker属性:存储传入的marker实例。
  2. 钩子函数(Hooks)

    • 作用:通过addHooks和removeHooks管理拖拽功能的激活与销毁
    • 关键点:
      • Draggable对象:基于Marker的图标(_icon)创建,实际控制拖拽行为
      • 事件绑定:监听dragstart、predrag、drag、dragend四个关键事件,对应不同阶段的拖拽逻辑。
      • CSS类:添加leaflet-marker-draggable类,可能用于自定义拖拽时的样式,如光标形状
  3. 自动平移(AutoPan)

    • 作用:当拖拽Marker到地图边缘时,自动平移地图以保持Marker可见
    • 关键点
      • 边界计算:根据autoPanPadding计算安全区域(panBounds)
      • 平滑移动:通过requestAnimFrame实现平滑的逐帧平移,避免卡顿
      • 位置补偿:更新Draggable对象的_newPos和_startPos,防止因地图平移导致的Marker位置跳变
  4. 拖拽事件处理

    1. _onDragStart
      • 作用:记录原始位置并关闭相关弹框
      • 关键点:
        • this._oldLatLng:存储原始位置,用于计算移动距离
        • marker.closePopup:关闭与Marker相关的弹出框
        • 触发movestart和dragstart事件
    2. _onPreDrag
      • 作用:在拖拽开始前检查是否需要自动平移
      • 关键点:
        • this._marker.options.autoPan:检查是否启用自动平移
        • this._adjustPan:调用_adjustPan方法进行平移
    3. _onDrag
      • 作用:实时更新Marker位置并触发相关事件
      • 关键点:
        • 计算新的经纬度
        • 更新Marker的位置
        • 触发move和drag事件
    4. _onDragEnd
      • 作用:清理状态,结束拖拽操作并触发相关事件
      • 关键点:
        • 停止自动平移
        • 触发moveend和dragend事件
  5. 辅助方法 move方法提供外部接口,判断拖拽过程中Marker是否实际移动

# 核心逻辑

​​

  1. 拖拽初始化​​:通过 Draggable 对象绑定 Marker 图标的拖拽能力。
  2. ​自动平移​​:在 predrag 阶段实时检测边界,动态调整地图视口。
  3. ​位置同步​​:将拖拽后的像素坐标转换为经纬度,更新 Marker 位置。
  4. ​事件传递​​:在拖拽各阶段触发事件(如 dragstart、drag),允许外部监听并自定义行为。

# 扩展思考

  • 性能优化​​:使用 requestAnimFrame 确保平移流畅,避免过度渲染。
  • 自定义行为​​:通过 autoPanSpeed 和 autoPanPadding 参数可调整平移灵敏度。
  • ​可扩展性​​:通过继承 Handler,Leaflet 允许开发者自定义其他交互行为(如旋转、缩放)

# 总结

MarkerDrag类通过Draggable对象实现了Marker的拖拽功能,支持自动平移和自定义样式。通过事件驱动,MarkerDrag提供了丰富的拖拽事件,方便开发者进行自定义操作。

编辑 (opens new window)
上次更新: 2025/04/28, 10:08:26
Marker
Icon

← Marker Icon→

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