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模块

    • DomEvent
      • 概述
      • 源码分析
        • 源码实现
        • 源码详细解析
      • 总结
    • DomUtil
    • DomEvent.Pointer
    • DomEvent.DoubleTap
    • Draggable拖拽实现
    • PosAnimation位置动画介绍
  • control

  • geometry

  • geo

  • layer

  • Map

  • 《Leaflet源码》笔记
  • dom模块
东流
2025-03-19
目录

DomEvent

# 概述

​DomEvent是Leaflet库中用于处理 DOM 事件的核心模块 DomEvent 的实现。它提供了一套跨浏览器兼容的事件绑定、解绑、传播控制等功能

# 源码分析

# 源码实现

DomEvent的源码实现如下:

export function on(obj, types, fn, context) {
  if (types && typeof types === "object") {
    for (var type in types) {
      addOne(obj, type, types[type], fn);
    }
  } else {
    types = Util.splitWords(types);

    for (var i = 0, len = types.length; i < len; i++) {
      addOne(obj, types[i], fn, context);
    }
  }

  return this;
}

var eventsKey = "_leaflet_events";

export function off(obj, types, fn, context) {
  if (arguments.length === 1) {
    batchRemove(obj);
    delete obj[eventsKey];
  } else if (types && typeof types === "object") {
    for (var type in types) {
      removeOne(obj, type, types[type], fn);
    }
  } else {
    types = Util.splitWords(types);

    if (arguments.length === 2) {
      batchRemove(obj, function (type) {
        return Util.indexOf(types, type) !== -1;
      });
    } else {
      for (var i = 0, len = types.length; i < len; i++) {
        removeOne(obj, types[i], fn, context);
      }
    }
  }

  return this;
}

function batchRemove(obj, filterFn) {
  for (var id in obj[eventsKey]) {
    var type = id.split(/\d/)[0];
    if (!filterFn || filterFn(type)) {
      removeOne(obj, type, null, null, id);
    }
  }
}

var mouseSubst = {
  mouseenter: "mouseover",
  mouseleave: "mouseout",
  wheel: !("onwheel" in window) && "mousewheel",
};

function addOne(obj, type, fn, context) {
  var id = type + Util.stamp(fn) + (context ? "_" + Util.stamp(context) : "");

  if (obj[eventsKey] && obj[eventsKey][id]) {
    return this;
  }

  var handler = function (e) {
    return fn.call(context || obj, e || window.event);
  };

  var originalHandler = handler;

  if (!Browser.touchNative && Browser.pointer && type.indexOf("touch") === 0) {
    // Needs DomEvent.Pointer.js
    handler = addPointerListener(obj, type, handler);
  } else if (Browser.touch && type === "dblclick") {
    handler = addDoubleTapListener(obj, handler);
  } else if ("addEventListener" in obj) {
    if (
      type === "touchstart" ||
      type === "touchmove" ||
      type === "wheel" ||
      type === "mousewheel"
    ) {
      obj.addEventListener(
        mouseSubst[type] || type,
        handler,
        Browser.passiveEvents ? { passive: false } : false
      );
    } else if (type === "mouseenter" || type === "mouseleave") {
      handler = function (e) {
        e = e || window.event;
        if (isExternalTarget(obj, e)) {
          originalHandler(e);
        }
      };
      obj.addEventListener(mouseSubst[type], handler, false);
    } else {
      obj.addEventListener(type, originalHandler, false);
    }
  } else {
    obj.attachEvent("on" + type, handler);
  }

  obj[eventsKey] = obj[eventsKey] || {};
  obj[eventsKey][id] = handler;
}

function removeOne(obj, type, fn, context, id) {
  id = id || type + Util.stamp(fn) + (context ? "_" + Util.stamp(context) : "");
  var handler = obj[eventsKey] && obj[eventsKey][id];

  if (!handler) {
    return this;
  }

  if (!Browser.touchNative && Browser.pointer && type.indexOf("touch") === 0) {
    removePointerListener(obj, type, handler);
  } else if (Browser.touch && type === "dblclick") {
    removeDoubleTapListener(obj, handler);
  } else if ("removeEventListener" in obj) {
    obj.removeEventListener(mouseSubst[type] || type, handler, false);
  } else {
    obj.detachEvent("on" + type, handler);
  }

  obj[eventsKey][id] = null;
}

