概述
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); // 触发事件
},
});
源码详解
-
类定义与初始化
- 作用:定义一个
MarkerDrag
的处理器,继承Handler
类。 - 关键点:
initialize
方法:初始化MarkerDrag
实例,接收一个marker
参数。_marker
属性:存储传入的marker
实例。
- 作用:定义一个
-
钩子函数(Hooks)
- 作用:通过
addHooks
和removeHooks
管理拖拽功能的激活与销毁 - 关键点:
- Draggable对象:基于
Marker
的图标(_icon
)创建,实际控制拖拽行为 - 事件绑定:监听
dragstart
、predrag
、drag
、dragend
四个关键事件,对应不同阶段的拖拽逻辑。 - CSS类:添加
leaflet-marker-draggable
类,可能用于自定义拖拽时的样式,如光标形状
- Draggable对象:基于
- 作用:通过
-
自动平移(AutoPan)
- 作用:当拖拽
Marker
到地图边缘时,自动平移地图以保持Marker
可见 - 关键点
- 边界计算:根据
autoPanPadding
计算安全区域(panBounds
) - 平滑移动:通过
requestAnimFrame
实现平滑的逐帧平移,避免卡顿 - 位置补偿:更新
Draggable
对象的_newPos
和_startPos
,防止因地图平移导致的Marker位置跳变
- 边界计算:根据
- 作用:当拖拽
-
拖拽事件处理
- _onDragStart
- 作用:记录原始位置并关闭相关弹框
- 关键点:
this._oldLatLng
:存储原始位置,用于计算移动距离marker.closePopup
:关闭与Marker
相关的弹出框- 触发
movestart
和dragstart
事件
- _onPreDrag
- 作用:在拖拽开始前检查是否需要自动平移
- 关键点:
this._marker.options.autoPan
:检查是否启用自动平移this._adjustPan
:调用_adjustPan
方法进行平移
- _onDrag
- 作用:实时更新
Marker
位置并触发相关事件 - 关键点:
- 计算新的经纬度
- 更新
Marker
的位置 - 触发
move
和drag
事件
- 作用:实时更新
- _onDragEnd
- 作用:清理状态,结束拖拽操作并触发相关事件
- 关键点:
- 停止自动平移
- 触发
moveend
和dragend
事件
- _onDragStart
-
辅助方法
move
方法提供外部接口,判断拖拽过程中Marker
是否实际移动
核心逻辑
- 拖拽初始化:通过
Draggable
对象绑定Marker
图标的拖拽能力。 - 自动平移:在
predrag
阶段实时检测边界,动态调整地图视口。 - 位置同步:将拖拽后的像素坐标转换为经纬度,更新
Marker
位置。 - 事件传递:在拖拽各阶段触发事件(如
dragstart
、drag
),允许外部监听并自定义行为。
扩展思考
- 性能优化:使用
requestAnimFrame
确保平移流畅,避免过度渲染。 - 自定义行为:通过
autoPanSpeed
和autoPanPadding
参数可调整平移灵敏度。 - 可扩展性:通过继承
Handler
,Leaflet 允许开发者自定义其他交互行为(如旋转、缩放)
总结
MarkerDrag
类通过Draggable
对象实现了Marker的拖拽功能,支持自动平移和自定义样式。通过事件驱动,MarkerDrag
提供了丰富的拖拽事件,方便开发者进行自定义操作。