export function stopPropagation(e) {
  if (e.stopPropagation) {
    e.stopPropagation();
  } else if (e.originalEvent) {
    // In case of Leaflet event.
    e.originalEvent._stopped = true;
  } else {
    e.cancelBubble = true;
  }

  return this;
}

export function disableScrollPropagation(el) {
  addOne(el, "wheel", stopPropagation);
  return this;
}

export function disableClickPropagation(el) {
  on(el, "mousedown touchstart dblclick contextmenu", stopPropagation);
  el["_leaflet_disable_click"] = true;
  return this;
}

export function preventDefault(e) {
  if (e.preventDefault) {
    e.preventDefault();
  } else {
    e.returnValue = false;
  }
  return this;
}

export function stop(e) {
  preventDefault(e);
  stopPropagation(e);
  return this;
}

export function getPropagationPath(ev) {
  if (ev.composedPath) {
    return ev.composedPath();
  }

  var path = [];
  var el = ev.target;

  while (el) {
    path.push(el);
    el = el.parentNode;
  }
  return path;
}

export function getMousePosition(e, container) {
  if (!container) {
    return new Point(e.clientX, e.clientY);
  }

  var scale = getScale(container),
    offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)

  return new Point(
    (e.clientX - offset.left) / scale.x - container.clientLeft,
    (e.clientY - offset.top) / scale.y - container.clientTop
  );
}

var wheelPxFactor =
  Browser.linux && Browser.chrome
    ? window.devicePixelRatio
    : Browser.mac
    ? window.devicePixelRatio * 3
    : window.devicePixelRatio > 0
    ? 2 * window.devicePixelRatio
    : 1;

export function getWheelDelta(e) {
  return Browser.edge
    ? e.wheelDeltaY / 2 // Don't trust window-geometry-based delta
    : e.deltaY && e.deltaMode === 0
    ? -e.deltaY / wheelPxFactor // Pixels
    : e.deltaY && e.deltaMode === 1
    ? -e.deltaY * 20 // Lines
    : e.deltaY && e.deltaMode === 2
    ? -e.deltaY * 60 // Pages
    : e.deltaX || e.deltaZ
    ? 0 // Skip horizontal/depth wheel events
    : e.wheelDelta
    ? (e.wheelDeltaY || e.wheelDelta) / 2 // Legacy IE pixels
    : e.detail && Math.abs(e.detail) < 32765
    ? -e.detail * 20 // Legacy Moz lines
    : e.detail
    ? (e.detail / -32765) * 60 // Legacy Moz pages
    : 0;
}

export function isExternalTarget(el, e) {
  var related = e.relatedTarget;

  if (!related) {
    return true;
  }

  try {
    while (related && related !== el) {
      related = related.parentNode;
    }
  } catch (err) {
    return false;
  }
  return related !== el;
}

export { on as addListener };
export { off as removeListener };
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244

# 源码详细解析

  1. 事件绑定与解绑
  • on(obj, types, fn, context)

    • 作用:为对象(通常是 DOM 元素)绑定事件监听器。
    • 实现:
      • 如果 types 是一个对象,则遍历对象,为每个事件类型绑定对应的处理函数。
      • 如果 types 是字符串,则通过 Util.splitWords 将其拆分为多个事件类型,依次绑定。
      • 调用 addOne 函数实际绑定事件。
    • 返回值:返回 this,支持链式调用。
  • off(obj, types, fn, context)

    • 作用:移除对象的事件监听器。
    • 实现:
      • 如果只有 obj 参数,则移除所有事件监听器。
      • 如果 types 是一个对象,则遍历对象,移除每个事件类型对应的处理函数。
      • 如果 types 是字符串,则通过 Util.splitWords 拆分为多个事件类型,依次移除。
      • 调用 removeOne 或 batchRemove 函数实际移除事件。
    • 返回值:返回 this,支持链式调用。
  • addOne(obj, type, fn, context)

    • 作用:为对象绑定单个事件监听器。
    • 实现:
      • 生成唯一的事件 ID,用于存储和查找事件处理函数。
      • 根据事件类型和浏览器特性,选择合适的方式绑定事件(如 addEventListener 或 attachEvent)。
      • 处理特殊事件(如 touch、dblclick、mouseenter 等)的兼容性。
      • 将事件处理函数存储在 obj[eventsKey] 中。
  • removeOne(obj, type, fn, context, id)

    • 作用:移除对象的单个事件监听器。
    • 实现:
      • 根据事件 ID 查找并移除事件处理函数。
      • 处理特殊事件(如 touch、dblclick)的兼容性。
      • 从 obj[eventsKey] 中移除事件处理函数。
  • batchRemove(obj, filterFn)

    • 作用:批量移除对象的事件监听器。
    • 实现:
      • 遍历 obj[eventsKey],移除符合条件的事件监听器。
      • 如果提供了 filterFn,则只移除匹配的事件类型。
  1. 事件传播控制
  • stopPropagation(e)

    • 作用:阻止事件传播。
    • 实现:
      • 优先调用 e.stopPropagation()。
      • 如果事件是 Leaflet 自定义事件,则设置 e.originalEvent._stopped。
      • 回退到设置 e.cancelBubble = true。
  • disableScrollPropagation(el) ​ 作用:阻止滚动事件的传播。 ​ 实现:绑定 wheel 事件,调用 stopPropagation。

  • disableClickPropagation(el)

  • 作用:阻止点击事件的传播。

  • 实现:绑定 mousedown、touchstart、dblclick、contextmenu 事件,调用 stopPropagation。

  • preventDefault(e)

    • 作用:阻止事件的默认行为。
    • 实现:
    • 优先调用 e.preventDefault()。
    • 回退到设置 e.returnValue = false。
  • stop(e)

    • 作用:同时阻止事件传播和默认行为。
    • 实现:调用 preventDefault 和 stopPropagation。
  1. 事件工具函数
  • getPropagationPath(ev)

    • 作用:获取事件的传播路径。
    • 实现:
    • 优先使用 ev.composedPath()。
    • 回退到手动遍历 parentNode 获取路径。
  • getMousePosition(e, container)

    • 作用:获取鼠标事件在指定容器中的位置。
    • 实现:
    • 如果未提供容器,则返回基于窗口的坐标。
    • 否则,根据容器的缩放比例和偏移量计算相对坐标。
  • getWheelDelta(e)

    • 作用:获取滚轮事件的滚动量。
    • 实现:
    • 根据浏览器和操作系统特性,计算滚轮事件的 deltaY 值。
    • 处理不同滚动模式(像素、行、页)的兼容性。
  • isExternalTarget(el, e)

    • 作用:判断事件的目标是否在指定元素外部。
    • 实现:
      • 遍历 e.relatedTarget 的父节点,判断是否与 el 相同。
      • 如果 relatedTarget 不存在,则返回 true。
  1. 兼容性处理
  • mouseSubst

    • 作用:处理鼠标事件的浏览器兼容性。
    • 实现:
      • 将 mouseenter 和 mouseleave 分别映射为 mouseover 和 mouseout。
      • 将 wheel 事件映射为 mousewheel(如果浏览器不支持 wheel)。
  • wheelPxFactor

    • 作用:根据浏览器和操作系统特性,计算滚轮事件的缩放因子。
    • 实现:
      • 针对不同浏览器和操作系统,设置不同的缩放因子。
  1. 导出与别名
export {on as addListener};
export {off as removeListener};
1
2

​

  • 作用:为 on 和 off 提供别名 addListener 和 removeListener。
  • ​意义:兼容旧版 API 或提供更直观的命名。 ​

# 总结

DomEvent是 Leaflet 中用于处理 DOM 事件的核心模块,具有以下特点:

  1. 跨浏览器兼容性:通过检测浏览器特性,选择合适的方式绑定和解绑事件。
  2. 高效的事件管理:使用唯一 ID 存储事件处理函数,支持批量移除和链式调用。
  3. 灵活的事件控制:提供阻止事件传播和默认行为的工具函数。
  4. 特殊事件处理:支持 touch、wheel、mouseenter 等特殊事件的兼容性处理。

这些功能为 Leaflet 的交互性(如地图拖动、缩放、点击等)提供了坚实的基础。

编辑 (opens new window)
上次更新: 2025/03/20, 00:55:00
浏览器判断方法Browser
DomUtil

← 浏览器判断方法Browser DomUtil→

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